@metabase/cli
v0.2.2
Published
Metabase CLI
Readme
metabase-cli
Command-line client for Metabase. Logs in to an instance in your browser (OAuth, Metabase v62+) or with an API key, and stores credentials securely on your machine.
Supported Metabase versions
The minimum supported server is Metabase v0.58 (major 58). Anything older is unsupported.
Commands that need more than a baseline OSS server declare it — a higher minimum major version or a premium token feature. The server version and token features are detected and cached when you run mb auth login (or mb auth list). For those commands, a preflight check runs before the first request and refuses with an actionable message (exit code 2) when:
- the server is older than the command's minimum version, or
- the command needs a premium feature (e.g.
remote_sync,transforms) that isn't enabled.
Plain OSS commands against a v0.58+ server (the majority) carry no elevated requirement and skip the preflight entirely. When a gated command runs but the server version can't be detected (no cached probe), it proceeds with a warning rather than refusing. To bypass the check for a single run, pass --skip-preflight; to bypass it process-wide (e.g. in CI), set MB_CLI_SKIP_PREFLIGHT=1. Both are footguns — only for servers you know are patched.
Install
npm install -g @metabase/cli
mb --helpOr build from source:
bun install
bun run build
node dist/cli.mjs --helpThe binary is mb. Examples below use that name.
Quick start
mb auth login --url https://metabase.example.com
mb auth statusAuthentication
Credentials are stored per-profile. The default profile is named default. Use --profile <name> to manage additional profiles.
mb auth login
Log in to a Metabase instance and save the credential to a profile. Interactive login offers two methods:
- In your browser (recommended; requires Metabase v62 or newer) — the CLI opens Metabase, you sign in with your password or SSO and approve the CLI, and a short-lived access token plus a rotating refresh token are stored. Tokens refresh automatically; you never paste a secret.
- With an API key — paste a key from Admin settings → Authentication → API keys.
Against a server older than v62 the CLI detects the missing OAuth support and falls back to the API key prompt automatically. Supplying an API key (flag, env, or stdin) always skips the browser flow, so CI and scripts behave exactly as before.
On success the server is probed once — the rendered output shows the user, role (Admin/User), and Metabase version, and the same values are cached in <configDir>/profiles.json so later commands skip re-probing. Failure of either the auth probe (/api/user/current) or the server probe (/api/session/properties) rejects the login; an existing profile keeps its last-known-good credential and gains a lastFailure entry.
| Flag | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| --url <url> | Metabase URL, including any subpath if the instance is hosted under one (https://my.org.com/metabase). Falls back to MB_URL, then prompts. |
| --api-key <value> | API key. Skips the browser flow. Visible in shell history — pipe on stdin instead. |
| --client-id <id> | Pre-registered OAuth client id (only needed when dynamic client registration is disabled on the server). |
| --profile <name>, -p | Profile to write to (default: default). |
| --skip-verify | Save without contacting the server (no probe, no cache). |
Non-interactive (non-TTY) login requires an API key; resolution order: --api-key → piped stdin → MB_API_KEY (first non-empty wins). Without one, non-interactive login fails rather than prompting.
mb auth login # interactive: browser or API key
echo "$MB_KEY" | mb auth login --url https://m.example.com
mb auth login --url https://m.example.com < key.txtmb auth status
Show whether a profile is authenticated. The output includes the auth method (OAuth or API key) alongside the cached user, role, and server version.
mb auth status
mb auth status --json
mb auth status --profile staging| Flag | Description |
| ------------------------ | ---------------------------------------- |
| --profile <name>, -p | Profile to inspect (default: default). |
| --json | Emit JSON. Auto-enabled on non-TTY. |
mb auth list
List configured authentication profiles. All profile metadata (URL, auth method, last successful probe, last failure) lives in <configDir>/profiles.json at mode 0600; the secrets (API key, or OAuth access/refresh tokens) sit in the OS keychain when available, or inline in the same file when the keychain is unavailable.
auth list re-probes every profile, one at a time — a probe can refresh and rewrite an expired OAuth token, so probes are serialized to avoid racing on the shared profiles.json. On success it refreshes lastProbe (Metabase version, token features, user identity) and clears lastFailure; on failure it updates lastFailure and leaves the prior lastProbe/url/credential untouched. Rendered columns: Profile | URL | Auth | Status | Role | Version | Last probed. Failed rows append a one-line footer pointing at mb auth login --profile <name>.
mb auth list
mb auth list --json| Flag | Description |
| -------- | ----------------------------------- |
| --json | Emit JSON. Auto-enabled on non-TTY. |
mb auth logout
Clear stored credentials for a profile. For an OAuth profile the refresh token is also revoked server-side, best-effort: local credentials are cleared first and a revocation failure only warns, so a slow or offline server never blocks the logout.
mb auth logout --yes
mb auth logout --profile staging --yes| Flag | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| --profile <name>, -p | Profile to clear (default: default). |
| --yes | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
Transforms
CRUD on /api/transform. Bodies for create / update are JSON; resolution order: --body → --file → piped stdin (auto-detected when stdin is not a TTY).
mb transform list
mb transform list
mb transform list --jsonmb transform get <id>
mb transform get 1 --jsonmb transform dependencies <id>
List the upstream transforms this transform depends on (the ones that must run before it). The positional id is a transform id.
mb transform dependencies 1 --jsonmb transform create
cat transform.json | mb transform create
mb transform create --file transform.json| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb transform update <id>
mb transform update 1 --body '{"name":"renamed"}'Same --body / --file resolution as create. Stdin is auto-detected when not a TTY.
mb transform delete <id>
mb transform delete 1 --yes| Flag | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| --yes | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
mb transform run <id>
Trigger a manual run. Returns {message, run_id} and exits immediately. Pass --wait to poll until the run reaches a terminal status (succeeded, failed, timeout, canceled); the final field on the result holds the polled run state, and the command exits 1 if the final status is anything but succeeded. Pass --sync to additionally wait until the run's output table is registered and surface its target_table_id, so you can build MBQL cards against it — the run registers the table itself, so no separate db sync-schema is needed; --sync implies --wait.
mb transform run 1
mb transform run 1 --wait --json
mb transform run 1 --sync --json| Flag | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| --wait | Poll until the run reaches a terminal status. |
| --sync | After a successful run, wait until the output table is registered and return its target_table_id (implies --wait). |
| --timeout <ms> | Polling timeout in ms (default 600000). Used with --wait. |
| --interval <ms> | Polling interval in ms (default 2000). Used with --wait. |
mb transform cancel <id>
Cancel the currently-running run for a transform. Exits 0 with {canceled: true, id} on success; exits 1 with a 404 if the transform has no active run.
mb transform cancel 1
mb transform cancel 1 --jsonmb transform get-run <run-id>
Fetch a single run by run id (not transform id). Same compact / --full projection convention as transform get.
mb transform get-run 1 --jsonmb transform runs
List recent transform runs across all transforms, or filter to one. Drains all pages by default; pass --limit to cap.
mb transform runs
mb transform runs --transform-id 1 --json
mb transform runs --limit 10 --json| Flag | Description |
| --------------------- | --------------------------------------------------- |
| --transform-id <id> | Filter to runs of a single transform id. |
| --limit <n> | Cap total runs returned (default: drain all pages). |
Transform jobs
CRUD on /api/transform-job. Bodies for create / update follow the same --body / --file / stdin pattern as transforms.
mb transform-job list
mb transform-job list --jsonmb transform-job get <id>
mb transform-job get 1 --jsonmb transform-job create
mb transform-job create --body '{"name":"daily","schedule":"0 0 0 * * ?"}'| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb transform-job update <id>
mb transform-job update 1 --body '{"schedule":"0 0 6 * * ?"}'mb transform-job delete <id>
mb transform-job delete 1 --yes| Flag | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| --yes | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
mb transform-job run <id>
Trigger a job manually and return immediately. The job runs every transform carrying one of the job's tags, plus those transforms' dependencies.
mb transform-job run 1
mb transform-job run 1 --force-refresh --json| Flag | Description |
| ----------------- | --------------------------------------------------------------------------------------------------- |
| --force-refresh | Re-run the whole plan, including dependency transforms that are already fresh (skipped by default). |
mb transform-job transforms <id>
List the transforms a job would run, resolved by the job's tags. The positional id is a job id.
mb transform-job transforms 1 --jsonmb transform-job set-active <true|false>
Activate or deactivate every transform job at once (admin only). Inactive jobs do not run on schedule; manual runs ignore the flag.
mb transform-job set-active false
mb transform-job set-active true --jsonTransform tags
CRUD on /api/transform-tag. Tags group transforms and jobs; reference them by id via the tag_ids field on a transform or job. The four built-in tags (hourly, daily, weekly, monthly) drive the built-in jobs. There is no get-by-id endpoint — use list.
mb transform-tag list
mb transform-tag list --jsonmb transform-tag create
mb transform-tag create --body '{"name":"nightly"}'| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb transform-tag update <id>
mb transform-tag update 5 --body '{"name":"renamed"}'mb transform-tag delete <id>
mb transform-tag delete 5 --yes| Flag | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| --yes | Skip the interactive confirmation prompt. In non-TTY contexts the prompt is skipped automatically (kubectl/gh/docker convention). |
Databases
Read warehouse metadata from /api/database. The db group exposes the full database list, the per-database record with optional table/field hydration, schema and table inspection, and the two manual-sync triggers.
db is aliased to database.
Agent traversal — the hydration ladder: start with
db get <db-id> --include tables, the compact table map (id, name, schema, description per table) — one call that fits most databases. Pick the relevant tables, then fetch fields per table withtable fields <table-id>(bounded: a table has at most a few hundred fields).--include tables.fieldsis the full rollup — small databases only. When output outgrows the--max-bytescap, the error message names the next command down the ladder. On warehouses with hundreds of tables, traverse by schema (db schemas <db-id>→db schema-tables <db-id> <schema>) or find tables by name (mb search <term> --models table --db-id <db-id>).
mb db list
mb db list
mb db list --json
mb db list --saved --json
mb db list --include tables --json # every db with its compact table map| Flag | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --include <which> | Hydrate related entities. Currently only tables is supported (each database is returned with its compact tables). To map a single warehouse, prefer db get <id> --include tables. |
| --saved | Include the Saved Questions virtual database in the list. The virtual db has id -1337 and no engine. |
mb db get <id>
mb db get 1
mb db get 1 --json
mb db get 1 --include tables --json # + compact table map (fits most databases)
mb db get 1 --include tables.fields --json # + every field of every table (small databases only)| Flag | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --include <which> | Hydrate related entities. tables is the compact table map — the recommended first call for schema discovery. tables.fields is the full rollup and fits only small databases; on anything larger, take the map and fetch fields per table with table fields <table-id>, or traverse by schema (see above). |
mb db schemas <id>
List the schemas in a database. Schemas with no tables are excluded. Cheap and bounded — this is the right entry point for an agent walking a warehouse.
mb db schemas 1
mb db schemas 1 --jsonmb db schema-tables <id> <schema>
List the tables in one schema, sorted by display name. Returns compact projections without fields — pair with table get --include fields (or table fields <id>) per table you actually need to introspect.
mb db schema-tables 1 public
mb db schema-tables 1 analytics --jsonmb db sync-schema <id>
Trigger a manual schema sync (POST /api/database/:id/sync_schema). Returns { id, status: "ok" } once the sync has been queued; the actual work happens asynchronously on the server. Pass --wait to poll the database until its initial_sync_status reports complete (a database that has already finished its initial sync returns at once). To wait for a specific newly-materialized transform table to register, prefer mb transform run <id> --sync.
mb db sync-schema 1
mb db sync-schema 1 --wait --json| Flag | Description |
| ----------------- | ----------------------------------------------------------- |
| --wait | Poll until initial_sync_status reports complete. |
| --timeout <ms> | Polling timeout in ms (default 600000). Used with --wait. |
| --interval <ms> | Polling interval in ms (default 2000). Used with --wait. |
mb db rescan-values <id>
Trigger a rescan of cached field values (POST /api/database/:id/rescan_values). Returns { id, status: "ok" } once the rescan has been queued.
mb db rescan-values 1
mb db rescan-values 1 --jsonTables
Inspect and edit warehouse tables via /api/table. For agent-driven field introspection, table get --include fields is the default — it returns the table plus its columns in a single bounded response.
mb table list
Returns every table in the chosen database (or across all databases) as a flat compact list — no fields, no per-table hydration. On a real warehouse with hundreds of tables this is still bounded (kilobytes), but db schema-tables <db-id> <schema> is the better starting point when you know the schema.
mb table list
mb table list --db-id 1 --json| Flag | Description |
| -------------- | ----------------------------------- |
| --db-id <id> | Filter tables by their database id. |
mb table get <id>
Returns the basic table record (no fields). Pass --include fields to route through /api/table/:id/query_metadata so the response carries the table's columns compact-projected as fields — this is the default agent path for field introspection (the response also carries FK targets and dimensions under --full). Use mb table fields <id> if you only want the fields as a list envelope.
mb table get 42
mb table get 42 --json
mb table get 42 --include fields --json| Flag | Description |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| --include <which> | Hydrate related entities. Currently only fields is supported (bundles compact-projected columns). |
mb table fields <id>
List the fields on a table (a thin projection over query_metadata.fields). Use this when you want just the field array without the surrounding table metadata.
mb table fields 42
mb table fields 42 --jsonmb table update <id>
Patch a table (PUT /api/table/:id). Body fields: display_name, description, caveats, points_of_interest, entity_type, visibility_type, field_order, show_in_getting_started. Pass the body via --body, --file, or stdin (exactly one).
mb table update 42 --body '{"display_name":"Customers"}'
mb table update 42 --file patch.json
echo '{"description":"Customer dimension"}' | mb table update 42Publish status surfaces on the table itself — table get/table list carry is_published (and collection_id under --full). Publishing tables to the Library is done with mb library publish.
Fields
Inspect and edit individual columns via /api/field.
mb field get <id>
mb field get 100
mb field get 100 --jsonmb field values <id>
Fetch the cached distinct values list (GET /api/field/:id/values). Returns the FieldValues envelope ({ values, field_id, has_more_values }); empty values on fields whose has_field_values is none or search.
mb field values 100 --jsonmb field summary <id>
Row count and distinct count for the field (GET /api/field/:id/summary). Metabase returns this as an array-of-pairs; the CLI normalizes it to { field_id, count, distincts }.
mb field summary 100
mb field summary 100 --jsonmb field update <id>
Patch a field (PUT /api/field/:id). Body fields: display_name, description, caveats, points_of_interest, semantic_type, coercion_strategy, fk_target_field_id, visibility_type, has_field_values, settings, nfc_path, json_unfolding. Pass the body via --body, --file, or stdin.
mb field update 100 --body '{"description":"customer email","semantic_type":"type/Email"}'
mb field update 100 --file patch.jsonUpload
Load CSV/TSV data into the warehouse via /api/upload. Requires an uploads database configured on the server (Admin → Settings → Uploads); the destination db and schema are set there, not per-command. append/replace target a table created by a prior upload, and the CSV columns must match.
mb upload csv
Create a new table plus a model over it from a CSV file. Prints the new model id and table id.
mb upload csv --file data.csv
mb upload csv --file data.csv --collection 5
mb upload csv --file data.csv --json| Flag | Description |
| ------------------- | ---------------------------------------------------------------------- |
| --file <path> | Path to the CSV/TSV file to upload (required). |
| --collection <id> | Target collection id for the created model, or root (default: root). |
mb upload append <table-id>
Insert a CSV file's rows into an existing uploaded table.
mb upload append 42 --file more-rows.csv
mb upload append 42 --file more-rows.csv --jsonmb upload replace <table-id>
Replace an existing uploaded table's contents with a CSV file's rows.
mb upload replace 42 --file rows.csv
mb upload replace 42 --file rows.csv --jsonCards
CRUD plus query execution on /api/card. A "card" is a Metabase question, model, or metric. The query subcommand runs the card and either returns Metabase's JSON envelope or streams a raw CSV / XLSX export.
mb card list
mb card list
mb card list --filter archived --json
mb card list --filter using_model --model-id 42 --json| Flag | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| --filter <preset> | One of all (default), mine, bookmarked, database, table, archived, using_model, using_segment. |
| --model-id <id> | Required when --filter is database, table, using_model, or using_segment. |
mb card get <id>
mb card get 1
mb card get 1 --json --fullmb card query <id>
Run the card's query. Without --export-format, returns the Metabase JSON envelope (status, row_count, data: { rows, cols }, …). With --export-format csv, --export-format json, or --export-format xlsx, the export bytes stream straight to stdout.
mb card query 1 --json
mb card query 1 --json --limit 20
mb card query 1 --export-format csv > export.csv
mb card query 1 --export-format json > export.json
mb card query 1 --export-format xlsx > export.xlsx
mb card query 1 --parameters '[{"type":"category","value":"A","target":["variable",["template-tag","c"]]}]'| Flag | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
| --export-format <fmt> | Stream the export instead of the JSON envelope. One of csv, json, xlsx. |
| --parameters <json> | JSON array of Metabase parameter objects (the same shape Metabase POSTs from a dashboard). |
| --limit <n> | Cap rows kept in the JSON envelope. No effect on streamed exports. |
| --format-rows | Streamed exports only: apply the card's visualization-settings formatting to values (default false). |
| --pivot-results | Streamed exports only: emit the pivoted output for pivot questions (default false). |
mb card alerts <id>
List the alerts watching this card. Manage them with mb alert create|update|send|archive, which take the alert id printed here.
mb card alerts 94
mb card alerts 94 --include-inactive --json| Flag | Description |
| -------------------- | ----------------------------------- |
| --include-inactive | Include archived (inactive) alerts. |
mb card create
cat card.json | mb card create
mb card create --file card.json
mb card create --body '{"name":"x","display":"table","dataset_query":{...},"visualization_settings":{}}'| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb card update <id>
Patch a card. Body is a partial subset of the create shape (name, display, dataset_query, visualization_settings, description, archived, collection_id, dashboard_id, cache_ttl, parameters, parameter_mappings, etc.). Only the keys you send are touched. If dataset_query is MBQL 5 (lib/type: "mbql/query") it goes through the same pre-flight validation as card create and mb query; pass --skip-validate to bypass.
cat patch.json | mb card update 1
mb card update 1 --file patch.json
mb card update 1 --body '{"name":"renamed"}'
mb card update 1 --body '{"display":"bar"}'
mb card update 1 --body '{"archived":true}'
mb card update 1 --file patch.json --skip-validate| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
| --skip-validate | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
mb card archive <id>
Soft-delete a card by setting archived: true. The archived card stays available via card list --filter archived and card get <id> until permanently deleted server-side. To unarchive (or otherwise toggle the flag) use mb card update <id> --body '{"archived":false}'.
mb card archive 1
mb card archive 1 --jsonDashboards
Read and write dashboards on /api/dashboard. A dashboard groups cards (questions, models, metrics) into a single layout. Each card on a dashboard is a "dashcard" — a placement record with its own id, position (row/col), and size (size_x/size_y). Dashcards live nested inside the parent dashboard's dashcards array; the API has no per-dashcard endpoint, so single-dashcard edits round-trip through PUT /api/dashboard/:id.
mb dashboard list
mb dashboard list
mb dashboard list --json
mb dashboard list --filter archived --json| Flag | Description |
| ------------------- | ------------------------------------------- |
| --filter <preset> | One of all (default), mine, archived. |
mb dashboard get <id>
mb dashboard get 1
mb dashboard get 1 --json
mb dashboard get 1 --json --full--full returns the full hydrated dashboard including the dashcards and tabs arrays. The default compact view returns only id, name, description, archived, and collection_id.
mb dashboard cards <id>
List the dashcards on a dashboard.
mb dashboard cards 1
mb dashboard cards 1 --jsonmb dashboard subscriptions <id>
List the subscriptions delivering this dashboard. Manage them with mb subscription create|update|archive, which take the subscription id printed here.
mb dashboard subscriptions 10
mb dashboard subscriptions 10 --json| Flag | Description |
| ------------ | --------------------------------------------------- |
| --archived | Show archived subscriptions instead of active ones. |
Dashboard parameters (filters)
A dashboard's parameters are its filter widgets. They're typed (Parameter schema): an invalid type is rejected at the CLI boundary with a message that echoes the full allowed enum (string/=, string/contains, number/between, date/range, category, id, temporal-unit, …).
Read them off the dashboard with mb dashboard get <id> --fields parameters --json (or --full for the whole record). There is no separate read verb — they're part of the dashboard.
Editing replaces the whole parameters array, so it's a read-modify-write loop: read the current set, modify it, and send it all back via mb dashboard create/mb dashboard update --body '{"parameters":[…]}'; omitting a parameter deletes it. Each parameter's id is a descriptive string you choose (reuse the slug, e.g. order_status), unique within the dashboard — Metabase stores any non-blank string as-is, so there is no need to generate a random id (use mb uuid only if you genuinely want an opaque one). Bind a parameter to a card column through a dashcard's parameter_mappings, whose parameter_id must match a parameter id exactly.
mb dashboard parameter-values <dashboard-id> <parameter-id>
Fetch the selectable values for one dashboard parameter ({values, has_more_values}). Values come from the parameter's static list, its source card, or — for a parameter mapped to a field — the field's live distinct values (chain-filtered).
mb dashboard parameter-values 1 order_status --json
mb dashboard parameter-values 1 order_status --query Cam --json| Flag | Description |
| ------------------ | -------------------------------------------------------------------------------------- |
| --query <substr> | Case-insensitive substring search (first 1000 matches) instead of the full value list. |
mb dashboard create
The body accepts the same dashboard-level fields as the underlying POST /api/dashboard (name, description, parameters, cache_ttl, collection_id, collection_position). It also accepts optional dashcards and tabs: when either is present, the CLI chains a PUT /api/dashboard/:id after the create and returns the updated dashboard with its dashcards/tabs applied. Use a negative id on a dashcard to indicate one the server should newly create.
cat dashboard.json | mb dashboard create
mb dashboard create --file dashboard.json
mb dashboard create --body '{"name":"My Dashboard","collection_id":4}'
mb dashboard create --body '{"name":"D","dashcards":[{"id":-1,"card_id":42,"row":0,"col":0,"size_x":12,"size_y":6}]}'| Flag | Description |
| --------------- | --------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
mb dashboard update <id>
Patch a dashboard. To edit the dashcard set, send the entire dashcards array — IDs not in the array get deleted, and a negative id indicates a new dashcard the server should create.
cat patch.json | mb dashboard update 1
mb dashboard update 1 --file patch.json
mb dashboard update 1 --body '{"name":"renamed"}'
mb dashboard update 1 --body '{"dashcards":[{"id":-1,"card_id":42,"row":0,"col":0,"size_x":12,"size_y":6}]}'mb dashboard update-dashcard <dashboard-id> <dashcard-id>
Patch a single dashcard's layout or settings. The command does the round-trip for you: GET /api/dashboard/:id, merges the patch into the targeted dashcard while preserving every other dashcard verbatim, then PUTs the whole array back.
mb dashboard update-dashcard 1 5 --body '{"row":2,"col":0}'
mb dashboard update-dashcard 1 5 --body '{"size_x":12,"size_y":4}'
cat patch.json | mb dashboard update-dashcard 1 5| Patch field | Type |
| ------------------------ | ---------------------------------- |
| row, col | non-negative integer |
| size_x, size_y | positive integer |
| dashboard_tab_id | integer or null |
| parameter_mappings | array of parameter-mapping objects |
| inline_parameters | array of strings |
| visualization_settings | object |
The patch must contain at least one field; an empty object is rejected before the network round-trip.
mb dashboard archive <id>
Soft-delete a dashboard by setting archived: true. The archived dashboard stays available via dashboard list --filter archived and dashboard get <id> until permanently deleted server-side. To unarchive use mb dashboard update <id> --body '{"archived":false}'.
mb dashboard archive 1
mb dashboard archive 1 --jsonSnippets
CRUD on /api/native-query-snippet. A snippet is a named, reusable piece of native (SQL) query text — referenced from cards via {{snippet: Name}}. The list endpoint returns either active or archived rows (mutually exclusive — pass --archived to swap).
mb snippet list
mb snippet list
mb snippet list --json
mb snippet list --archived --json| Flag | Description |
| ------------ | ---------------------------------------------- |
| --archived | Show archived snippets instead of active ones. |
mb snippet get <id>
mb snippet get 1
mb snippet get 1 --json --fullmb snippet create
cat snippet.json | mb snippet create
mb snippet create --file snippet.json
mb snippet create --body '{"name":"active","content":"WHERE active = true"}'| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
Body fields: name (required), content (required), description (optional), collection_id (optional positive integer).
mb snippet update <id>
Patch a snippet. Body is a partial subset of the create shape plus archived. Only the keys you send are touched.
cat patch.json | mb snippet update 1
mb snippet update 1 --file patch.json
mb snippet update 1 --body '{"name":"renamed"}'
mb snippet update 1 --body '{"archived":true}'| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb snippet archive <id>
Soft-delete a snippet by setting archived: true. To unarchive use mb snippet update <id> --body '{"archived":false}'.
mb snippet archive 1
mb snippet archive 1 --jsonSegments
CRUD on /api/segment. A segment is a saved MBQL filter macro tied to a table — used in card filters to share a reusable predicate. Mutating endpoints require a revision_message for the audit log.
mb segment list
mb segment list
mb segment list --jsonmb segment get <id>
mb segment get 1
mb segment get 1 --json --fullmb segment create
cat segment.json | mb segment create
mb segment create --file segment.json
mb segment create --file segment.json --skip-validate| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
| --skip-validate | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
Body fields: name (required), table_id (required positive integer), definition (required MBQL filter object), description (optional). If definition is MBQL 5 (lib/type: "mbql/query") it goes through the same pre-flight validation as card create and mb query; pass --skip-validate to bypass.
mb segment update <id>
Patch a segment. The body MUST include revision_message. Other keys are partial: name, definition, archived, description, caveats, points_of_interest, show_in_getting_started. If definition is MBQL 5 (lib/type: "mbql/query") it goes through the same pre-flight validation as segment create; pass --skip-validate to bypass.
cat patch.json | mb segment update 1
mb segment update 1 --file patch.json
mb segment update 1 --body '{"name":"renamed","revision_message":"rename"}'
mb segment update 1 --file patch.json --skip-validate| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
| --skip-validate | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
mb segment archive <id>
Soft-delete a segment by setting archived: true. The default revision message is "Archived via mb CLI"; override with --revision-message.
mb segment archive 1
mb segment archive 1 --revision-message "deprecated"| Flag | Description |
| --------------------------- | ------------------------------------------- |
| --revision-message <text> | Audit-log message recorded with the change. |
Measures
CRUD on /api/measure. A measure is a saved MBQL aggregation (a single :aggregation clause) tied to a table — referenced from cards and metrics to share a reusable computation. Mutating endpoints require a revision_message for the audit log.
mb measure list
mb measure list
mb measure list --jsonmb measure get <id>
mb measure get 1
mb measure get 1 --json --fullmb measure create
cat measure.json | mb measure create
mb measure create --file measure.json
mb measure create --file measure.json --skip-validate| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
| --skip-validate | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
Body fields: name (required), table_id (required positive integer), definition (required MBQL aggregation object), description (optional). If definition is MBQL 5 (lib/type: "mbql/query") it goes through the same pre-flight validation as card create and mb query; pass --skip-validate to bypass.
mb measure update <id>
Patch a measure. The body MUST include revision_message. Other keys are partial: name, definition, archived, description. If definition is MBQL 5 (lib/type: "mbql/query") it goes through the same pre-flight validation as measure create; pass --skip-validate to bypass.
cat patch.json | mb measure update 1
mb measure update 1 --file patch.json
mb measure update 1 --body '{"name":"renamed","revision_message":"rename"}'
mb measure update 1 --file patch.json --skip-validate| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
| --skip-validate | Skip the local MBQL 5 pre-flight validation; let the server be the authority. Use only when the bundled schema disagrees with what the server accepts. |
mb measure archive <id>
Soft-delete a measure by setting archived: true. The default revision message is "Archived via mb CLI"; override with --revision-message.
mb measure archive 1
mb measure archive 1 --revision-message "deprecated"| Flag | Description |
| --------------------------- | ------------------------------------------- |
| --revision-message <text> | Audit-log message recorded with the change. |
Timelines
CRUD on /api/timeline. A timeline is a named collection of dated events rendered as annotations on time-series charts. Timelines live in collections (collection_id: null = root) and carry an icon (star, cake, mail, warning, bell, cloud).
mb timeline list
mb timeline list
mb timeline list --json
mb timeline list --archived --json| Flag | Description |
| ------------ | ----------------------------------------------- |
| --archived | Show archived timelines instead of active ones. |
mb timeline get <id>
mb timeline get 1
mb timeline get 1 --json --fullmb timeline events <id>
List the events on a timeline. Archived events are excluded unless --archived is passed (which returns both).
mb timeline events 1
mb timeline events 1 --archived --json| Flag | Description |
| ------------ | ------------------------ |
| --archived | Include archived events. |
mb timeline create
mb timeline create --body '{"name":"Releases"}'
cat timeline.json | mb timeline create
mb timeline create --file timeline.json| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
Body fields: name (required), description (optional), icon (optional, default star), collection_id (optional positive integer, omit for the root collection), default (optional boolean marking the collection's default timeline).
mb timeline update <id>
Patch a timeline. Body is a partial subset of the create shape plus archived. Only the keys you send are touched. Changing archived cascades to every event on the timeline.
mb timeline update 1 --body '{"name":"Product releases"}'
cat patch.json | mb timeline update 1
mb timeline update 1 --file patch.json| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb timeline archive <id>
Soft-delete a timeline (and, by server-side cascade, all its events) by setting archived: true. To unarchive use mb timeline update <id> --body '{"archived":false}'.
mb timeline archive 1
mb timeline archive 1 --jsonmb timeline delete <id>
Permanently delete a timeline and all its events. Irreversible — prefer mb timeline archive unless you mean it. Prompts for confirmation on a TTY; requires --yes otherwise.
mb timeline delete 1 --yes
mb timeline delete 1| Flag | Description |
| ------- | ------------------ |
| --yes | Skip confirmation. |
Timeline events
CRUD on /api/timeline-event. An event is a dated annotation on a timeline. There is no server-side list endpoint — list events with mb timeline events <id>.
mb timeline-event get <id>
mb timeline-event get 1
mb timeline-event get 1 --json --fullmb timeline-event create
mb timeline-event create --body '{"name":"v2 launch","timestamp":"2026-07-01T00:00:00Z","timezone":"UTC","time_matters":false,"timeline_id":1}'
cat event.json | mb timeline-event create
mb timeline-event create --file event.json| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
Body fields: name (required), timestamp (required, ISO 8601), timezone (required, IANA name like UTC or America/New_York), time_matters (required boolean — true when the time of day is significant, false when only the date is), timeline_id (required positive integer), description (optional), icon (optional, default: the timeline's icon).
mb timeline-event update <id>
Patch an event. Body is a partial subset of the create shape plus archived. Only the keys you send are touched; timeline_id moves the event to another timeline.
mb timeline-event update 1 --body '{"name":"v2.1 launch"}'
cat patch.json | mb timeline-event update 1
mb timeline-event update 1 --file patch.json| Flag | Description |
| --------------- | ----------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. |
mb timeline-event archive <id>
Soft-delete an event by setting archived: true. To unarchive use mb timeline-event update <id> --body '{"archived":false}'.
mb timeline-event archive 1
mb timeline-event archive 1 --jsonmb timeline-event delete <id>
Permanently delete an event. Prompts for confirmation on a TTY; requires --yes otherwise.
mb timeline-event delete 1 --yes
mb timeline-event delete 1| Flag | Description |
| ------- | ------------------ |
| --yes | Skip confirmation. |
Dashboard subscriptions
Read and write dashboard subscriptions on /api/pulse. A subscription delivers a rendered dashboard on a schedule — by email, to a Slack channel, or to an HTTP webhook. It pins the dashboard's cards by both id (the card) and dashboard_card_id (its placement); mb dashboard cards <dashboard-id> prints both.
A subscription's dashboard_id and collection_id are fixed at creation. There is no delete — archiving is the terminal state, and it also disables every channel.
mb subscription list
mb subscription list
mb subscription list --dashboard-id 10 --json
mb subscription list --archived --json| Flag | Description |
| --------------------- | --------------------------------------------------- |
| --dashboard-id <id> | Only subscriptions on this dashboard. |
| --archived | Show archived subscriptions instead of active ones. |
Listing from the dashboard side is mb dashboard subscriptions <dashboard-id>.
mb subscription get <id>
mb subscription get 1
mb subscription get 1 --full --jsonThe compact view returns id, name, dashboard_id, collection_id, archived, skip_if_empty, plus the pinned cards and the channels with their schedules and recipients. --full adds the hydrated creator, entity ids, and per-card download permissions.
mb subscription create
The body needs name, dashboard_id, cards, and channels.
Each channel names a channel_type (email, slack, http) and a schedule_type (hourly, daily, weekly, monthly) plus the fields that schedule needs: daily needs schedule_hour (0–23); weekly also needs schedule_day (mon…sun); monthly also needs schedule_frame (first, mid, last). Email recipients are {"email":"[email protected]"} or {"id":<user-id>}; Slack targets a channel with "details":{"channel":"#general"}. A channel is enabled unless you say otherwise.
mb subscription create --body '{"name":"Weekly orders","dashboard_id":10,"cards":[{"id":94,"dashboard_card_id":87,"include_csv":false,"include_xls":false}],"channels":[{"channel_type":"email","schedule_type":"daily","schedule_hour":8,"recipients":[{"email":"[email protected]"}]}]}'
cat subscription.json | mb subscription create
mb subscription create --file subscription.json| Flag | Description |
| --------------- | --------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
mb subscription update <id>
Patches name, cards, channels, skip_if_empty, parameters, archived. cards and channels each replace the whole list, so send every one you want to keep — mb subscription get <id> --full prints the current set.
The update reads the subscription first and carries archived and skip_if_empty forward when your patch omits them. That is load-bearing: PUT /api/pulse/:id defaults every omitted key, and both of those default to false, so a raw name-only PUT would un-archive the subscription and clear skip_if_empty.
mb subscription update 1 --body '{"name":"Daily orders"}'
mb subscription update 1 --body '{"channels":[{"channel_type":"email","schedule_type":"weekly","schedule_hour":8,"schedule_day":"mon","recipients":[{"email":"[email protected]"}]}]}'
mb subscription update 1 --file patch.json| Flag | Description |
| --------------- | --------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
mb subscription archive <id>
Archive a subscription, stopping all deliveries. Also disables every channel on it, so restoring means un-archiving and then re-enabling the channels.
mb subscription archive 1
mb subscription archive 1 --jsonQuestion alerts
Read and write question alerts on /api/notification. An alert watches one card and delivers it when a send condition fires on a schedule: has_result (the card returned any row), or goal_above / goal_below (both need a goal set on the card's visualization).
Schedules are Quartz cron strings — 0 0 8 * * ? * is daily at 08:00. /api/notification also carries Metabase's internal system-event notifications; mb alert scopes every request to card alerts, so they never appear.
Archiving deactivates an alert rather than deleting it: mb alert list --include-inactive still finds it, and mb alert update <id> --body '{"active":true}' brings it back.
mb alert list
mb alert list
mb alert list --card-id 94 --json
mb alert list --include-inactive --json| Flag | Description |
| --------------------- | ----------------------------------- |
| --card-id <id> | Only alerts watching this card. |
| --creator-id <id> | Only alerts created by this user. |
| --recipient-id <id> | Only alerts delivered to this user. |
| --include-inactive | Include archived (inactive) alerts. |
Listing from the question side is mb card alerts <card-id>.
mb alert get <id>
mb alert get 9
mb alert get 9 --full --jsonThe compact view returns id, active, creator_id, the payload (card_id, send_condition, send_once), the cron subscriptions, and the handlers with their recipients. --full adds the hydrated card the alert watches.
mb alert create
The body needs payload, subscriptions, and handlers. Each handler names a channel_type (channel/email, channel/slack, channel/http) and its recipients; a recipient is {"type":"notification-recipient/user","user_id":3} or {"type":"notification-recipient/raw-value","details":{"value":"[email protected]"}}.
mb alert create --body '{"payload":{"card_id":94,"send_condition":"has_result"},"subscriptions":[{"cron_schedule":"0 0 8 * * ? *"}],"handlers":[{"channel_type":"channel/email","recipients":[{"type":"notification-recipient/raw-value","details":{"value":"[email protected]"}}]}]}'
cat alert.json | mb alert create
mb alert create --file alert.json| Flag | Description |
| --------------- | --------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
mb alert update <id>
Patches the top-level fields you send: payload, subscriptions, handlers, active. Fields inside payload merge over the current ones, so {"payload":{"send_condition":"goal_above"}} keeps the card. subscriptions and handlers each replace the whole list — mb alert get <id> prints the current set. An alert cannot be moved to a different card.
The update reads the alert first and merges your patch over it. That is load-bearing: PUT /api/notification/:id is a spec-diff, and a body whose id doesn't match the stored one makes Metabase delete the alert and insert a replacement under a fresh id.
mb alert update 9 --body '{"payload":{"send_condition":"goal_above"}}'
mb alert update 9 --body '{"subscriptions":[{"cron_schedule":"0 0 9 * * ? *"}]}'
mb alert update 9 --body '{"active":true}'| Flag | Description |
| --------------- | --------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
mb alert send <id>
Send an alert now, off-schedule. Delivers to every handler, ignoring the send condition. The channel must be configured on the server (email needs SMTP set up).
mb alert send 9
mb alert send 9 --jsonmb alert archive <id>
Archive an alert, stopping all deliveries and dropping its scheduled trigger.
mb alert archive 9
mb alert archive 9 --jsonCollections
Read collections on /api/collection. Collections are the folders that contain cards, dashboards, and other collections. The list endpoint surfaces a virtual root collection (id "root") alongside regular numeric ids; the get endpoint accepts only the numeric id.
mb collection list
mb collection list
mb collection list --json
mb collection list --filter archived --json| Flag | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------- |
| --filter <preset> | One of all (default), archived (returns the trash collection only), personal (only personal collections). |
mb collection get <id>
<id> accepts any of: a positive integer collection id, the literal root (the virtual "Our analytics" root), the literal trash (the trash collection), or a 21-character entity id (NanoID). Anything else is rejected with a ConfigError before any HTTP call.
mb collection get 4
mb collection get root --json
mb collection get trash --json
mb collection get voo1If9y8Sld0lXej6xl0 --json
mb collection get 4 --json --full--full returns the full hydrated collection including slug, entity_id, can_write, namespace, and personal_owner_id. The default compact view returns id, name, description, archived, location, parent_id, type, authority_level, and is_personal. The root collection has a stripped-down shape — archived, description, location, type, etc. are absent rather than null.
mb collection items <id>
List the cards, dashboards, sub-collections, and other content stored inside a collection. The CLI drains all pages of /api/collection/:id/items; pass --limit to cap the result. <id> accepts the same forms as collection get — including root for top-level content (items there have collection_id: null).
mb collection items 4
mb collection items root --json
mb collection items 4 --models card,dashboard --json
mb collection items 4 --pinned-state is_pinned --json| Flag | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| --models <csv> | Restrict to one or more models (card, dataset, metric, dashboard, snippet, collection, document, …). |
| --archived | Return archived items instead of unarchived. |
| --pinned-state <state> | One of all, is_pinned, is_not_pinned. |
| --limit <n> | Cap total items returned. Default: drain all pages. |
mb collection tree
Fetch the full collection hierarchy as a nested tree. Output is always JSON — the recursive structure does not render meaningfully as a key/value table.
mb collection tree
mb collection tree --jsonmb collection create
Create a collection from a JSON spec. The body accepts the same fields as POST /api/collection: name (required), description, parent_id (omit or null for the root), namespace, and authority_level.
cat collection.json | mb collection create
mb collection create --file collection.json
mb collection create --body '{"name":"My Collection","parent_id":4}'
mb collection create --body '{"name":"ETL"}' --namespace transforms| Flag | Description |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --body <json> | Inline JSON body. |
| --file <path> | Path to JSON body file. Use - to read from stdin. |
| --namespace <ns> | Collection namespace (transforms, snippets, analytics, shared-tenant-collection, tenant-specific). Omit for a normal collection; required for a collection a transform's collection_id can point at. |
mb collection archive <id>
Soft-delete a collection by setting archived: true. The archived collection stays available via collection list --filter archived until permanently deleted server-side. Restore it from the trash in the Metabase UI.
mb collection archive 4
mb collection archive 4 --jsonLibrary
Curate the Metabase Library — a governed subtree (library-data "Data" for published tables, library-metrics "Metrics" for official metrics, under a library root). Tables published to Data appear first when people pick a data source and rank up in search, steering everyone toward trusted, analysis-ready tables. Requires the library premium feature (Pro/Enterprise) and admin or data-analyst permission (Curate alone won't publish tables). Publish status surfaces on the table via is_published (table get/table list).
mb library get
Show the Library and its Data/Metrics collection ids (GET /api/ee/library/). Errors if the Library hasn't been created yet.
mb library get
mb library get --jsonmb library create
Create the Library subtree (POST /api/ee/library/). Idempotent — returns the existing Library when it's already there.
mb library create
mb library create --json