Erik Vaněk153 downloadsSync your vault with Filen end-to-end encrypted cloud storage.
Sync your vault with Filen end-to-end encrypted cloud storage, on desktop and mobile. Talks to Filen directly through the official SDK, so there is no server to run and nothing to configure outside Obsidian.
Back up your vault before using this. It is early software that has not yet been run against a real Filen account, and a sync plugin writes and deletes files on both sides by design. Deletions go to a trash you can recover from and conflicts keep both copies, but neither is a substitute for a copy of your notes somewhere this plugin cannot reach.
Not in the community plugin list yet, so pick one of these until it is.
With BRAT, which also keeps it updated:
NeDDy3z/filen-obsidian.By hand, if you would rather not add another plugin:
main.js and manifest.json from the latest release.<your vault>/.obsidian/plugins/filen-sync/, creating the folder if needed.Once it is in the community list, Settings, Community plugins, Browse, search for Filen will replace both of these.
Obsidian/<vault name>. It is created if it does not exist.123 456 from an authenticator app does not break the login.On a second device, install, log in, and point it at the same folder on Filen.
| Mode | Effect |
|---|---|
| Both ways (default) | Changes travel in both directions |
| Vault to Filen only | Your vault is the source of truth |
| Filen to vault only | Filen is the source of truth |
| Off, do not sync | Nothing runs, including the automatic triggers |
One-way modes do not simply ignore the other side. If the non-authoritative side deleted a file, it is put back from the authoritative one, because ignoring it would leave the two sides permanently disagreeing and re-deciding the same thing on every sync. So in "Vault to Filen", deleting a note on another device brings it back from this vault; in "Filen to vault", deleting a note here brings it back from Filen.
Conflicts follow the direction too. Pushing uploads your copy, and Filen's version history keeps the old one. Pulling saves your copy beside the note as note.conflict-20260818-143005.md before writing Filen's version, so nothing is lost either way.
Three independent triggers, configurable in settings:
On desktop the status bar shows the current state and clicking it syncs. Mobile has no status bar, so it reports through notices instead.
Every sync compares three things: your vault now, Filen now, and a snapshot of the last successful sync kept per device. The snapshot is what tells a deletion apart from a new file.
To do that it lists every file in the vault and reads each one's path, size and modification time. A sync plugin cannot decide what to transfer without seeing what is there. Exclusions and the .obsidian setting are applied to that listing afterwards, and file contents are only ever read for files that are actually being uploaded.
| At last sync | In vault | On Filen | Result |
|---|---|---|---|
| no | yes | no | upload |
| no | no | yes | download |
| no | yes | yes | same: skip. different: conflict |
| yes | yes | no | deleted on Filen, so delete locally |
| yes | no | yes | deleted locally, so delete on Filen |
| yes | changed | unchanged | upload |
| yes | unchanged | changed | download |
| yes | changed | changed | conflict |
Conflicts keep both copies. The Filen version is saved next to yours as note.conflict-20260818-143005.md and your version is uploaded. Nothing is merged, nothing is silently overwritten.
Deletions are recoverable. Local ones go to the vault .trash, remote ones to the Filen trash, and Filen keeps previous versions of overwritten files.
Modification times travel in both directions, which is what stops two devices bouncing the same file back and forth forever.
Force sync reconciles as if this device had never synced. It starts from an empty snapshot, so no delete branch above can fire: nothing is removed on either side, and anything that differs becomes a kept-both-copies conflict. Reach for it when two devices have drifted apart.
| Mode | Syncs |
|---|---|
| Off (default) | Notes only |
| Plugins and themes | Plugin code, the enabled-plugin lists, appearance.json, hotkeys.json, themes/, snippets/ |
| Everything except window layout | The whole folder, per-plugin settings included |
Synced settings only take effect after Obsidian restarts. It reads the enabled-plugin lists and the theme once at startup and holds them in memory, so a downloaded community-plugins.json or appearance.json changes nothing on a running app. The plugin tells you when a sync has written settings files. Reload reasonably promptly: if you change a setting first, Obsidian writes its in-memory copy back over the downloaded one, and the next sync uploads that instead.
workspace.json is never synced in any mode. Obsidian rewrites it every time you move a pane, so syncing it means two devices fighting over window layout forever, and a phone layout is not a desktop layout anyway.
The middle mode exists because plugin code is what you actually want on a new device, while plugins/<id>/data.json is the risky part: a running plugin rewrites its own settings from memory at unpredictable moments, so a downloaded copy can be clobbered or read half-written, and those settings are often device-specific. Pick "Everything" if you want it anyway.
This plugin's own folder is always skipped, so your credentials and this device's snapshot stay on this device.
npm install
npm run dev # watch build into main.js
npm run build # typecheck and minified build
| File | What it is |
|---|---|
src/sync.ts |
Decision table, direction narrowing, config filtering, snapshot format. No I/O |
src/local.ts |
The vault side, via Obsidian's adapter |
src/filen.ts |
The Filen side, via the SDK's cloud() calls |
src/main.ts |
Plugin, settings, commands, action executor |
Releases are built by GitHub Actions, not locally. Push a tag matching the version in manifest.json and .github/workflows/release.yml builds main.js, signs it with a build provenance attestation, and publishes the release. That lets anyone confirm the shipped bundle came from this source rather than someone's laptop:
gh attestation verify main.js -R NeDDy3z/filen-obsidian
src/filen.ts avoids sdk.fs() deliberately. Those helpers resolve paths through an internal cache only sdk.fs().readdir() fills, and sdk.fs().writeFile() is node-only. One getDirectoryTree() call gives the whole subtree instead, and uploads go through cloud().uploadWebFile().
.obsidian/plugins/filen-sync/data.json, as with any Obsidian sync plugin. They grant full access to your Filen drive, so treat vault backups as sensitive.@filen/sdk calls itself a work in progress. The version is pinned exactly.Issues and pull requests are welcome. The plugin is small on purpose, so the bar for adding a setting or a dependency is high: if a few lines of plain code will do, prefer that.
Getting set up:
git clone https://github.com/NeDDy3z/filen-obsidian
cd filen-obsidian
npm install
npm run dev
npm run dev rebuilds main.js on every change. To try it in a real vault, symlink the repo into that vault's plugin folder and reload Obsidian after each build:
ln -s "$(pwd)" "<your vault>/.obsidian/plugins/filen-sync"
Use a scratch vault rather than your real notes. This plugin deletes files on both sides when the decision table says to, and a bug in that table is the kind that eats notes.
Before opening a pull request, run npm run build, which typechecks and produces the bundle. Keep an eye on the size of main.js: most of it is the Filen SDK, and anything that pulls in another large dependency needs a good reason.
A few conventions:
sdk.fs() is avoided or why workspace.json is never synced.manifest.json. GitHub Actions builds, signs and publishes it.Contributions are accepted under the same license as the project.
Copyright 2026 Erik Vaněk. Licensed under the Apache License, Version 2.0.
The built main.js bundles @filen/sdk, which is licensed under the GNU Affero General Public License v3.0. AGPL-3.0 is a strong copyleft licence, so a distributed build that includes the SDK is a combined work and has to be offered under AGPL-3.0, whatever licence this repository's own source carries. Apache-2.0 code may be combined into an AGPL-3.0 work, but the result cannot be relicensed back to Apache-2.0.
In practice that means the Apache-2.0 licence above covers the source in src/, while any release artifact that embeds the SDK must be distributed under AGPL-3.0 with its source made available. Resolve this before publishing a release: either license the whole plugin AGPL-3.0 to match, or obtain different terms from Filen.
Other bundled dependencies are MIT (buffer, events, path-browserify, and the SDK's own MIT dependencies), BSD-2-Clause (dotenv, progress-stream), and BSD-3-Clause or GPL-2.0 (node-forge).