Skip to content
Development
Skill

/documents

The Metabase-flavored Markdown grammar for document_write — the CommonMark subset, {% card %} embeds, {% entity %} links, ::: layout containers (flex / supporting / resize) and nesting rules, surgical `edits` and their limits, card clones, comments. Triggers — "create a document

From plugin
metabase
50k30 skills11 agents23 commands

Claude Code

$ npx -y skills add metabase/metabase --skill documents --agent claude-code

SKILL.md

documents.SKILL.md
name: documents
description: The Metabase-flavored Markdown grammar for document_write — the CommonMark subset, {% card %} embeds, {% entity %} links, ::: layout containers (flex / supporting / resize) and nesting rules, surgical `edits` and their limits, card clones, comments. Triggers — "create a document / report", "put prose beside a chart", "embed a question in a document", "edit a document", "document comments".

Documents

A document is a rich-text page mixing prose with embedded saved questions. `document_write` takes **Metabase-flavored Markdown**: CommonMark plus tokens. Charts must exist first (`question_write`), embedded by id.

document_write {"method": "create", "name": "Weekly report",
                "content_markdown": "# Weekly report\n\nOrders trended up this week.\n\n{% card id=118 %}\n"}

**CommonMark subset:** headings, paragraphs, bold/italic/inline code, links (bare URLs autolink), lists, blockquotes, fenced code, horizontal rules, images. **No tables, strikethrough, or task lists** — the editor has no nodes for them; show tabular data by embedding a table-display question.

Tokens

  • **Card embed** (block, own line): `{% card id=118 %}` or `{% card id=118 name="Revenue by region" %}` (`name` overrides the title). The id must be a saved question you can read; an unresolvable id fails the write. Height is assigned for you.
  • **Entity link** (inline chip): `{% entity id="42" model="dashboard" %}` — id quoted; `model` ∈ `card`, `dataset`, `metric`, `dashboard`, `collection`, `table`, `database`, `document` (`user` parses but renders without a working link).

Layout containers (`:::` fences)

`::: <name> {attrs}` on its own line opens a container; a bare `:::` closes the **innermost** open one (never `::: end`) — every opener needs its own closer, in order.

  • `::: flex {columns=[60,40]}` — a row of 1–3 cells (percent widths); a cell is a card embed or a `::: supporting` block. Never nest `flex` in `flex`.
  • `::: supporting` — a prose cell (paragraphs, headings, lists) inside a flex row.
  • `::: resize {height=442 minHeight=280}` — wraps exactly one card embed or flex row to pin its pixel height.

Prose beside a chart:

::: flex {columns=[60,40]}
::: supporting
### What happened
Revenue climbed through the quarter, led by the Gadget category.
:::
{% card id=118 %}
:::

(First `:::` closes `supporting`, last closes `flex`.) To pin the row's height, wrap it in `::: resize {height=400}` … `:::`.

Card clones

A card the document doesn't already own is **cloned into it** on write and its id rewritten in the stored body — the markdown stored is not the markdown you sent, so always take the returned `content_markdown` as current, and edit against that, never your own earlier text. The clone is what renders: to change an embedded chart (display, settings, query), `question_write` the **clone id** shown in `content_markdown`; the master card does nothing for the document. E.g. you embedded 148, the body now reads `{% card id=155 %}` — `question_write {"method": "update", "id": 155, "display": "row"}` changes the document, updating 148 does not.

Updating

Pass `id` and exactly **one** of:

  • `edits: [{old_str, new_str, replace_all?}]` — the default. Each `old_str` must match the **current server-side** markdown exactly once: 0 matches = the document changed since you read it (copy from the returned `content_markdown`); >1 = extend the snippet or set `replace_all: true`. Blocks keep their ids, so comment anchors survive.
  • `content_markdown` — full rewrite; every block is re-created, so **every comment thread on the body is orphaned** (listed in the response's `orphaned_comment_threads`). Only for restructuring; tell the user first.
  • `edits: []` — metadata only (`name`, `collection_id`, `collection_position`, `archived`), body untouched.

Writes are last-write-wins; a stale `old_str` failing to match is the only staleness signal.

**What `edits` can do:** `new_str` is parsed as Markdown, the same grammar as `content_markdown`. Inline syntax such as `**bold**` or `*em*` formats, a blank line (`\n\n`) splits a paragraph, and a `new_str` that replaces a whole block with `- item` lines, a fence, or a `:::` container turns it into that block (listed in `changed_blocks`). Inside a code span or code block the replacement stays code. So edits cover rewording, formatting, and restructuring a block; `content_markdown` is only for rewriting the whole body.

Add a paragraph — extend the end of the block before it:

"edits": [{"old_str": "led by the Gadget category.",
           "new_str": "led by the Gadget category.\n\nGizmos were flat for the third quarter running."}]

Edit one bullet - match its text only, never the `- ` marker, so the edit stays inside that item:

"edits": [{"old_str": "Churn fell to 4%", "new_str": "Churn fell to 3.8%"}]

Comments

`get_content {"items": [{"type": "document", "id": 12}], "include": ["comments"]}` returns threads grouped by anchored block, each with `anchor: {start, end, text}` — the exact character slice of the returned `content_markdown` — and `thread` messages (`id`, `creator`, `text`, `is_resolved`, `created_at`). A thread whose block was rewritten or deleted appears under `orphaned_comments`: the discussion still exists but points at no text, and only the UI can re-anchor it. MCP can read comments but not reply, resolve, or re-anchor — tell the user when a thread needs that.

Don't

  • Don't `question_write` the master card to change an embedded chart — a no-op for the document; edit the clone id.
  • Don't put Markdown syntax in `new_str` that you mean literally - it is parsed; escape it (`\*`).
  • Don't include the `- ` marker in a bullet edit - match the item's text only.
  • Don't full-rewrite for a small change — every comment thread is orphaned, irreversibly.
  • Don't write tables, strikethrough, or task lists — no document representation.

To confirm

`get_content {"items": [{

Read more
@metabase
Ships withmetabase

Metabase is the easy, open-source way for everyone in your company to ask questions and learn from data.

50k30 skills11 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
50.3k
Star history of metabase/metabase on GitHub, a chart by star-history.com

Other skills on metabase.

add-malli-schemas

add-malli-schemas

Efficiently add Malli schemas to API endpoints in the Metabase codebase with proper patterns, validation timing, and error…

@metabase@metabaseInstall Skill
add-tracing

add-tracing

Add OpenTelemetry tracing spans to Clojure code following Metabase tracing conventions. Use when instrumenting backend code with…

@metabase@metabaseInstall Skill
api-changelog

api-changelog

Draft or audit the REST API changelog (docs/developers-guide/api-changelog.md) by semantically diffing the checked-in OpenAPI…

@metabase@metabaseInstall Skill
app-db-migrations

app-db-migrations

Add, test, run and roll back application-database (Liquibase) migrations. Use when adding a changeset, changing a table or column…

@metabase@metabaseInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about documents.

Which coding agents can use documents?
documents installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.