Skip to main content

git-sync

Round-trip Metabase content (cards, dashboards, transforms, snippets, collections, Library-published table/field metadata) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import, export (with branch guard and a preflight preview), branches, stash, add/remove a collection from sync. Load when the user wants to "import the latest changes", "export to git", "push my changes to the repo", "open a PR with my Metabase changes", "git sync", "dirty check", "stash before pulling", "add a collection to sync", or anything `mb git-sync …`.

Source facts

Repository
metabase/metabase-cli
Last source activity
October 7, 2026 at 18:00
Detected SKILL.md language
English
Stars
16
Forks
6

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
git-sync
description
Round-trip Metabase content (cards, dashboards, transforms, snippets, collections, Library-published table/field metadata) between an instance and a git remote via `mb git-sync …` — status, dirty / has-remote-changes checks, import, export (with branch guard and a preflight preview), branches, stash, add/remove a collection from sync. Load when the user wants to "import the latest changes", "export to git", "push my changes to the repo", "open a PR with my Metabase changes", "git sync", "dirty check", "stash before pulling", "add a collection to sync", or anything `mb git-sync …`.
allowed-tools
Read, Write, Edit, Bash, AskUserQuestion
requires
["remoteSync"]
# git-sync (representations ↔ instance) Metabase content (cards, dashboards, transforms, snippets, collections, …) can live in a git repo as YAML and round-trip in and out of a Metabase instance via the `git-sync` verbs. The instance is configured with a `remote-sync-*` settings block (URL, branch, token, type read-only/read-write); the CLI drives the sync tasks against `/api/ee/remote-sync/*`. Only collections flagged for sync serialize; everything else is local-only. Table and field metadata round-trips too, for Library-published tables in a flagged collection — see "Published table metadata" below. The repo and the instance are two ends of the same state; what "push my changes" means depends on where the changes were made. If they were made **in the instance** (CLI verbs against a read-write instance — cards edited, transforms created, tables published, metadata written), "push my changes" / "save this to the repo" / "open a PR with these changes" means driving the instance's export to a branch (`stash` or `create-branch` + `export`) and opening the PR from that branch — not reconstructing the changes as files. If the workflow is **repo-first** (content YAML edited in the repo, the instance imports it), plain `git` against the repo is the way — but only in the serialized layout already there: files in paths or formats the serializer doesn't own are invisible to Metabase and never apply on import. This skill covers the import/export workflow. Flag conventions and auth setup live in `core` (`mb skills get core`). To author content YAML by hand: the per-resource clause and settings shapes mirror the API form — query bodies follow the `mbql` skill, `visualization_settings` follow the `visualization` skill — except the portable YAML uses **name-based** references (e.g. `[Sample Database, PUBLIC, ORDERS, TOTAL]`, and entity-ids for cross-entity FKs) where the API form uses numeric ids. For the on-disk folder layout, model new files on what the synced repo already contains. ## Precondition: read state before mutating Always run `status` (or the individual checks below) before `import` or `export`. Importing on a dirty instance silently rejects unless you pass `--force`. <!-- requires: remoteSyncMerge --> Exporting after the remote moved on ends in a `conflict` task unless the export merges or forces. <!-- /requires --> ```bash mb git-sync status --profile <n> --json # → branch, dirty, current task mb git-sync is-dirty --profile <n> --json # → {is_dirty: bool}; instance has unexported changes mb git-sync dirty --profile <n> --json # → list the dirty objects mb git-sync current-task --profile <n> --json # → in-flight task (or idle) ``` <!-- requires: remoteSyncRemoteChanges --> ```bash mb git-sync has-remote-changes --profile <n> --json # → {has_changes: bool, remote_version, local_version, cached}; remote has unimported commits ``` <!-- /requires --> **Clean up before exporting.** If you've created entities you intend to delete (a failed transform you're going to retry, a card you authored to test a body shape, a draft dashboard) — do the deletes _before_ the first `git-sync export`. Once committed, the cleanup needs a second commit, and the failed entity stays visible in `git log` forever. For transforms, prefer `transform update <id>` over delete + create (see the `transform` skill). ## Import (remote → instance) ```bash mb git-sync import --branch <branch> --profile <n> # Default flags: --wait, polling --interval 2000 --timeout 600000 ``` Pulls the configured branch and applies it to the instance. Polls until the task reaches a terminal state (`succeeded` / `failed`). | Flag | Purpose | | ----------------- | ------------------------------------------------------------------------------------ | | `--branch <name>` | Defaults to the `remote-sync-branch` setting; override per-call. | | `--no-wait` | Return as soon as the task is queued; combine with `mb git-sync wait` later. | | `--force` | **Discards local Metabase-side dirty changes** (lossy). Confirm with the user first. | | `--timeout <ms>` | Polling deadline. Default 600 000. | | `--interval <ms>` | Polling cadence. Default 2 000. | Workflow: 1. Read state (above) — confirm `is_dirty: false` (or `--force` is intended). 2. `git-sync import --branch <branch>` — runs to terminal status by default. <!-- requires: remoteSyncRemoteChanges --> Between the two, confirm `has-remote-changes` reports `has_changes: true` — there's actually something to import. <!-- /requires --> <!-- requires: remoteSyncMerge --> ### Pull without discarding local work `mb git-sync import --merge` keeps un-pushed local changes and folds the remote's in by a three-way merge, where a plain import refuses on a dirty instance and `--force` discards the local work. Entities changed on both sides, or no merge base (a remote whose history was rewritten, or an instance that has never synced), end the task in `conflict` without touching local state; the conflicting entities are listed in the task's `conflicts`. It does not combine with `--force`. <!-- /requires --> ## Export (instance → remote) ```bash mb git-sync export -m "commit message" --branch <branch> --profile <n> ``` Pushes Metabase-side changes back to the configured remote. `-m` is the commit message; without it the server picks a default. Defaults to `--wait`. | Flag | Purpose | | ------------------- | -------------------------------------------------------------------------------------------------- | | `--branch <name>` | Defaults to the tracked `remote-sync-branch`. To push elsewhere, `stash` or `create-branch` first. | | `-m, --message <s>` | Commit message. | | `--force` | Force-push / overwrite remote. Confirm with the user. | | `--no-wait` | Don't poll. | Workflow: 1. **Branch guard** (below) — confirm the instance isn't tracking `main`/`master`, or that the user has explicitly accepted exporting to it. 2. Read state (above) — confirm `is-dirty` reports there's something to export. 3. `git-sync export -m "..."` — pushes and polls. 4. (Optional) `git-sync status` — verify `is_dirty: false` after. <!-- requires: remoteSyncBranchGuard --> `--branch` only asserts the branch you believe is tracked: the server rejects any other with a 409 naming the current one. Left out, the CLI reads the tracked branch first and refuses when none is tracked or the caller is not an admin. <!-- /requires --> <!-- requires: remoteSyncMerge --> ### Merge when the remote moved on `mb git-sync export --merge -m "..."` folds the remote's changes in by a three-way merge, pushes the result and reconciles it into Metabase, where a plain export ends in a `conflict` task. Entities changed on both sides, or a remote whose history was rewritten, still end it in `conflict` and write nothing. It does not combine with `--force`. <!-- /requires --> <!-- requires: remoteSyncExportPreflight, remoteSyncMerge --> ### Preview the push first ```bash mb git-sync export-preflight --profile <n> --json # → {has_changes, clean, conflicts, summary, force_push_casualties, reason} ``` A dry run against the live remote branch, writing nothing. Read it between the state check and the export, and decide from the fields: | Answer | Meaning | Move | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `has_changes: false` | The remote has not moved past the last sync, or nothing has been synced yet — and then `force_push_casualties` is empty whatever the remote holds. | `export -m "..."` applies as-is. Don't read empty casualties on a never-synced instance as leave to `--force`. | | `has_changes: true`, `reason: null` | The remote moved on. `clean` says whether a three-way merge would apply; `conflicts` names the entities changed on both sides; `summary` counts what a merge would fold in. | `clean: true`: `export --merge -m "..."`. `clean: false`: the conflicts need the user's call between the three options under "After a `conflict` task". A plain `export` ends in a `conflict` task, and so does `stash`: its new branch starts at the moved remote tip. | | `reason: "history-rewritten"` | The remote was force-pushed or rebased, so there is no merge base. | Only `export --force` can push, and only with the user's explicit go-ahead; `force_push_casualties` is exactly what it would delete or overwrite. | `force_push_casualties` is reported on every answer: it is the remote content a force push would discard instead of merging, so read it before ever passing `--force`. It is empty when nothing has been synced yet, yet `export --force` then replaces the remote's managed directories wholesale: check the remote before forcing a first export. Read the preflight before every export: an export that finds the divergence itself ends in `conflict`, and after that these fields can't be trusted (below). ### After a `conflict` task Never answer a conflict with a retry. A task that ends in `conflict` may make the server count the remote commit it saw as synced; the server version does not say whether it does. If it does, `has-remote-changes` and `export-preflight` report nothing pending, `force_push_casualties` comes back empty, and a retried `export`, `export --merge` or `import --merge` succeeds without bringing the remote's changes into Metabase; a later full or forced export can then overwrite them on the remote. So don't decide from those reads. Take the user's call between: - **The remote's side:** `import --force`. Discards Metabase's un-pushed work. - **Metabase's side:** `export --force`. Overwrites the remote, and its casualties can't be previewed after a conflict: show the user the remote's commits since the last export in git first. - **Both, through a reviewed PR:** 1. Note the tracked branch from `mb git-sync status --json` (`branch`): this is `<original>`. 2. `mb git-sync create-branch <name>` 3. `mb git-sync export -m "..."` 4. `mb git-sync import --force`, which reloads the instance from `<name>`. 5. Open a PR from `<name>` into `<original>` and review its diff before it merges. If the server counted the conflict as synced, `<name>` starts at the remote's tip, so the PR can undo the remote's edits to the conflicting entities without a git conflict: restore any such edit in the PR. 6. Once the PR merges, `mb git-sync import --branch <original>` tracks the original branch again and loads the merged result. <!-- /requires --> ### Branch guard: don't export to main/master without confirmation Sync work is conventionally done on a feature branch — exporting to `main` (or `master`) commits team-shared content directly. Before `git-sync export`, check the tracked branch and if it's `main`/`master`, ask the user whether to switch first. Read the current branch with `mb git-sync status --profile <n> --json | jq -r '.branch'`. If the branch is `main` or `master`, prompt with `AskUserQuestion`: > "The instance is tracking `<branch>` — exporting commits straight to it. Switch to a feature branch first?" >
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub