Pascal Lauener219 downloadsSync multiple vault folders to multiple Google Drive folders, including Shared Drives. Each pairing syncs a subfolder or the whole vault two-way, mirrors the folder structure, and picks up files created directly in Drive.
Automatic two-way sync between your Obsidian vault (whole vault or a single subfolder) and a Google Drive folder. You can edit, add, or delete files in Obsidian or directly in Google Drive — both sides are reconciled.
.trash folder in the vault, Drive deletions to the Drive trash — never permanentThis plugin can delete and overwrite files on both sides. Misuse can cause data loss. Take these precautions seriously:
Before you run the plugin for the first time (or with a new folder): back up your vault AND your Google Drive folder. Two-way sync is powerful, but any sync software can destroy data when misconfigured. The trash (local .trash, Drive trash) is a safety net — not a substitute for a real backup.
sync-state.json into a new or empty vaultThe plugin remembers the last sync state in sync-state.json (in the plugin folder). This file belongs exclusively to the vault where it was created.
If you copy this file into a new/empty vault, the plugin will "believe" that all the files it lists were deleted locally — and propagate those deletions to Google Drive. Your Drive folder would be emptied.
The plugin protects you as far as it can (the state file is bound to vault name + Drive folder and is discarded on mismatch). But this protection fails if the new vault happens to use the same name and the same Drive folder. So don't rely on it:
sync-state.json or click "Reset sync state" in the settings. The next sync will then reconcile cleanly (download) instead of deleting.For your first run, try the plugin with a throwaway vault and a separate Drive folder before pointing it at your real notes.
The plugin keeps a state (the "base") per file and folder: MD5 hash, timestamps, and — crucial for deletion safety — which side the file actually existed on at the last sync (local/remote). On the next run it compares the current local state, the Drive state, and this base:
This "existed-before" proof prevents the classic sync catastrophe where an empty or foreign state wipes the whole Drive folder. On top of that, the base is bound to vault + Drive folder — a state file copied from another vault is detected and discarded.
Deleted files are never gone permanently: locally they go to Obsidian's .trash folder in the vault, in Drive to the trash.
The full decision table lives in src/reconciler.ts and is covered by unit tests in test/unit/.
Folder structure: the folder hierarchy is mirrored into Drive — subfolders are created and synced (even empty ones). Additionally, each file's vault-relative path is stored in Drive under appProperties.obsidianPath as a fallback.
New to this / not technical? Follow the illustrated, plain-language guide instead: Step-by-step Google setup — it walks through creating the Google app click by click.
The short version:
The loopback redirect (
http://127.0.0.1:<port>) is automatically allowed for the "Desktop app" type — you don't need to register a redirect URI manually.
Using the plugin on mobile too? No extra OAuth client is needed — you sign in on desktop and copy the token to mobile. See Mobile setup.
Manual (for development/testing):
git clone <this-repo> obsidian-google-drive-mirror
cd obsidian-google-drive-mirror
npm install
npm run build
The build produces a ready-to-install directory .build/ (main.js + manifest.json + styles.css). Copy its contents into your vault under:
<Vault>/.obsidian/plugins/google-drive-mirror/
Enable the plugin in Obsidian under Settings → Community plugins.
In the plugin settings:
.obsidian config folder), or turn it off — then the "Local vault folder" field appears, where you must pick a subfolder (e.g. Notes/Sync).drive.google.com/drive/folders/<THIS-ID>.The plugin runs on Obsidian mobile (iOS/Android) too, using the same Google
account and the same synced data. But mobile can't sign in directly:
Google removed the copy-paste (OOB) flow, rejects custom obsidian:// redirects
for every client type, and a phone can't run the desktop loopback server. So
there is no extra OAuth client to create — instead you move the sign-in
token from desktop to mobile:
That's it — mobile is now signed in and syncs like desktop. The token is account-level, so it works on any device with the same client credentials.
The client ID/secret come from a "Desktop app" OAuth client (the same one from step 1 of the setup). Its secret isn't truly secret for installed apps, so putting it on your phone is expected and fine.
When enabled, deleting a file (or folder) locally will not remove it from Google Drive — the Drive copy is kept, and it does not come back locally on the next sync. Default: off (a local deletion is propagated to Drive's trash).
Below the settings you'll find a sync tree showing all synced folders and files. Each entry has a checkbox:
The tree auto-refreshes after each sync, and there's a refresh button next to the heading for a manual update. The heading also shows how many entries are currently only in Drive.
Native Google Workspace files can't be synced as files — they have no downloadable binary, only the live cloud document. With the per-target "Sync Google Workspace files" toggle enabled, the plugin instead creates a small stub note in your vault for each one that embeds the live Google editor. Supported types and their stub suffixes:
| Type | Stub file |
|---|---|
| Google Docs | <name>.gdoc.md |
| Google Sheets | <name>.gsheet.md |
| Google Slides | <name>.gslides.md |
| Google Drawings | <name>.gdraw.md |
(Other Google types — Forms, Sites, etc. — are left as-is.)
These stub notes are pointers, not content: the file is never downloaded and never uploaded back, and the stubs are excluded from the sync entirely (they're never uploaded to Drive and never treated as deletions). Deleting a stub note doesn't touch the Google file; deleting the Google file doesn't auto-remove the stub. Default: off.
Tip: the embed fills the full note pane width. Sign-in uses your browser's Google session inside the embed.
A separate feature (its own tab in the settings) that keeps this plugin's own settings — your sync targets, filters, and options — in sync across your devices, so a second desktop or a phone inherits the same setup. It is not part of the folder sync above and never deletes anything.
How it works:
.obsidian structure (e.g. .obsidian/plugins/google-drive-mirror/data.json). Keep it separate from your note folders — the plugin's normal note sync automatically ignores anything under .obsidian, so the two never collide.Also sync other plugins' settings (optional, off by default). A second toggle additionally syncs the data.json of every other installed plugin (only for plugins already installed on both devices — it does not install or enable plugins). Each file is fully passphrase-encrypted, so any secrets other plugins store stay unreadable in Drive. Note: other plugins usually need an Obsidian reload to pick up downloaded settings.
⚠️ On the stored passphrase: it is stored obfuscated and bound to the device, not truly encrypted at rest — anyone with access to your local files could recover it (but they could already read your vault and credentials in that case). What it does guarantee: the passphrase never reaches Drive, so a leak of just the Drive folder cannot decrypt your settings. A settings file copied to another device won't carry a usable passphrase — you re-enter it there.
Trigger it manually with the command "Sync plugin settings now" or the button in the settings tab. It has its own live status line and log, just like the folder sync.
⏳ 3/12 …, ✅, ⚠️). Via "Show log" in the settings you see the full, live-updating log.If you change the Drive folder or the sync scope, the internal sync history is reset automatically, so the next run reconciles cleanly (instead of deleting).
npm install
npm run dev # esbuild in watch mode (inline sourcemap)
npm run build # typecheck + production build -> main.js
npm test # vitest (unit + integration)
Before any change to the sync logic: keep npm test green. The core logic (reconciler, deletion safety, folders) is test-covered.
Architecture:
| File | Responsibility |
|---|---|
src/main.ts |
Plugin entry point, commands, vault events, auto-sync timer, status bar |
src/oauth.ts |
OAuth loopback flow, token refresh against Google |
src/drive-client.ts |
Google Drive REST API wrapper (list/upload/download/trash, folders, Shared Drives) |
src/sync-engine.ts |
Orchestration, local hashing, execution of file and folder actions |
src/reconciler.ts |
reconcile() (files) + reconcileFolders() (folders): diff, conflict and deletion decisions |
src/sync-state.ts |
Persistent sync base (local/remote flags) in its own file |
src/sync-status.ts |
Live status + persistent log with retention |
src/storage.ts |
Read/write JSON files in the plugin folder |
src/settings-tab.ts |
Settings UI, log window |
src/suggesters.ts |
Autocomplete dropdowns for local and Drive folders |
src/logger.ts |
Central logger (debug logging optional) |
Syncing thousands of files is optimized in three ways:
A sync can be interrupted at any time (closing Obsidian, a crash) — without data loss:
changes.list with startPageToken) would reduce the per-sync listing cost further. Deliberately not implemented yet — it requires caching the full remote state between runs, which is high-risk for a data-loss-sensitive plugin.MIT