Get AI summaries of any video or article — Sign up free
Critique your plugin #3 — Obsidian October 2024 thumbnail

Critique your plugin #3 — Obsidian October 2024

Obsidian·
5 min read

Based on Obsidian's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Avoid editing vault files with separate `vault.read` and `vault.modify` calls when other plug-ins might touch the same file; it can cause last-writer-wins data loss.

Briefing

Obsidian’s plug-in API has two recurring failure points—file edits that aren’t atomic and lifecycle code that runs at the wrong time. The biggest practical risk is data loss when multiple plug-ins modify the same vault file concurrently. A common pattern is calling `vault.read` to load a file, editing the string in memory, then calling `vault.modify` to write it back. That approach works until another plug-in performs the same read/modify sequence on the same file: both can read the original contents, but whichever plug-in writes last overwrites the other’s changes, effectively racing to “win” the disk write.

The fix is to use the vault “process” API, which pairs the read and write inside a single atomic transaction queued through Obsidian’s file system adapter. Because the file system adapter serializes disk operations, the process API ensures that the callback receives the latest on-disk contents and that the returned modified string is saved as one indivisible operation. Instead of manually orchestrating read-then-write, the callback function performs transformations on the provided contents and returns the updated text; Obsidian then commits it safely in the operations queue. This prevents other plug-ins from interleaving their own edits between the read and the write.

The second major source of confusion is the `onunload` lifecycle hook. Many developers mistakenly treat it like a “close Obsidian” event, but `onunload` fires only in two cases: when a plug-in is disabled (manually toggled off or uninstalled) and when the plug-in is updated. Updates are especially easy to misunderstand: the old version must be unloaded so the new version can be loaded cleanly. That means `onunload` is primarily for cleanup—removing global event listeners, releasing resources, and avoiding memory leaks.

Where things go wrong is putting “action” logic in `onunload`. The hook should not be used to save data to disk, and it’s also the wrong place to detach custom UI elements in a way that assumes a disable-only scenario. Developers often call something like `detachLeavesOfType` for their custom view types inside `onunload`, expecting it to remove UI only when the plug-in is turned off. But because `onunload` also runs during updates, detaching leaves during an update can wipe out user layout state. After the update, `onload` may recreate the view in a default sidebar position, discarding any custom placement.

As of Obsidian 1.7, plug-in custom views are automatically unloaded when a plug-in is disabled or uninstalled, removing the need for developers to manually disambiguate disable vs update. With that change, `onunload` can focus on cleanup, while Obsidian handles unloading custom views, preserving user layout state across updates. The result is a plug-in that edits files safely and manages lifecycle events without erasing the workspace the user built.

Cornell Notes

The core guidance is to avoid two common plug-in mistakes in Obsidian: non-atomic file edits and misuse of the `onunload` hook. Editing a file by doing `vault.read`, modifying the string, then `vault.modify` can lose changes when multiple plug-ins touch the same file concurrently. The vault “process” API fixes this by running the read-and-write as a single atomic transaction through the file system adapter’s queued operations. Separately, `onunload` does not run when closing Obsidian; it runs only on disable/uninstall and on plug-in updates. As of Obsidian 1.7, custom views are automatically unloaded on disable/uninstall, so manual detaching for layout cleanup is often unnecessary and can otherwise wipe user placement during updates.

Why does the simple `vault.read` → modify string → `vault.modify` pattern risk losing data?

Because multiple plug-ins can interleave their operations. Both plug-ins may read the same original file contents successfully, then each writes its own modified version. If plug-in B writes after plug-in A, plug-in A’s changes are overwritten, so the final file reflects only the last writer. The underlying issue is that the read and write happen as separate steps that can be separated by other queued operations from other plug-ins.

How does the vault process API prevent concurrent-edit races?

The process API wraps the read and write in a single atomic transaction. Instead of manually calling `vault.read` and `vault.modify`, the plug-in provides a callback to the process function. Obsidian supplies the current on-disk contents to the callback, and the callback returns the modified string. Obsidian then commits that returned value as one indivisible operation in the file system adapter’s operations queue, preventing other plug-ins from inserting edits between the read and write.

When does `onunload` actually run in Obsidian plug-ins?

`onunload` runs only when a plug-in is disabled/uninstalled and when the plug-in is updated. It is not triggered by simply closing Obsidian. During an update, the old code must unload so the new code can load, which is why `onunload` fires as part of the update lifecycle.

What belongs in `onunload`, and what should be avoided?

`onunload` is for cleanup: removing global event listeners, releasing resources, and preventing memory leaks. It should not be used for actions like saving data to disk. It’s also risky to detach UI elements in a way that assumes disable-only behavior, because the same hook runs during updates too.

Why can detaching custom leaves in `onunload` break user layout after updates?

If a plug-in detaches its custom view leaves during `onunload`, that detachment also happens during updates. Then `onload` may recreate the view in its default position (for example, the right sidebar) when it doesn’t find existing instances. The user’s prior layout position or custom view state gets lost because the update wiped the leaves before the new code could reuse them.

What changed in Obsidian 1.7 that affects custom view unloading?

As of Obsidian 1.7, Obsidian automatically unloads a plug-in’s custom views from the workspace when the plug-in is disabled or uninstalled. That means plug-in code no longer needs to manually handle whether the hook is running due to disable vs update for the purpose of unloading custom views. Developers can let Obsidian handle view unloading and keep `onunload` focused on cleanup.

Review Questions

  1. What specific concurrency problem occurs when two plug-ins both use `vault.read` followed by `vault.modify` on the same file, and how does the process API change the execution model?
  2. List the two situations that trigger `onunload`, and describe why updates make it easy to misuse this hook.
  3. How does Obsidian 1.7 change the recommended approach to detaching custom leaves or preserving custom view placement across plug-in updates?

Key Points

  1. 1

    Avoid editing vault files with separate `vault.read` and `vault.modify` calls when other plug-ins might touch the same file; it can cause last-writer-wins data loss.

  2. 2

    Use the vault process API so the read-and-write happen as a single atomic transaction in the file system adapter’s queued operations.

  3. 3

    Treat `onunload` as a cleanup hook for disable/uninstall and updates—not as a “close Obsidian” event.

  4. 4

    Do not perform disk-saving actions inside `onunload`; focus on releasing resources and removing global event listeners to prevent memory leaks.

  5. 5

    Be careful detaching custom views in `onunload`, because the same hook runs during updates and can reset user layout state.

  6. 6

    With Obsidian 1.7, custom views are automatically unloaded on disable/uninstall, reducing the need for manual detaching logic tied to lifecycle differences.

Highlights

Non-atomic read/modify/write sequences can erase another plug-in’s changes when both edit the same file concurrently.
The vault process API fixes races by returning a modified string from a callback inside an atomic transaction.
`onunload` runs on disable/uninstall and on plug-in updates—never just because Obsidian was closed.
Detaching custom leaves during `onunload` can wipe user placement after updates by forcing views back to default positions.
Obsidian 1.7 automatically unloads custom views on disable/uninstall, so manual detaching for that purpose is often unnecessary.

Topics

  • Vault Process API
  • Atomic Transactions
  • Plugin Lifecycle
  • onunload Cleanup
  • Custom Views