Aleksandr Terekhin177 downloadsSync a vault with a GitLab repository without requiring Git.
Sync an Obsidian vault with a GitLab repository without requiring Git on the device.
GitLab Gitless Sync keeps a vault and a single GitLab repository branch in sync using only the GitLab REST API. It is designed for mobile devices (where Git is unavailable) while staying fully interoperable with an ordinary desktop Git workflow against the same branch.
Requires Obsidian 1.11.4 or newer.
Once available in the Obsidian Community Plugins registry: open Settings → Community plugins → Browse, search for GitLab Gitless Sync, install and enable it.
Download main.js, manifest.json, and styles.css from the
latest release and copy them
into <vault>/.obsidian/plugins/gitlab-gitless-sync/, then enable the plugin.
Four steps: create the repository and a token, connect the vault, turn on automatic sync, add a
.gitignore.
Create a project in GitLab — either on gitlab.com or on a self-managed instance (any HTTPS base URL works). A brand-new empty project is fine: the plugin can create the first commit from your vault. An existing project with content is fine too.
Create an access token for the plugin:
Project → Settings → Access tokens → Add new token
Fine-grained permissions do not cascade — grant each one explicitly. If the target branch is protected, add Protected Branch Read.
If your GitLab instance does not support fine-grained access tokens, use a project access token
with role Maintainer and scope api. The plugin uses GitLab's REST API to create commits;
write_repository is only for Git-over-HTTP and is not enough for this plugin.
Keep the token private. If you stop using the plugin or move the vault to another repository, revoke the old token and create a new one.
Back up the vault before connecting. The plugin never deletes a file and never overwrites one side silently, but connecting does bring remote content into the vault.
Fill in Settings → GitLab Gitless Sync → GitLab repository:
https://gitlab.com, or your self-managed HTTPS instance
(e.g. https://gitlab.example.com). HTTPS is required.developing/obsidian-world.main.Then press Connect to GitLab. The plugin inspects both sides and shows a summary before changing anything; nothing happens until you confirm. One button handles every starting state:
<name> (local conflict).<ext>. Nothing is deleted.Keep Obsidian open until the confirmation notice appears. After that, Sync with GitLab (sidebar icon or command palette) syncs on demand at any time.
All automatic modes live under Settings → GitLab Gitless Sync → Sync and are off-by-default except Sync on startup. Pick the set that matches the device.
On desktop — sync by time. The desktop app stays open for hours, so time-based triggers are what keep it current:
8 is a good starting point; raise it if you get commits mid-thought.10 for an actively shared vault, 60 if you mostly write alone.On mobile — sync on app lifecycle. iOS and Android suspend the app instead of leaving it running, so foreground/background transitions are the most reliable mobile sync triggers:
Every mode runs the same engine as the manual button, and each trigger has a cooldown so overlapping events cannot stack up syncs.
.gitignoreThe plugin excludes only its own runtime folder (.obsidian/plugins/gitlab-gitless-sync/), its log,
the .git directory, and sync-metadata files. Everything else — including the rest of
.obsidian/ — is synced unless you ignore it. So on a new vault, create a .gitignore in the
vault root.
Recommended — keep settings shared, drop device-specific state. Obsidian's workspace*.json and
caches change on every device on every session and cause constant churn and needless conflicts:
.obsidian/workspace.json
.obsidian/workspace-mobile.json
.obsidian/cache
.trash/
Also reasonable — keep Obsidian's config out of the repository entirely. Choose this when settings, themes, and plugin sets should stay per-device:
.obsidian/
Not recommended — no .gitignore at all. Everything syncs, including workspace.json, which
means the desktop and the phone fight over layout state on every sync and generate conflict copies
of a file you never edit by hand.
Root and nested .gitignore files are both honored, for normal syncs and for the first commit when
seeding an empty repository. Files that are already tracked stay tracked even if a rule starts
matching them later — ignore rules keep new files out, they do not remove history. .gitignore
itself is an ordinary file: it is committed and shared, so the rules apply on every device.
Works without Git on the device. No local .git, no isomorphic-git, no shell Git — only HTTPS
calls to the GitLab REST API. That is what makes it work on iOS and Android.
Ordinary Git commits. The plugin writes real commits with your configured author, so desktop
collaborators keep using plain git pull/git push on the same branch and see nothing unusual.
Nothing is silently lost. Both sides of a conflict are always preserved; the losing version becomes a conflict copy instead of being overwritten. A delete that clashes with an edit produces a Markdown marker rather than removing the file. Binary files are copied byte-for-byte.
Fast, incremental syncs. A normal sync does not scan or hash the whole vault — it works from a local journal of what changed plus a remote diff. There is a separate Full audit and sync command for when you want an exhaustive local rescan.
Crash-safe. Sync runs as a transaction. If a remote commit succeeds but writing files locally is interrupted, the next sync detects the pending transaction and finishes it.
Local-only sync state. The tracked-file index, base commit, and journal live on the device and are never committed, so the repository stays clean and devices never fight over metadata.
Self-managed GitLab. Any HTTPS GitLab instance works, alongside gitlab.com.
| Setting | What it does |
|---|---|
| Sync on startup | One sync after Obsidian is ready. Desktop-oriented — on mobile the app is usually suspended before it runs. |
| Sync on app foreground (mobile only) | One sync when you reopen the app. The reliable mobile trigger. |
| Sync on app background (mobile only) | One sync when you leave the app. Best-effort: the OS may suspend the app first. |
| Sync after edits | One sync a few seconds after your last change, per Edit debounce. |
| Sync on a timer | One sync every Timer interval minutes, to pull other devices' changes. |
| Manual | Sync with GitLab — sidebar icon or command palette — runs the same engine on demand. |
A conflict is a file that changed on both the device and GitLab since the last sync. The plugin never overwrites either side; this setting only decides which version keeps the original path.
<name> (local conflict).<ext>.A deletion that clashes with a change on the other side produces a Markdown conflict marker instead of removing the file.
Off by default. Use Debug only while diagnosing sync behavior.The GitLab token is stored in Obsidian SecretStorage. It is never written to plugin data, logs,
conflict files, URLs, or GitLab commits. Logs redact credential-shaped fields such as
PRIVATE-TOKEN, Authorization, token, and password. All traffic is HTTPS — a non-HTTPS base
URL is rejected.
The plugin communicates only with the GitLab instance configured by the user. It does not collect telemetry or analytics and does not send vault data to any other service. A GitLab account/project and access token are required.
pnpm install.pnpm build type-checks (tsc -noEmit -skipLibCheck) and bundles the production plugin with
esbuild to main.js.pnpm test runs the Vitest suite.pnpm check runs tests, type-check, lint, and build.main.js and dist/ are git-ignored build artifacts and are never committed.This project is an independently maintained GitLab rewrite, based on GitHub Gitless Sync by Silvano Cerza. Modified in 2026 by Alexander Terekhin.
Licensed under AGPL-3.0-only. The upstream license and attribution are preserved — see
LICENSE and THIRD_PARTY_NOTICES.md.