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.
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.