Skip to main content

Template Filters

ZotFlow registers custom Liquid filters on its template engines — link builders, HTML ↔ Markdown conversion, editable regions, and CSL citations. This page is the full reference. For context variables, see Template Variables & Defaults.

Custom Filters

ZotFlow registers the following custom filters on top of LiquidJS built-in filters:

process_nav_info

Applies to: All template types

Converts an annotation key into a URL-encoded JSON navigation parameter for constructing obsidian://zotflow deep links.

{{ annotation.key | process_nav_info }}

Input: "ABC12345" Output: %7B%22annotationID%22%3A%22ABC12345%22%7D

Applies to: Zotero Source Note

Generates a clickable link to open an attachment. By default, produces a ZotFlow protocol URI (obsidian://zotflow?type=open-attachment&...) that opens the attachment in ZotFlow's built-in reader. Pass "zotero" as the argument to generate a native Zotero URI instead.

{{ attachment | attachment_link }}
{{ attachment | attachment_link: "zotero" }}

Applies to: Zotero Source Note

Generates a clickable link that navigates to a specific annotation. By default, produces a ZotFlow protocol URI (obsidian://zotflow?type=open-annotation&...) that opens the parent attachment in ZotFlow's reader and navigates to the annotation. Pass "zotero" as the argument to generate a native Zotero URI instead.

{{ annotation | annotation_link }}
{{ annotation | annotation_link: "zotero" }}

Applies to: Zotero Source Note

Generates a clickable link to an item. By default, produces a ZotFlow protocol URI (obsidian://zotflow?type=open-note&...) that opens the item's Source Note. Pass "zotero" as the argument to generate a native Zotero zotero://select/... URI instead.

{{ item | item_link }}
{{ related | item_link: "zotero" }}

Auto-detection: When using the "zotero" argument, the Zotero URL prefix automatically uses groups/<id> for group libraries and library for personal libraries.

html2md

Applies to: Zotero Source Note

Converts Zotero HTML (ProseMirror format) to Markdown. Handles math formulas, code blocks, tables, images, and Zotero wrapper div attributes. Almost always chained with wrap_editable:

{{ note.note | html2md | wrap_editable: "NOTE", note.key }}

This filter is async; LiquidJS automatically evaluates it as a Promise. Only applicable to HTML strings.

wrap_editable

Applies to: Zotero Source Note, Local Source Note (local: "ANNO" and "PERSIST" only)

Wraps content in hidden HTML comment markers recognized by ZotFlow's editor extension, forming an editable region.

{{ value | wrap_editable: "TYPE", key }}
ParameterTypeDescription
"TYPE"string"NOTE" — Zotero child note; "ANNO" — annotation comment; "PERSIST" — local-only persist region
keystringCorresponding Zotero note key or annotation key — or, for "PERSIST", a stable id you choose

Output: Input string wrapped with <!-- ZF_TYPE_BEG_key --> / <!-- ZF_TYPE_END_key --> markers on their own lines. Markers must keep their own lines: a line that starts with an HTML comment becomes a CommonMark HTML block, and any markdown sharing that line renders as raw text in Reading view.

  • Note region: {{ note.note | html2md | wrap_editable: "NOTE", note.key }}
  • Annotation comment region: {{ annotation.comment | wrap_editable: "ANNO", annotation.key }}
  • Persist region: {{ "Write your thoughts here…" | wrap_editable: "PERSIST", "summary" }}

Annotation comments undergo a lightweight annoHtml2md conversion before entering the template context (<b>**, <i>*, <sub>/<sup> preserved, stray </> escaped), so you can pipe directly to | wrap_editable without going through | html2md.

Persist regions are local-only: their content survives every note update and is never synced to Zotero (see Source Notes → Persist Regions for the full behavior). Unlike the other two types, you may equivalently write the marker pair by hand in the template:

## My Summary
<!-- ZF_PERSIST_BEG_summary -->

<!-- ZF_PERSIST_END_summary -->

Id rules for persist regions:

  • You pick the id (summary, reading-todo, …). Allowed characters: letters, digits, _, - (max 64).
  • Ids must be unique within a note and stable across renders — the id is how ZotFlow finds the region's new home on each update. Don't generate ids from loop variables unless they're stable Zotero keys.
  • A blank line between the BEG and END markers is recommended (the filter form emits one) — it gives the empty region an obvious place to click. Directly adjacent markers still work, but the insertion point is harder to hit.

citation (CSL)

Applies to: Zotero Source Note and all Citation templates (they share the library-note engine; not available in local templates)

Renders a citation (cluster) with citeproc using real CSL styles. Takes a single item, a list of items, or annotations:

{{ item | citation }} → (Doe, 2020)
{{ item | citation: "ieee" }} → [1] (positional arg = style id)
{{ item | citation: style: "apa", locale: "de-DE", format: "text" }}
{{ items | citation }} → (Doe, 2020; Roe, 2021)

A list renders as one citation cluster — merging, ordering, and separators follow the style's rules. Use a for loop only when you want independent citations.

Annotation sugar — automatic page locators. Piping annotations cites the annotated item (annotation → attachment → top-level item) with pageLabel as the page locator; "p."/"pp." pluralization comes from citeproc and the locale:

{{ annotation | citation }} → (Doe, 2020, p. 5)
{{ annotations | citation }} → (Doe, 2020, p. 5; Roe, 2021, p. 12)

Items and annotations can be mixed in one list; locators apply per element.

Data source & style management: see CSL Citations — CSL-JSON is stored on sync (item.csljson, also readable directly in templates) with lazy backfill for older items, and styles are managed from the Activity Center's CSL tab.

Parameters (all optional):

ParameterDefaultDescription
positional / style:the default style from settingsstyle id, e.g. "ieee", "apa"
locale:style's default-locale → en-USBCP-47, e.g. "zh-CN"
format:the default format from settingstext / html / markdown / markdown-pure

Unknown parameter names or invalid formats raise errors listing what's supported. A style with an unresolved dependency chain (missing parent style or locale) raises an actionable error instead of degrading silently — install the missing piece from the Activity Center.

Known limitations: each filter call renders independently, so author disambiguation does not work across calls (two "Doe"s won't become "J. Doe"/"M. Doe"), and position-aware forms like ibid. don't apply. An explicit locator: argument is not implemented — page locators currently come from the annotation sugar only.

bibliography (CSL)

Applies to: Zotero Source Note and all Citation templates

Renders a bibliography for a list of items (a single item works too):

{{ items | bibliography }}
{{ items | bibliography: style: "apa", join: "\n\n" }}
{{ item | bibliography: "ieee" }} → [1] A. Author, …

Pass the whole list in one call: sorting and numbering are computed by citeproc over the entire list — a for loop would give every entry [1] and break ordering. Takes the same parameters as citation, plus join: (entry separator, default "\n\n").

Recipes:

Footnote workflow with note-class styles (Chicago full note): citation output is the footnote body:

[^{{ item.citationKey }}]: {{ item | citation: "chicago-note-bibliography" }}

Numbered styles (IEEE): a single-item render always numbers [1], so strip it deterministically and let Obsidian number the footnotes:

[^{{ item.citationKey }}]: {{ item | bibliography: "ieee" | remove_first: "[1] " }}

(Non-numbered styles have no [1] prefix — remove_first simply matches nothing.)

Related-works list:

{% assign cited = item.relatedItems | where: "resolved", true %}
{% if cited.size > 0 %}
## References
{{ cited | bibliography: style: "apa" }}
{% endif %}

process_raw_anno_json

Applies to: Local Source Note

Encodes raw annotation JSON as a URL-encoded string (with image data stripped to reduce size), for use inside %% ZOTFLOW_ANNO_..._BEG %% comment markers.

{{ annotation.raw | process_raw_anno_json }}