Vault hooks internals¶
This is the developer view of the hooks subsystem (
src/hooks/). For the user guide to configuring property hooks and folder automation, see Vault Hooks and Folder Automation.
Hooks.setup(plugin) (called from main.ts) wires four sub-features:
FileMenu.setup(plugin); // context menus on files/folders
EditorMenu.setup(plugin); // editor context menu + command
VaultHooks.setup(plugin); // the vault-event engine (this file)
CanvasNodeMenu.setup(plugin); // canvas node context menu
Note:
src/hooks/(vault events) is not the same assrc/architecture/hooks/(React hooks likeuseOnClickAway).
The event engine — hooks/VaultHooks.ts¶
On workspace.onLayoutReady, it registerEvents against:
- vault:
create,rename,delete,modify - metadataCache:
changed - workspace:
file-open
Every handler early-returns if VaultStateManager.INSTANCE.isFreezed() — this prevents feedback loops when the plugin itself is writing to the vault.
Folder automation (onCreate)¶
When a file is created, it computes the canvas path for the parent folder via canvasPathFromFolder(settings.foldersFlowsPath, parent.path). If that .canvas exists, it loads the flow and opens the step-selector modal. → A folder with an associated flow auto-launches it on every new note.
Housekeeping handlers¶
rename— folder rename renames the paired canvas; file rename keeps theribbonCanvas/jsLibraryFolderPathsetting pointers in sync.delete— folder delete removes its paired canvas and clearsjsLibraryFolderPath; file delete clearsribbonCanvas. Invalidates the flow cache.modify— invalidates thecanvas.flowscache for.canvasfiles.file-open— registers the opened markdown file intoVaultStateManager(snapshots its frontmatter for later diffing).
Property hooks (metadataCache "changed")¶
This is the "run JS when a property changes" feature (onCacheUpdate / processMetadataChange):
- Debounce per file by
METADATA_DEBOUNCE_MS = 60ms; skip non-.mdand files alreadyisOnProcess. - Read the property→hook map from
settings.hooks.properties; if empty, return. - Diff old vs new frontmatter per configured property (
valuesEqual, a JSON deep-equal). On a change, build aHookEvent. - Run the script (
executeHook): the hook's JS is wrapped in anAsyncFunctioninvoked asscriptFn(event, zf), whereeventis mutable andzf = await fnsManager.getFns(). The user script mutatesevent.response.frontmatter/event.response.removeProperties/event.response.flowToTrigger. - Write back any mutations via
FrontmatterService.setProperties(...), guarded byVaultStateManager.processStart/processFinishedto prevent re-entrancy. - Optional flow trigger: if the script set
flowToTriggerand the file is active, resolvecanvasPathFromFlowName(hooks.folderFlowPath, name)and open that flow's selector. - Frontmatter cache is revoked after
FRONTMATTER_CACHE_TTL_MS = 60_000ms.
Hook type contract (hooks/typing.ts)¶
type HookRequest = { oldValue; newValue; property; frontmatter };
type HookResponse = { frontmatter; removeProperties; flowToTrigger? };
type HookEvent = { request: HookRequest; response: HookResponse; file };
type HookSettings = { script: string };
type HooksConfig = { folderFlowPath: string; properties: Record<string, HookSettings> };
Config UI¶
The Hooks settings section renders a React PropertyHooksManager — a @dnd-kit sortable list of per-property hooks, each with a CodeMirror editor (with hook-specific autocomplete in hooks/extensions/autoconfiguration/), add/edit/delete, and uuid-v7 keys. It loads Obsidian's native property types via ObsidianNativeTypesManager.getAllTypes() and saves to plugin.settings.hooks.properties. A FoldersFlowSelectorHandler binds a folder-suggest to settings.hooks.folderFlowPath.
Taking a hook's change back (#455)¶
A hook's write is the one that surprises people: it lands on a note you were not looking at. So when a hook changes a note, the notice it shows carries an undo, live for 30 seconds, naming the property and the note. It runs the same plan the change panel would run for that batch.
It is a Notice, never a modal — ignoring it is the normal case — and after 30 seconds it goes without a second word. The undo stays available from the record.
A hook whose response set a property to the value it already had recorded no write at all (the record is a diff), so it offers nothing.
Menu integrations¶
FileMenu— folder menu "Edit folder workflow"; markdown-file menu to transform/edit/remove/copy/paste a note's step config;.canvascache invalidation.EditorMenu— editor context menu +editor-menu-flowcommand to open theeditorCanvasflow selector.CanvasNodeMenu—canvas:node-menuitems to edit/copy/paste embedded step config on ZettelFlow canvases.
VaultStateManager¶
A singleton (architecture/plugin) that (a) snapshots per-file frontmatter so property hooks can diff old vs new, and (b) exposes a freeze flag (isFreezed / processStart / processFinished) that hook handlers check to avoid reacting to the plugin's own writes.
See also¶
Event-driven workflows build on the same vault/metadata signals and the same VaultStateManager freeze state, but bind a whole flow (not just a script) to an event — off by default, throttled, and loop-guarded.