Skip to content
github-actions edited this page Jun 25, 2026 · 4 revisions

The Sync module integrates with Obsidian Headless to sync vaults without requiring the desktop app.

You need a subscription for Obsidian Sync and follow its initial setup guides, and then explicitly enable this module in your setup:

require("obisidian").setup {
  sync = {
    enabled = true,
  },
}

Commands

:Obsidian sync

Sync the current workspace vault. On first run, it will:

  1. Check if you have a working obsidian-headless CLI, if not it will prompt you go install a local copy managed by this plugin, or you can manually install a global CLI with npm install obsidian-headless -g
  2. Check if logged in (prompt for credentials if not)
  3. Check if any vault is configured (prompt to create at least one connection to remote)
  4. Apply sync configuration from plugin settings
  5. Run the sync

When switching workspaces, any running continuous sync process for the previous workspace is stopped before starting sync for the new workspace.

:Obsidian sync [SUBCMD]

Once you have configured a working setup, :Obsidian sync alone will work as a menu like top-level :Obsidian, you can <CR> for the select interface or <Tab> it for autocompletion.

Available subcommands:

  • :Obsidian sync start: start sync for current workspace
  • :Obsidian sync pause: pause sync for current workspace
  • :Obsidian sync setup: setup wizard
  • :Obsidian sync disconnect: disconnect existing connections
  • :Obsidian sync log: open log for current session

Statusline Component

This plugin exports a formatter statusline component via require("obsidian.sync.status").component, for finer control and live updates via dedicated statusline plugins, take lualine.nvim as an example:

require("lualine").setup {
  sections = {
    lualine_x = {
      {
        require("obsidian.sync.status").icon, -- raw icon
        color = require("obsidian.sync.status").color, -- default colors
        cond = require("obsidian.sync.status").cond, -- returns a boolean to indicate should update
      },
    },
  },
}

To redraw status more dynamically once the sync state changes for other statusline setups, put this in your config:

vim.api.nvim_create_autocmd("User", {
  pattern = "ObsidianSyncChanged",
  command = "redrawstatus",
})

The default sync highlight groups are:

  • ObsidianSyncSynced linked to DiagnosticOk
  • ObsidianSyncSyncing linked to DiagnosticWarn
  • ObsidianSyncPaused linked to DiagnosticInfo
  • ObsidianSyncError linked to DiagnosticError

You can override them in your config like this:

vim.api.nvim_set_hl(0, "ObsidianSyncSynced", { fg = "#98c379" })
vim.api.nvim_set_hl(0, "ObsidianSyncSyncing", { fg = "#e5c07b" })
vim.api.nvim_set_hl(0, "ObsidianSyncPaused", { fg = "#61afef" })
vim.api.nvim_set_hl(0, "ObsidianSyncError", { fg = "#e06c75" })

Sync Settings

These settings map directly to ob sync-config options from the Obsidian Headless CLI. They are applied automatically before each sync run.

Option Type Default Description
mode "bidirectional"|"pull-only"|"mirror-remote" nil (bidirectional) Sync direction. pull-only only downloads and ignores local changes. mirror-remote only downloads and reverts local changes.
conflict_strategy "merge"|"conflict" "merge" How to handle conflicts. conflict mode will generate conflict files in your repo, more support will be in later releases, for now prefer merge
file_types string[] { "image", "audio", "video", "pdf", "unsupported" } Attachment types to sync. Use an empty table {} to disable attachment syncing.
configs string[]|nil nil Obsidian app config categories to sync (e.g. "app", "appearance", "hotkey", "core-plugin", "community-plugin", etc). nil = leave server config unchanged. {} = explicitly disable config syncing. See Quirks.
excluded_folders string[] {} Folders to exclude from syncing.
device_name string|nil nil Device name shown in sync version history.
config_dir string ".obsidian" Config directory name.

Available API (require("obsidian.sync"))

  • sync.start(workspace?) - Start continuous sync for a workspace (defaults to current workspace)
  • sync.pause(workspace?) - Stop continuous sync for a workspace (defaults to current workspace)
  • sync.menu(subcmd?) - Open the sync action menu or run a specific subcommand (start, pause, log, setup, disconnect)
  • sync.is_configured(workspace) - Check if a workspace is configured for sync
  • sync.setup() - Run the interactive setup wizard
  • sync.disconnect() - Interactively unlink configured local vaults from remote

Note

lower-level CLI helpers live in require("obsidian.sync.client") and are not the primary public API surface.

Quirks

Running alongside the Obsidian desktop app

The Obsidian desktop app runs its own sync process. If both the app and obsidian.nvim sync are active on the same machine at the same time, they will race to push .obsidian/*.json config files (appearance, core plugins, hotkeys, etc.). When both sides have local changes the ob CLI resolves conflicts in favor of the remote version — your local Obsidian app changes get overwritten.

If you open neovim while the Obsidian app is also open, disable config syncing from the obsidian.nvim side:

sync = {
  enabled = true,
  configs = {},  -- explicitly disable .obsidian/*.json syncing
}

configs semantics:

Value Behavior
nil (default) Leave server's config-sync setting unchanged — safe if the Obsidian app is never open at the same time
{} Explicitly disable config syncing — pass --configs "" to ob sync-config
{"app", "appearance", ...} Sync only the listed categories

If you use neovim exclusively (no Obsidian desktop app), configs = nil is fine. If both coexist on the same machine, set configs = {} to avoid conflicts.

Options

---https://help.obsidian.md/sync/settings
---@class obsidian.config.SyncOpts
---
---@field enabled? boolean
---
---Which backend to use. Built-in: "obsidian" (obsidian-headless CLI).
---Custom backends can be added with `require("obsidian.sync").register(name, backend)`.
---@field backend? string
---
---When to run a sync.
--- - "continuous": keep a long-running sync process (default for obsidian backend).
--- - "on_write": run a one-shot sync (debounced) after each note save.
--- - "manual": only sync via :Obsidian sync start or explicit calls.
---@field trigger? "continuous"|"on_write"|"manual"
---
---Sync mode: bidirectional (default), pull-only (only download, ignore local changes), or mirror-remote (only download, revert local changes)
---@field mode? "bidirectional"|"pull-only"|"mirror-remote"
---
---Conflict strategy when a conflict is detected, NOTE: conflict is not currently supported in this client
---@field conflict_strategy? "merge"|"conflict"
---
---Attachment types to sync: image, audio, video, pdf, unsupported, empty table to disable attachment syncing
---@field file_types? obsidian.sync.FileType[]
---
---Config categories to sync. nil = leave server config unchanged. {} = explicitly disable config syncing (pass --configs ""). Non-empty list = sync only those categories.
---@field configs? obsidian.sync.ConfigCategory[]
---
---Config directory name, this is for obsidian app
---@field config_dir? string
---
---Folders to exclude
---@field excluded_folders? string[]
---
---Device name to identify this client in the sync version history
---@field device_name? string
sync = {
  enabled = false,
  backend = "obsidian",
  trigger = "continuous",
  mode = nil,
  conflict_strategy = "merge",
  file_types = { "image", "audio", "video", "pdf", "unsupported" },
  configs = { "core-plugin", "core-plugin-data" },
  excluded_folders = {},
  device_name = nil,
  config_dir = ".obsidian",
}

Clone this wiki locally