Einar Aleksejev72 downloadsKeep notes synchronized across multiple vaults using the sync_targets frontmatter property.
An Obsidian plugin to keep individual notes synchronized across multiple vaults using the sync_targets frontmatter property.
When working with multiple separate Obsidian vaults (such as a work vault and a personal knowledge base), you often need the exact same note to exist in more than one vault, frequently organized under completely different folder hierarchies.
Filesystem-level links (symbolic links, NTFS junctions, hardlinks) fail in practice:
As a result, notes diverge or cloud synchronization breaks.
Cross-Vault Sync maintains standalone, real .md files in each vault and automatically synchronizes their content bi-directionally in the background.
Both files remain identical because the sync_targets YAML property lists all file locations. It is enough to list the other locations. The plugin adds the note's own entry (see Behavior notes).
Each entry is a vault name, a slash, and the path in that vault as Copy path → From vault folder gives it, without the .md extension. An entry that ends with a slash is a folder: the copy in it gets the note's own name. Copy sync target in the file menu of a note or folder copies the whole entry, ready to paste:
---
sync_targets:
- Vault A/Projects/topic
- Vault B/Notes/
---
After the first sync the plugin replaces a folder entry with the exact file, here Vault B/Notes/topic. From then on the copies can be renamed independently: each vault keeps only its own entry current.
Every copy carries its own sync state in its frontmatter, like the header of a network packet. The plugin keeps no sync state anywhere else, so it works the same with the copies in one vault, in several vaults, or on several computers. Every sync that writes a copy writes three fields into it:
sync_version: 3
sync_hash: "a41f…"
sync_known:
- "Vault A/Projects/topic"
- "Vault B/Notes/topic"
sync_version is the number of the sync that wrote this contentsync_hash is the SHA-256 hash of the note without these three fieldssync_known lists the entries that had a copy at that syncA copy whose content no longer matches its own sync_hash was edited after its last sync. A sync reads every copy and decides:
| Copies | Result |
|---|---|
| one edited copy, or several with the same content | it wins quietly |
| copies edited in different ways | a conflict: the newest wins, the others are kept as <name>.sync-conflict-<hash>.md |
| a copy edited from an older version than the newest sync | a conflict with that sync |
| no edited copy, different versions | the highest version wins: the others missed a sync |
The version alone is not enough: an edit changes the text, not the version. The hash alone is not enough either: a copy that missed a sync matches its own hash too. A write that changes only these fields keeps the file's modification time, so a conflict never takes the plugin's own write for a newer edit.
The same vault can be in a different folder on each computer, for example under another user name. The entries name the vault, not its folder, so the same list works on every computer. The plugin keeps one table of vault folders per computer, in the app's local storage, which Obsidian Sync does not copy:
sync_targets names the note itself. When you edit sync_targets in an open note, the plugin adds the note's own entry. It does not add it when the list names an existing note in the same vault: such a list belongs to a copy. Conflict backups, OneDrive or Obsidian Sync conflict copies and duplicated notes carry the original's list but not their own entry, so they are never synced. Moving or renaming a note in Obsidian updates its own entry automatically, and the copies keep their names*.sync-conflict-<hash>.md) are never synced, also when you open themVault B/Notes, creates no note: the plugin reports the missing slash insteadsync_known that is missing was deleted on purpose and is not recreated automatically. Run Sync active note now to recreate itThe plugin reads and writes the files listed in sync_targets, also outside the vault, and writes conflict backups next to them. It watches the folders of the active note's targets for changes: the note you are working in. A note in another tab or split is not watched. Its copies are checked when you switch to it. It needs this to keep the copies in other vaults identical. It does not use the network. Its own data in the vault holds the settings only.
The plugin needs the Obsidian desktop app, version 1.13.4 or newer.
main.js, manifest.json and styles.css from a GitHub release<vault>/.obsidian/plugins/cross-vault-sync/The project follows the official sample plugin: TypeScript in src/ (main.ts holds only the plugin lifecycle), bundled by esbuild into main.js. It needs Node.js 22 or 24.
npm ci
npm run build # type check and bundle into main.js
npm run lint # ESLint with eslint-plugin-obsidianmd
npm run test:unit # type check the tests, run Vitest, fail below 100% coverage
npm run test:e2e # build, then run the end-to-end tests in real Obsidian
test/unit) use Vitest with obsidian-test-mocks for the Obsidian API and an in-memory file system shared by the test vaults. A write from outside Obsidian into a vault fires Obsidian's modify or create event, as Obsidian's own file watcher does, and test/unit/false-conflicts.test.ts lets those events start every synctest/e2e) use wdio-obsidian-service. They download Obsidian into .obsidian-cache and run on the plugin's minAppVersion and on the latest version. On Linux, Obsidian needs a display (WSLg on WSL 2, Xvfb in CI) and the libraries its .deb package depends on: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1 and libsecret-1-0. It also needs ALSA (libasound2t64 on Ubuntu 24.04), which the package does not list.github/workflows) lints every commit, runs both test suites and creates a draft release with build provenance for every tagnpm version patch|minor|major updates manifest.json and versions.json.
MIT License, see LICENSE (partially inspired by the local-folder-sync project by Jens Eldering).