Use this reference for lifecycle, persistence, networking, platform, and performance decisions. Confirm APIs against the installed obsidian declarations and manifest.minAppVersion.
- Lifecycle and cleanup
- Vault data integrity
- Settings persistence and migrations
- Networking, secrets, and privacy
- Mobile and platform compatibility
- Startup and runtime performance
- Official references
Plugin, ItemView, and many other Obsidian abstractions participate in the Component lifecycle. Decide who owns each resource and how long it should live. When a resource has a shorter lifetime than the plugin, put it in a child Component and attach it with addChild(); removing the child or unloading its parent then runs the child's cleanup.
Register resources through the owning plugin, view, or component wherever possible:
| Resource | Preferred registration or cleanup |
|---|---|
| Vault/workspace/metadata event | this.registerEvent(...) |
| DOM listener owned by plugin/component | this.registerDomEvent(...) |
| Repeating timer | this.registerInterval(...) |
| View content | ItemView.onClose() and containerEl.empty() |
| Shorter-lived feature/controller | Child Component with onload() / onunload() |
| Rendered Markdown | Pass the owning view/component to MarkdownRenderer.render() |
| Manual window/document listener | Store the exact handler/options and remove it |
| Timeout, observer, cache, in-flight task | Cancel, disconnect, clear, or invalidate explicitly |
Keep constructors cheap. Keep onload() to loading durable state and registering capabilities. Defer vault scans, restored-view normalization, migrations that require a ready workspace, and create-event listeners with workspace.onLayoutReady().
Make callbacks safe after unload. Invalidate in-flight work and avoid updating detached elements or stale views after an await. Do not create an orphan Component solely to satisfy an API parameter; attach it to a real lifecycle owner.
Treat every note as user-owned, concurrently editable data.
- Normalize configured paths with
normalizePath()and reject empty/root destinations when the operation expects a folder. - Resolve paths with
getAbstractFileByPath()and verifyinstanceof TFileorTFolderbefore use. - Use
cachedRead()for display and cache-consistent inspection. PreferVault.process()over a read/modify/write pair for transformations. - Use
Editorfor the active editing buffer. UseVault.process()for background transformations so Obsidian can serialize the write. Its processor callback must be synchronous. - Use
FileManager.processFrontMatter()instead of manually rewriting YAML properties. - Use
FileManager.trashFile()instead of permanent deletion. - Use Vault APIs instead of adapter or filesystem APIs unless the feature truly requires a desktop filesystem.
When asynchronous preparation is unavoidable, read the original, perform the asynchronous work, then enter Vault.process(). Inside its synchronous callback, compare the current content with the original and return unchanged content or surface a conflict if they differ. Never silently overwrite an intervening edit. Re-resolve files by path when long-running work may outlive a rename or deletion.
Make repeated operations idempotent where practical. Guard duplicate saves and report generation with an in-flight flag or key. Validate parsed output before committing it.
Define complete defaults. On load:
- Treat
nullas a fresh install. - Reject or safely recover from impossible top-level shapes.
- Merge nested objects deliberately; a shallow spread can erase new nested defaults.
- Normalize ranges, enums, paths, and legacy fields.
- Migrate secrets out of legacy settings without writing secret values back.
- Persist only when initialization or migration actually changed durable data.
Keep large user documents in the Vault as readable files when that matches the product. Keep data.json for ordinary settings and small resumable state. Document any data-format migration and make it retry-safe. Validate persisted values independently of settings-UI validation.
- Use
SecretComponentto choose or create a secret and store only its identifier in settings. - Resolve the secret at request time with
this.app.secretStorage.getSecret()or an injectedApp; handle missing values without logging them. - Prefer
requestUrl()over browserfetch()for plugin network calls. - Apply explicit timeouts or cancellation, validate status and response shape, and present actionable errors without echoing credentials or private note content.
- Disclose providers, transmitted fields, account requirements, telemetry, and external storage behavior in user-facing documentation.
- Do not add client-side telemetry. If server-side telemetry exists, follow current disclosure and privacy-policy requirements.
- Never commit real keys, vault data, screenshots containing private notes, or debug logs containing request bodies.
When isDesktopOnly is false:
- Avoid top-level imports of
fs,path,electron, or other Node-only modules. - Use
Platforminstead ofprocess.platform. - Dynamically load desktop-only dependencies inside a guarded branch.
- Check
FileSystemAdapterwithinstanceof; expect a different adapter on mobile. - Avoid unsupported regular-expression features for the declared mobile compatibility range.
- Test touch targets, software-keyboard overlap, narrow layouts, file picking, and network errors under mobile emulation and on an actual device for release-critical flows.
Set isDesktopOnly to true when a core feature cannot work without desktop APIs; do not advertise mobile support through a manifest flag alone.
- Avoid network calls, full-vault scans, and expensive parsing in
onload()and view constructors. - Wait for layout readiness before startup work and before listeners that would otherwise replay initialization events.
- Reuse metadata cache data, index incrementally, invalidate by path, debounce user-driven searches, and bound caches.
- Build a minified production bundle for release and measure startup time for changes that affect initialization.