ZettelFlow cookbook¶
Practical, copy-paste recipes for power users (epic #246 B3) — the hard to master end. They run in a Script action on a canvas step, a property hook, or (for the conditionals) a trigger condition. Full surface: the API reference.
In a Script action you have content (the note being built), context (values collected by earlier steps), note, app (the Obsidian app), and zf (the ZettelFlow API).
Compute a frontmatter field on creation¶
// Stamp a daily link and a computed slug.
const today = `[[${moment().format('YYYY-MM-DD')}]]`;
content.addFrontMatter({
dailyLink: today,
slug: (context.title ?? '').toLowerCase().replace(/\s+/g, '-'),
});
Move the new note based on a choice¶
// `context.targetFolder` was collected by a dynamic-selector step.
const file = app.workspace.getActiveFile();
await app.vault.rename(file, `${context.targetFolder}/${file.basename}.${file.extension}`);
List the notes in a folder (build an index)¶
const folder = zf.internal.vault.resolveTFolder('Projects');
const notes = zf.internal.vault.obtainFilesFrom(folder, ['md']);
content.add('\n## Projects\n' + notes.map(f => `- [[${f.basename}]]`).join('\n'));
Guard an event trigger with a condition¶
// In a step's trigger condition (see the trigger-conditions page):
event.property === 'status' && event.newValue === 'permanent'
Ask your knowledge graph¶
You don't always need a script — ZettelFlow surfaces the graph directly:
- Typed relations written by the relation actions (
supports:,contradicts:,expands:…) are ordinary frontmatter, so you can query them with Dataview: - Concept navigation walks the graph by typed relation; Open questions lists every unanswered
question::; the Evidence map synthesizes a note from its own graph — all read-only, offline, reachable from the Open ZettelFlow ribbon menu.
Ask ZettelFlow's own analyses¶
zf.knowledge gives your script the projections the plugin runs on itself — the model is already bound, so there is nothing to pass.
Stamp a new note with what the vault currently needs. Script action:
if (!zf.knowledge.ready()) return; // the index rebuilds on load
const debt = zf.knowledge.debt();
const neglected = zf.knowledge.unexamined({ limit: 3 });
content.addFrontMatter({
vault_debt: debt.score, // 0–100; 0 is clean
needs_my_judgement: neglected.map(idea => idea.path),
});
Offer only the notes that contradict this one. Dynamic selector:
const evidence = zf.knowledge.evidence("ideas/atomicity.md");
return evidence.contradicts.map(path => [path, path]);
Route a note by how connected it already is. Property hook:
const { groups } = zf.knowledge.neighbors(event.file.path);
const degree = groups.reduce((sum, group) => sum + group.targets.length, 0);
event.response.frontmatter.hub = degree >= 5;
return event;
Ask a model, and keep the last word¶
zf.ai.propose uses the provider you configured and shows you the answer before it goes anywhere. It returns your text, or null if you rejected or dismissed it.
if (!zf.ai.available()) return;
const summary = await zf.ai.propose(
"Summarise this note in one sentence:\n" + content.get(),
{ path: note.getFinalPath(), subject: "one-line-summary" }
);
if (summary) content.addFrontMatter({ summary }); // only what you accepted
Integrate Templater / Dataview¶
const tp = zf.external.tp; // Templater (if installed)
const dv = zf.external.dv; // Dataview (if installed)
See the API reference for the full surface and the .js editor's zf. / app. autocomplete.