Summary
- Custom note templates — full Eta template engine support for customizing bookmark note layout (frontmatter fields, body sections, ordering). Default template is a drop-in replacement for the previous
hard-coded formatter (zero breaking changes). - CodeMirror 6 template editor — syntax-highlighted template editor in settings using Obsidian's already-bundled CM6 (zero bundle overhead). Highlights Eta tags, YAML keys, and Markdown headings.
- Semantic sync comparison — file rewrites no longer trigger on format-only changes (whitespace, field reordering). Frontmatter is parsed and compared key-by-key; mtime is only updated when content actually
differs. Closes #19. - Correct attachment extensions — asset file extensions are now resolved from the HTTP Content-Type header (priority), then asset label, then URL. Per-type download toggles for banners, screenshots, PDF
archives, and full-page archives. Closes #36. - Robust note extraction — 3-tier fallback for bi-directional note sync: comment-delimited block () → ## Notes section → frontmatter note field. Custom templates can use it.noteBlock
for template-independent note editing. Partially addresses #22. - content_html template variable — crawled HTML content exposed as it.content_html in templates; only fetched from the API when the active template references the variable.
- HTML sanitization — content_html strips scripts, iframes, event handlers, and javascript: URLs before exposure to templates.
- Settings panel cleanup — fixed indented section headers and per-theme bordered setting cards; scoped all plugin CSS to .hoarder-settings.
- @dylan-park contributed a great fix to avoid constantly moving and renaming archived bookmarks in #40
Breaking changes
None for default users. The default template reproduces the previous formatter output character-for-character.
Users who have already added a ## Notes section above other sections in a custom layout: the ## Notes extractor now strips trailing footer links rather than truncating at any [-prefixed line, which may
change note boundaries in edge cases.
New in this release: Custom Templates
Getting started
In plugin settings, scroll to Note Template, toggle on Use custom template, and the editor will appear pre-populated with the default template.
Templates use Eta syntax (a lightweight EJS variant). All variables are accessed via it.*.
Bi-directional note sync
If you use Sync notes back to Karakeep, your template must support note extraction. There are two ways:
Option A — it.noteBlock (recommended for custom templates)
Place <%= it.noteBlock %> anywhere in your template body. This wraps the note in HTML comment delimiters that the plugin can reliably find regardless of surrounding structure:
Your note text here
Option B — ## Notes section (default template)
The default template uses a ## Notes heading. If you keep this section, the plugin will extract notes from it. The heading must appear in the template, and the note must be followed only by footer links.
Either way, the frontmatter must also include:
original_note: <%= it.yaml.note %>
The plugin uses this field to detect whether your local edits differ from Karakeep before deciding to push changes back.
Warnings
The editor validates your template as you type. Warnings (not errors) are shown for:
- Missing YAML frontmatter
- Missing
bookmark_idin frontmatter - Missing
original_note(breaks note sync) - Missing
## Notessection (only ifit.noteBlockis also absent)
If the template has a runtime error during sync, the plugin falls back to the default template and logs an error to the console.
Reset to default
Click Reset to Default to restore the original template at any time.
Syntax
| Syntax | Purpose |
|---|---|
<%= it.title %>
| Output a value |
<% if (it.summary) { %>...<% } %>
| Conditional block |
<% it.tags.forEach(function(tag) { %>...<% }) %>
| Loop |
Available variables
Bookmark fields
| Variable | Type | Description |
|---|---|---|
it.bookmark_id
| string
| Karakeep bookmark ID |
it.title
| string
| Resolved title |
it.url
| string | null
| Source URL |
it.description
| string | null
| Page description or text content |
it.created_at
| string
| ISO 8601 creation date |
it.modified_at
| string | null
| ISO 8601 modification date |
it.note
| string
| Your note text |
it.noteBlock
| string
| Note wrapped in comment delimiters (see below) |
it.summary
| string | null
| AI-generated summary |
it.archived
| boolean
| Whether the bookmark is archived |
it.favourited
| boolean
| Whether the bookmark is favourited |
it.content_type
| string
| "link", "text", or "asset"
|
it.content_html
| string | null
| Full crawled HTML (sanitized) |
it.tags
| string[]
| Sanitized tag names |
it.hoarder_url
| string
| Link to bookmark in Karakeep |
it.visit_link
| string | null
| Escaped source URL (null for assets) |
it.sync_highlights
| boolean
| Whether highlight sync is enabled |
Pre-escaped YAML values
Use these inside frontmatter — they handle quoting and special characters automatically.
| Variable | Description |
|---|---|
it.yaml.url
| URL, safe for YAML |
it.yaml.title
| Title, safe for YAML |
it.yaml.note
| Note, safe for YAML |
it.yaml.summary
| Summary, safe for YAML |
Assets
| Variable | Type | Description |
|---|---|---|
it.assets.content
| string
| Inline embed(s) for attachments |
it.assets.image
| string | undefined
| Wikilink to image file |
it.assets.banner
| string | undefined
| Wikilink to banner image |
it.assets.screenshot
| string | undefined
| Wikilink to screenshot |
it.assets.full_page_archive
| string | undefined
| Wikilink to HTML archive |
it.assets.pdf_archive
| string | undefined
| Wikilink to PDF |
it.assets.video
| string | undefined
| Wikilink to video |
it.assets.additional
| string[] | undefined
| Wikilinks to other files |
Highlights
it.highlights is an array sorted by position in the source. Each item has:
| Field | Description |
|---|---|
h.text
| Highlighted text |
h.color
| Highlight color (e.g. "yellow")
|
h.note
| Annotation on the highlight |
h.date
| Formatted date (e.g. "January 15, 2024")
|
h.created_at
| ISO 8601 date |
h.id
| Highlight ID |
Helper functions
| Function | Description |
|---|---|
it.escapeYaml(str)
| Escape a string for use in YAML |
it.escapeMarkdownPath(str)
| Escape a path for use in a Markdown link |
it.formatDate(iso)
| Format an ISO date as "Month DD, YYYY"
|