Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

iCloud Selective Sync

Your Obsidian vault has thousands of notes. Your iPhone should have dozens.

If you use iCloud Drive to store your vault, you already know the problem: iCloud syncs everything or nothing. It has no concept of selective sync. Symlinks do not work — iCloud ignores them. Obsidian Sync costs $96 per year and still syncs your entire vault. There is no built-in way to say "I only want my daily notes and my reading list on my phone, not my 40,000 archive notes and my 2GB of PDFs."

iCloud Selective Sync solves this at the plugin level. You mark folders in your vault with a single line of YAML frontmatter. The plugin copies only those folders — and nothing else — into your iCloud Drive location, where the Obsidian iOS app picks them up. Changes made on your phone flow back automatically. The rest of your vault stays local.

No subscription. No third-party sync service. No command line scripts to maintain.


How It Works

The plugin reads frontmatter from your folder notes. A folder note is the note that describes a folder — for example, Daily Notes/Daily Notes.md or Daily Notes/index.md. When a folder note contains icloud-sync: true in its YAML frontmatter, the plugin treats that folder as a sync root and mirrors it to your iCloud vault location.

---
icloud-sync: true
---

You can exclude specific files or subfolders within a synced folder:

---
icloud-sync: true
icloud-sync-exclude:
  - "drafts/"
  - "*.tmp"
  - "private.md"
---

To stop syncing a subfolder that lives inside a synced parent, add icloud-sync: false to that subfolder's folder note. This overrides the parent's setting.

The plugin then:

  1. Copies your marked folders into ~/Library/Mobile Documents/iCloud~md~obsidian/Documents/YOUR_VAULT/
  2. Watches that iCloud directory for incoming changes using the native fs.watch API (backed by FSEvents on macOS)
  3. Runs a periodic comparison every N seconds to catch any changes the watcher missed
  4. Uses a three-way state log (vault mtime, iCloud mtime, last-known-synced mtime) to determine which side changed and in which direction to sync

The Obsidian iOS app reads from that iCloud location. From the phone's perspective, it is a complete vault containing only the folders you chose.


