Architecture overview
Developer-facing documentation. If you just want to use ZettelFlow, start at Home. This section explains how the plugin is built so you (or an AI assistant driving the harness) can extend it safely.
What ZettelFlow is, technically
ZettelFlow is an Obsidian plugin that turns a native Canvas file into a directed graph describing a note-creation workflow. When the user launches a flow, the plugin walks that graph as a wizard (root → steps → actions), collects input, and generates a new note by merging step-template files and running each step's actions.
It is a monorepo with two independent deliverables:
| Part | Path | Stack | Ships to |
|---|---|---|---|
| The plugin (the product) | src/, manifest.json, esbuild.config.mjs |
TypeScript, React 19, Zustand 5, CodeMirror 6, esbuild + Dart Sass | Obsidian community directory (dist/main.js, manifest.json, dist/styles.css) |
| Community backend (optional) | backend/, docker-compose.yml, containers/ |
Python 3.11, FastAPI, Pydantic v2, MongoDB (hexagonal architecture) | A server the user points the plugin at |
| Docs (this site) | docs/, mkdocs.yml |
MkDocs Material | GitHub Pages on push to main |
Technology stack (plugin)
- Language/build: TypeScript (
target es2018,baseUrl: srcfor bare-import aliases), bundled by esbuild (src/main.ts → dist/main.js, CJS). SCSS compiled byesbuild-sass-plugin(src/styles/main.scss → dist/styles.css).tsc -noEmitis a type-check gate only; esbuild is the real bundler. - UI: React 19 mounted into Obsidian modals/views via
createRoot/unmount. - State: Zustand 5 — used only for the note-builder wizard
(
useNoteBuilderStore). Everything else is a hand-rolledgetInstance()singleton. - Canvas integration:
monkey-aroundpatches Obsidian's internal Canvas view to emit custom workspace events. - Editors: CodeMirror 6 for the
.jsscript editor and the{{frontmatter}}live-preview extension. - Other:
@dnd-kit/*(sortable lists),@popperjs/core,uuid(v7),tiny-jsonc+json-stable-stringify(deterministic canvas serialization / lenient repair).
Source layout (src/)
src/
├── main.ts # Plugin subclass; onload/onunload orchestration
├── starters/ # Bootstrap: ZComponents (ribbon, settings tab, API), {{frontmatter}} processors
├── config/ # ZettelFlowSettings + DEFAULT_SETTINGS + settings-tab UI (3 sections)
├── architecture/ # The internal framework (see plugin-core.md)
│ ├── api/ # ActionsStore, CustomZettelAction base, fnsManager (zf), ZfVault/ZfScripts
│ ├── plugin/ # ObsidianApi facade, Lifecycle, canvas patcher + extensions, services, VaultStateManager
│ ├── components/ # React/DOM primitives: core (CodeView, search), settings modals
│ ├── monitoring/ # Logger + custom exceptions
│ ├── patterns/ # AbstractChain (chain-of-responsibility)
│ ├── lang/ # i18n (en/es), t()
│ └── styles/ # c() class-name prefixer (zettelkasten-flow__)
├── actions/ # 11 built-in actions, each a 4-file bundle (see actions-and-note-builder.md)
├── application/ # Product logic
│ ├── notes/ # NoteBuilder + NoteDTO + ContentDTO (the note-generation pipeline)
│ ├── components/ # noteBuilder wizard (Zustand state machine), shared React components
│ └── community/ # Community browser modals, galleries, HTTP client
├── zettelkasten/ # Step/flow editor modals (StepBuilderModal, SelectorMenuModal, installed editors)
├── hooks/ # Vault hooks: folder automation + property hooks + context-menu integrations
└── styles/ # SCSS (BEM-ish, prefixed .zettelkasten-flow__*)
Import aliases
There is no paths map. tsconfig.json sets baseUrl: "src", so every top-level folder
is importable as a bare specifier (and esbuild resolves the same way):
| Alias | Resolves to |
|---|---|
main |
src/main.ts |
config |
src/config/ |
starters |
src/starters/ |
architecture (+ subpaths) |
src/architecture/ |
actions |
src/actions/ |
application (+ subpaths) |
src/application/ |
hooks |
src/hooks/ |
zettelkasten |
src/zettelkasten/ |
styles |
src/styles/ |
The big picture — how a note gets made
User triggers a flow (ribbon / command / folder-create hook / editor menu)
│
▼
SelectorMenuModal ──mounts──► React wizard (useNoteBuilderStore)
│
▼ walks the Canvas graph (Flow/FlowNode) as a state machine
RootSelector → (ElementSelector | ActionSelector)* → ProgressBar
│ │
│ └─ each action.hasUI renders a React step; user submits a value
▼
NoteBuilder.build()
├─ read chosen step-template .md files → ContentDTO (body + frontmatter)
├─ execute() every action → writes to ContentDTO (frontmatter / body {{key}} / context)
├─ FileService.createFile(finalPath, body)
├─ FrontmatterService.processTypedFrontMatter (typed YAML write)
└─ postProcess() every action → side effects needing the TFile (e.g. backlinks) + Templater
▼
New note opens
Two cross-cutting engines run alongside the wizard:
- Canvas patcher (
architecture/plugin/canvas) — monkey-patches the Canvas view so the plugin can add "Edit step" / "Create managed step" menus and serialize canvas data deterministically. - Vault hooks (
hooks/) — react to vault/metadata events for folder automation (auto-launch a folder's flow on note create) and property hooks (run user JS when a frontmatter property changes).
Where to read next
| You want to… | Read |
|---|---|
| Understand bootstrap, settings, ActionsStore, canvas patching, logging, i18n | Plugin core |
| Add or understand an action / how the note is assembled | Actions & note builder |
| Understand folder automation & property hooks | Vault hooks internals |
| Understand the community feature and the FastAPI backend | Community & backend |
| Set up a dev environment and build/test/release | Getting started |
| Follow conventions / add a new action step by step | Contributing & conventions |
| Understand the Obsidian score and how we maximise it | Obsidian review & scoring |
| See known gaps and the revival roadmap | Project health & roadmap |
Glossary
- Flow — a
.canvasfile interpreted as a workflow graph. - Node / Step — a canvas node (
text/file/group/link/javascript) carrying aStepSettingsconfig (stored aszettelflowConfig). - Root — a node eligible to start a flow (not a
.jsfile). - Action — a unit attached to a step that contributes to the generated note (11 built-in types). Has a design-time config UI and an optional build-time wizard UI.
- Zone — where an action's value lands:
frontmatter,body({{key}}substitution), orcontext(ephemeral, shared between actions/scripts). zf— the scripting API object injected into Script actions and property hooks ({ external: { tp, dv }, internal: { vault, user } }).