Skip to content

Purpose-led inquiry checkpoints (#401)

An explicitly saved inquiry stores sensitive user text (purpose, response and gaps), selected and consulted paths, and its human resolution in the plugin's local data. This is not the content-free activity counter. One inquiry is retained until explicit clear/replace; the optional judgement log's 500-entry cap and recording toggle do not control it. Nothing is transmitted. Ordinary vault/device sync remains under the user's configuration, not a new plugin sync or encryption guarantee.

Drafts are not persisted by unrelated settings changes. A successful Save/Pause acknowledges one revision; newer edits or failed saves are not guaranteed after restart. Unsupported saved data is retained for explicit recovery/reset. Clearing inquiry bookkeeping never removes authored Markdown.

Capabilities & privacy

ZettelFlow discloses every capability it uses, ahead of Obsidian's upcoming capability-label system (network / file system / clipboard / script execution). The plugin collects no telemetry and transmits no personal data or vault contents.

What the plugin accesses

Capability Used? What for How
File system (vault) Always Read your Canvas flow files; create and edit notes as the output of a flow; install a system or a loose piece from Community into your vault, and take one back (Remove sends to Obsidian's trash only the files you never changed). A snapshot in the Reader (#763) writes one image per capture, in your attachments folder, only when you choose where it goes — and never the book. Obsidian's Vault API and FileManager — no hardcoded .obsidian paths, path-normalised, desktop and mobile.
Network Opt-in Only Community: read-only GETs that fetch the systems, steps, actions and markdown templates of the static ZettelFlow community catalog on GitHub raw. No backend, no account, no uploads. Network — the tutorial catalog, while Learn is open. Community's Learn room does a read-only GET of the tutorial catalog and its illustrations from GitHub — the same kind of request as the gallery, made only when Learn is shown, never at startup. No account, no upload, no telemetry. The last copy is kept in this vault's local storage, so Learn opens offline. The Library's articles (#744) are pages you saved with Web Clipper, read from your vault: ZettelFlow never fetches their address, and a remote picture inside a clipping is loaded by Obsidian's own Markdown renderer, exactly as in the editor. request/requestUrl in src/application/community/. No requests are made unless you open Community (the gallery's catalog) or its Learn room is shown (the tutorials). A book's designed pages (#771) fetch nothing: their styles, pictures and fonts come only from inside the book.
AI provider (external) Opt-in, off by default The optional 🤖 AI action category (summarize, classify, generate-questions, …). When you enable it and run an AI action, the (length-bounded) note content is sent to the single https OpenAI-compatible endpoint you configure to get a completion back. Nothing the model writes reaches a note until you accept it — every completion is a proposal you accept, edit or reject (#337, §XII) — and AI never runs in automations at all. requestUrl in src/architecture/ai/OpenAiCompatibleProvider.ts only. No bundled key, no default endpoint, no other endpoint, no telemetry. Off by default; input/output are capped and the endpoint must be https (or localhost). Your verdict on each proposal is recorded in the judgement record. Reading handwriting in the Reader (#748) sends an image of one ink note's strokes and its passage, only when you press Read as text — see Read as text. The Reader's six AI moves (#759: Why?, Challenge, Question, What's the claim, Reflect, Explain) send one selected passage, its paragraph and the one before, and the book's and chapter's titles — nothing from your vault — only when you click one; see Moves by your AI provider. Translate in the Reader (#766), on desktop and Android, sends one selected passage and the target language — nothing else — only when you click it; on iPhone and iPad it is the system's own. See Look up and translate. See AI provider setup.
Dictionaries (vault, read only) When you look a word up Look up in the Reader (#766) reads the StarDict dictionaries in the folder you chose (Settings → Reading), on the first look-up that needs one, and parses them on the device. No network call of any kind, with AI on or off. Nothing is ever written to that folder, and no dictionary is ever downloaded. vault.readBinary in src/architecture/plugin/library/DictionaryShelf.ts — never the Adapter API; a .dict.dz is inflated by the platform's own DecompressionStream. See Dictionaries for look up.
Speech (on the device) When you press Listen Listen in the Reader (#765) reads the book aloud in your device's own voices, through the Web Speech API. Listen uses your device's own voices; no text leaves the device: a voice the engine marks as remote (localService: false) is never chosen, not even as a fallback. Listening writes nothing; your voice and speed are kept in this device's local storage. On iPad and iPhone it stops when the screen locks or you leave Obsidian, and says so. speechSynthesis of the Reader's own window, in src/architecture/components/core/reader/readerListenSession.ts only — no request, no download. See Listen.
Clipboard On your click Writes only, never reads. The “copy” buttons put a step / action / canvas-node configuration on the clipboard as JSON so you can paste it elsewhere or share it. navigator.clipboard.writeText in the step, action and canvas-node editors. The Reader's and the Library's Cite puts the citation it shows on the clipboard when you press its Copy (#768), and Look up's Copy its answer. Nothing is ever read from your clipboard, and nothing is copied unless you press a copy button.
Vault enumeration Always Lists every markdown file path in the vault to build the offline knowledge model that every analysis reads (health, discovery, the graph, Cultivate). vault.getMarkdownFiles() via the ObsidianApi facade. Paths and metadata only, all local, no network. You can keep folders out of it entirely with the knowledge scope.
Dynamic code execution Opt-in Runs the JavaScript you author: the Script action, dynamic selectors, vault hooks and workflow-event conditions. Your code is handed zf and Obsidian's app, so it can do whatever the plugin itself can — including reading the whole offline knowledge model through zf.knowledge. That is a read-only surface: it exposes no vault writer, and your script still writes only through the note it is building or a hook's response. Reaching the AI provider from a script goes through zf.ai.propose, which cannot write without your verdict (§XII). Built in exactly one module, architecture/api/lib/FnConstructor.ts — both the async form the script surfaces use and the synchronous CommonJS wrapper that loads your library scripts (a guardrail test fails the build if a second site appears). A library script is loaded as a CommonJS module and is handed Node's require on desktop only, where Obsidian exposes it; on mobile it is simply absent. No remote code is ever fetched or executed — only code stored in your own vault or settings. A community system may ship JavaScript, and the install now says so before it writes anything, naming the steps that carry code and asking you to acknowledge it.
Judgement record On by default Stores the decisions you make on your own ideas — accept / modify / reject / confirm / challenge — so cognitive agency can be derived instead of invented. A local, bounded log (most recent 500) in the plugin's data.json: a note path, a short locale-free subject id, an origin and a verdict. Ruling a gap out (#534) adds one such descriptor — subject: "gap:<the other note's path>" — and writes nothing to the vault: two paths, an origin and a verdict, verified byte-for-byte by a write-path test. No note content, no AI output, no network. Honours the knowledge scope and can be turned off.

The satellite note (#419)

A step can declare a second, linked note, so one walk produces the literature/permanent pair. That is one additional file per declaring step, at a path announced before anything is written, and create-only: an existing note is never touched, and a name collision aborts the write instead of overwriting. No network, no clipboard, no code execution — and nothing interpretive: the template and the relation are the author's own choices, so §XII imposes no verdict gate.

Note-builder drafts (#410)

Closing the creation wizard mid-flow keeps what you answered, so you can resume instead of starting over. The draft lives in the plugin's data.json — local, never transmitted — and holds the note title, the step template paths, the recorded results of the steps you completed, the links you accepted and the ids of the nodes you walked. It can therefore contain text you typed into a step, which is why it is listed here next to the inquiry checkpoint rather than treated as metadata.

It is bounded (five canvases, newest kept), expires after 30 days, is deleted the moment the note is created or you choose start fresh, and can be switched off entirely in settings. A draft the current version cannot parse is retained, not deleted — the plugin never destroys work it merely fails to understand.

What the automated scan sees that is not ours

Obsidian's Community-hub scan reports runtime base64 encode/decode. That call does not come from ZettelFlow code — there is no atob/btoa anywhere in src/. It comes from the bundled 3D graph library (3d-force-graph, which vendors three.js loaders) used by the graph view. It decodes assets the library ships with; it does not encode anything about your vault, and it makes no request.

No telemetry

ZettelFlow does not embed analytics, crash reporting, or any phone-home. The only outbound requests are the opt-in community fetches (read-only) and the opt-in AI provider above. Nothing about your notes leaves your machine unless you enable AI and run an AI action (which sends the note content only to the endpoint you set), or press Read as text under an ink note (which sends that note's strokes, as an image, and its passage — to the same endpoint), or click one of the Reader's AI moves on a passage (which sends that passage, its paragraph and the one before, and the book's and chapter's titles — to the same endpoint), or click Translate on a passage on desktop or Android (which sends that passage and the target language — to the same endpoint). Look up makes no request at all: your dictionaries are read on the device. Sharing a template is a manual GitHub contribution you make yourself — the plugin never uploads anything.

This documentation website

The plugin has no telemetry; this website counts its readers, so we know which pages help and which are missing something. It uses GoatCounter, chosen for privacy:

  • No cookies, no personal data, no fingerprinting, no cross-site tracking — so no consent banner.
  • If your browser sends Do Not Track or Global Privacy Control, you are never counted.
  • What is counted: page views, the Was this page helpful? answer, and terms typed into the docs search — never who sent them.
  • The numbers are private to the maintainer; they are not sold or shared.

Diagnostics stay content-free (#401)

The inquiry journey reads the knowledge index, so a diagnostic emitted while indexing the very note you are working on must not become a side channel. The index diagnostics that journey can trigger — inline-relation enrichment, per-note upsert, and the conceptual-timeline/development-journal hooks — log only a fixed failure category (the error type, e.g. TypeError), never the note's path, its body, or the raw exception message. A private inquiry note cannot leak into the log this way.

The thinking-heatmap journal (local only)

The thinking heatmap is fed by a development-event journal stored in the plugin's own data.json as a per-day count map (day → count) — no note names, no content. It is pruned to the last ~year and makes no network request. It is on by default (it only records benign aggregate counts) and can be disabled under Settings → Thinking → What ZettelFlow remembers (Record development events).

The community gallery is fully static: the plugin only ever does read-only GETs of catalog files from GitHub raw — the gallery's docs/main_template.json when Community opens, and Learn's docs/learn/catalog.json with its pictures only when Learn is shown, at most once a session. There is no backend, no account, and the plugin never uploads anything — contributions are made through GitHub (an issue form + a PR).

Ahead of Obsidian's labels

Obsidian has announced machine-readable disclosure labels on the community hub. When that format ships, ZettelFlow will adopt it so these capabilities are declared in the plugin manifest as well as here. Until then, this page and the README section are the authoritative disclosure.