Features

  • Mark any folder for iCloud sync using one line of YAML frontmatter — no UI configuration per folder required
  • Bidirectional sync: changes made on iPhone appear in your desktop vault; changes made on desktop appear on your phone
  • Real-time push: vault file changes (create, modify, delete, rename) are synced to iCloud immediately via Obsidian vault events
  • Real-time pull: iCloud directory changes trigger a debounced sync via fs.watch / FSEvents
  • Configurable periodic polling as a backstop for changes the watcher may miss
  • Nested sync control: a subfolder inside a synced folder can opt out with icloud-sync: false
  • Per-folder exclude patterns supporting folder names (drafts/), glob patterns (*.tmp, **/*.log), and exact filenames (private.md)
  • Four conflict resolution strategies: keep both versions, newest wins, vault always wins, or iCloud always wins
  • Three delete strategies: move to trash, delete permanently, or never propagate deletes
  • Conflict copies are named with a timestamp suffix (note.conflict-20260228-143022.md) and pushed to both sides so they are visible on your phone
  • Auto-detects your iCloud vault path — no configuration needed for standard setups
  • Status bar indicator showing idle, syncing, or error state
  • Optional sync notifications showing counts of pushed, pulled, conflicted, and deleted files
  • "Sync now" command and ribbon icon for manual triggering
  • "Full resync (reset state)" command to force a complete re-evaluation of all files
  • Debug logging mode that writes detailed sync activity to the developer console
  • Automatically ignores .obsidian/, .trash/, .DS_Store, .icloud placeholder files, and node_modules/
  • No files are written into your main vault's .obsidian/ folder in iCloud — plugin config stays private
  • Settings and sync state are persisted across restarts; the plugin resumes correctly after Obsidian is closed and reopened
  • Desktop-only plugin (declared in manifest) — it will not load on mobile where it is not needed

Installation

Option A: Manual install from GitHub release

  1. Go to the Releases page and download the latest release zip
  2. Extract the zip — you will find main.js, manifest.json, and styles.css
  3. Create the plugin directory in your vault:
    ~/YOUR_VAULT/.obsidian/plugins/icloud-sync/
    
  4. Copy all three files into that directory
  5. In Obsidian, go to Settings > Community plugins, disable Safe Mode if prompted, and enable iCloud Selective Sync

Option B: Build from source

Requirements: Node.js and Bun

git clone https://github.com/YOUR_USERNAME/obsidian-icloud-sync
cd obsidian-icloud-sync
bun install
bun run build

This produces main.js in the project root. Copy main.js, manifest.json, and styles.css to your vault plugin directory:

mkdir -p ~/YOUR_VAULT/.obsidian/plugins/icloud-sync/
cp main.js manifest.json styles.css ~/YOUR_VAULT/.obsidian/plugins/icloud-sync/

Then enable the plugin in Obsidian.


Setup

  1. Enable the plugin in Settings > Community plugins > iCloud Selective Sync
  2. Open the plugin settings and confirm the detected iCloud vault path looks correct (see Configuration Reference below)
  3. Choose your conflict and delete strategies
  4. Open a folder note for any folder you want on your iPhone
  5. Add icloud-sync: true to the YAML frontmatter and save the note
  6. The plugin detects the frontmatter change automatically and begins syncing that folder within seconds

If you do not use a dedicated folder note plugin, the plugin looks for a note inside each folder whose name matches the folder's own name (e.g., Projects/Projects.md) or index.md. You can customize this pattern in settings.


Configuration Reference

All settings are found in Settings > iCloud Selective Sync.

iCloud vault path

Default: auto-detected

The absolute path to the vault directory inside your iCloud Drive location. When left empty, the plugin automatically resolves this to:

~/Library/Mobile Documents/iCloud~md~obsidian/Documents/YOUR_VAULT_NAME/

This is the standard location that the Obsidian iOS app uses. You only need to set this manually if your setup is non-standard — for example, if you have multiple vaults with the same name or if your iCloud Drive is mounted at a custom path.

Sync interval

Default: 30 seconds Range: 10 to 300 seconds, in steps of 10

How frequently the plugin runs a full directory comparison between your vault and iCloud, regardless of whether any file system events fired. This is the backstop: iCloud Drive can delay materializing files (showing .icloud placeholder files instead of real content), and some changes may not produce fs.watch events reliably.

Lower values make pull-from-iCloud more responsive but consume slightly more CPU. 30 seconds is a reasonable default for most users.

Conflict strategy

What happens when a file has been modified on both the vault side and the iCloud side since the last sync.

  • Keep both versions (default): The iCloud version is saved alongside the vault version with a timestamped suffix — for example, note.conflict-20260228-143022.md. Both the original and the conflict copy are pushed to iCloud so you can see both on your phone and decide which to keep. This is the safest option and the recommended starting point.
  • Newest wins: The file with the later modification timestamp overwrites the other. Fast and simple, but if iCloud delays a file's materialization, its mtime may not accurately reflect when you actually edited it on your phone.
  • Vault always wins: The desktop vault version is always authoritative. Changes made on iPhone are discarded if a conflict arises. Use this if your phone is primarily for reading.
  • iCloud always wins: The iCloud version overwrites the vault version. Use this if your phone is your primary writing device and conflicts on desktop are acceptable to discard.

Delete strategy

What happens when a file is deleted on one side.

  • Move to trash (default): When a file is deleted on the iCloud side (for example, you deleted a note on your phone), the plugin moves the corresponding vault file to Obsidian's .trash/ folder rather than permanently deleting it. This is the safest option — you can always recover the file from the trash.
  • Delete permanently: Deletions are propagated as permanent deletes on both sides. Use this only if you are confident in your sync setup and want a true bidirectional delete.
  • Never delete: Deletions are never propagated. If a file disappears from one side, the other side keeps its copy untouched. Files can only be added or updated, never removed through sync.

Folder note filenames

Default: {folder}.md, index.md

A comma-separated list of filename patterns the plugin checks when looking for a folder note inside each directory. The placeholder {folder} is replaced with the actual folder name.

With the default setting, for a folder named Daily Notes, the plugin looks for:

  • Daily Notes/Daily Notes.md
  • Daily Notes/index.md

If you use a different folder note convention — for example, the Folder Note plugin configured to use README.md — set this to README.md. If you use multiple conventions across your vault, list all of them separated by commas: {folder}.md, index.md, README.md.

Show status bar

Default: on

Displays a small indicator in the Obsidian status bar showing the current sync state: idle (cloud icon), actively syncing (refresh icon), or error (warning icon).

Notifications

Default: on

Shows an Obsidian notice after each sync cycle that transferred or deleted at least one file, reporting the count of files pushed, pulled, conflicted, and deleted. No notice is shown when everything is already up to date.

Debug logging

Default: off

When enabled, the plugin writes detailed logs for every sync decision to the browser developer console (accessible via View > Toggle Developer Tools in Obsidian). Logs show which files were scanned, which operations were determined, and why each decision was made. Useful for diagnosing unexpected behavior. Disable in normal use.


Frontmatter Reference

Sync configuration is stored in folder note YAML frontmatter. Both keys are optional — a folder note without either key has no effect on sync.

icloud-sync

Type: boolean (true or false)

Set to true to mark a folder as a sync root. The plugin will copy all files in this folder (and its subfolders, recursively) to the iCloud vault location.

---
icloud-sync: true
---

Set to false explicitly to opt a subfolder out of sync even if a parent folder has icloud-sync: true. This overrides the parent:

---
icloud-sync: false
---

A folder whose note has no icloud-sync key at all is neither a sync root nor explicitly excluded — it inherits its sync status from the nearest synced ancestor.

icloud-sync-exclude

Type: list of strings

A list of patterns to exclude from sync within this folder. Patterns are evaluated relative to the sync root folder. Supported pattern types:

Folder patterns — a trailing / matches a directory and everything inside it:

icloud-sync-exclude:
  - "drafts/"
  - "archive/"

Glob patterns* matches any characters except /; ** matches across path separators:

icloud-sync-exclude:
  - "*.tmp"
  - "*.log"
  - "**/*.bak"

Exact filenames or paths — matches the filename anywhere in the tree, or an exact relative path from the sync root:

icloud-sync-exclude:
  - "private.md"
  - "attachments/large-video.mp4"

You can combine all three types in a single list:

---
icloud-sync: true
icloud-sync-exclude:
  - "drafts/"
  - "*.tmp"
  - "private.md"
---

The following paths are always excluded regardless of configuration: .obsidian/, .trash/, .DS_Store, .icloud, and node_modules/.


How Sync Works

Push (vault to iCloud)

Obsidian fires internal events whenever a file is created, modified, deleted, or renamed in your vault. The plugin listens to all four events. When a change occurs in a file that belongs to a synced folder, the plugin immediately copies the file to the corresponding path in the iCloud directory, preserving the original modification timestamp. Rename events delete the old iCloud path and create the new one.

Pull (iCloud to vault)

Two mechanisms run in parallel:

  1. FSEvents watcher: The plugin registers a recursive fs.watch on your iCloud base directory. When iCloud materializes a file that was changed on your iPhone, this fires and triggers a debounced full sync (2-second debounce to batch rapid changes).
  2. Periodic polling: A full directory comparison runs every N seconds (configured via Sync interval). This catches any changes that FSEvents may have missed — for example, when iCloud Drive was downloading files in the background while Obsidian was closed.

Three-way state comparison

The plugin maintains an internal record of the last-known vault mtime, last-known iCloud mtime, and sync timestamp for every file it has seen. During a full sync, it compares current mtimes against this record to determine what happened:

  • File exists in vault only, no prior record: new in vault, push to iCloud
  • File exists in iCloud only, no prior record: new in iCloud, pull to vault
  • File exists in vault only, prior record shows it was synced: deleted from iCloud, apply delete strategy
  • File exists in iCloud only, prior record shows it was synced: deleted from vault, apply delete strategy
  • File exists in both, only vault mtime changed: vault is newer, push
  • File exists in both, only iCloud mtime changed: iCloud is newer, pull
  • File exists in both, both mtimes changed: conflict — read both files and compare content; if content is identical, update the state record and skip; otherwise apply the conflict strategy

Content comparison on apparent conflicts guards against mtime drift — iCloud can alter a file's mtime during its own sync process without changing the content.

Concurrency guard

The plugin tracks files that are actively being synced in a set. If an event fires for a file that is already mid-sync, it is skipped to prevent feedback loops (e.g., pulling a file from iCloud triggering a push event back to iCloud).


Commands

Both commands are available in the Obsidian command palette (Cmd+P).

iCloud Sync: Sync now Immediately runs a full bidirectional sync of all configured sync roots. Also available as a ribbon icon (the circular arrows icon in the left sidebar).

iCloud Sync: Full resync (reset state) Clears the internal sync state record and runs a complete re-evaluation of all files. Use this if you believe the plugin's state has drifted from reality — for example, after manually copying files into the iCloud directory, after restoring a vault from backup, or after the plugin was disabled for a long period. This operation will re-examine every file and re-sync anything that has diverged.


Limitations

macOS and desktop only. The plugin uses Node.js file system APIs (fs, path, os) and the macOS iCloud Drive directory structure. It will not load on Obsidian Mobile and has no effect there — it is declared isDesktopOnly: true in its manifest.

No API to force iCloud upload. The plugin copies files into the iCloud Drive directory on your Mac. Whether and when iCloud Drive uploads those files to Apple's servers — and when your iPhone downloads them — is entirely up to the iCloud daemon. Under normal conditions iCloud uploads new or changed files within seconds. If your Mac is offline, iCloud Drive is paused, or your iCloud storage is full, sync to your phone will stall. The plugin has no visibility into this; it can only confirm that the file is in the iCloud directory on disk.

.icloud placeholder files. When iCloud has not yet downloaded a file from the cloud to your Mac, it leaves a .icloud placeholder file in its place (e.g., .note.md.icloud). The plugin skips .icloud files during scanning. This means a file that exists on your phone but has not been materialized on your Mac will not be visible to the plugin until iCloud downloads it. The periodic polling will pick it up once it appears.

Folder notes must exist. The plugin discovers which folders to sync by reading folder note frontmatter. If a folder has no folder note (or its folder note does not match any configured filename pattern), the plugin cannot know that folder should be synced. You must create the folder note and add the frontmatter manually.

No binary diff. All file operations are full-file copies. There is no delta sync. For large files (audio recordings, PDFs, large images), every modification copies the entire file again. This is intentional — it keeps the implementation simple and avoids corruption risks. For most Obsidian use cases (Markdown notes and small attachments), this is not a practical concern.


Building from Source

Requirements: Node.js 20 or later and Bun.

# Clone the repository
git clone https://github.com/YOUR_USERNAME/obsidian-icloud-sync
cd obsidian-icloud-sync

# Install dependencies
bun install

# Build for production (runs TypeScript type check, then esbuild)
bun run build

# Copy to your vault
mkdir -p ~/YOUR_VAULT/.obsidian/plugins/icloud-sync/
cp main.js manifest.json styles.css ~/YOUR_VAULT/.obsidian/plugins/icloud-sync/

For development with watch mode:

bun run dev

This runs esbuild in watch mode and rebuilds main.js on every source change. Reload the plugin in Obsidian with Settings > Community plugins > reload or use the Hot Reload community plugin.


License

MIT. See LICENSE.

About

Obsidian iCloud Sync

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages