Skip to content
Development
Skill

/transforms

Authoring transforms with transform_write — the query source (definition vs query_handle), the target table and what patching it renames, why the target database is never yours to choose, the two shapes the tool refuses (python, incremental), tags and folders. Read before your

From plugin
metabase
49k31 skills11 agents23 commands
Install
$ npx -y skills add metabase/metabase --skill transforms --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/transforms

Context preview

The summary Claude sees to decide when to auto-load this skill.

Authoring transforms with transform_write — the query source (definition vs query_handle), the target table and what patching it renames, why the target database is never yours to choose, the two shapes the tool refuses (python, incremental), tags and folders. Read before your

SKILL.md

transforms.SKILL.md
name: transforms
description: Authoring transforms with transform_write — the query source (definition vs query_handle), the target table and what patching it renames, why the target database is never yours to choose, the two shapes the tool refuses (python, incremental), tags and folders. Read before your first transform_write. Triggers — "make a transform", "materialize this query into a table", "build a table other questions can query", "rename a transform's output table", "why won't it let me edit this transform".

Transforms

A transform is a saved query Metabase **runs to materialize its results into a real warehouse table**, which questions, other transforms, and anything reaching the database then query like any table. (A model is resolved at read time; a transform is a table that exists between runs.)

transform_write {"method": "create", "name": "Daily revenue",
                 "query_handle": "qh_…",
                 "target": {"name": "daily_revenue", "schema": "analytics"}}

Query source — exactly one of `query_handle` | `definition`

Preferred: author and run with `execute_query` (or `execute_sql` for native — fine here), then pass the returned `query_handle`; it saves exactly what ran. The alternative, inline `definition`, is the transform's **source map**, not a bare query — the shape `get_content`'s `"definition"` include returns, so read-modify-write round-trips; the inner query is the `execute_query` dialect (`learn("query-dialect")`), numeric ids:

"definition": {"type": "query", "query": {"lib/type": "mbql/query", "stages": [...]}}

A bare query in `definition` is the common miss (older transforms stored it that way) and a teaching error.

Target table

`target` is `{name, schema?}` — the table the transform writes, **recreated in full on every run**.

  • `schema` is required on databases that have schemas.
  • On update `target` is **patched, not replaced**: passing only `name` renames the output table and keeps the schema — the supported rename.
  • A new transform whose target table **already exists is refused** — it creates its table, never adopts one. Pick another name or schema.
  • Never set `target.database`: the target follows the database the query reads; naming another is an error.
  • `target.type` is always `"table"`; incremental types are configured in Metabase.

Refused, not degraded

Two shapes are rejected outright, because rewriting them would silently change how the transform loads: **Python transforms** (an update that would rewrite one is refused) and **incremental loading** — checkpoint (`definition.source-incremental-strategy`) and append/merge (`target.target-incremental-strategy`). Configure those in Metabase; the *query* of an incremental transform is still editable here as long as those keys stay out.

`transform_write` only authors: no run (saving doesn't execute), no archive, no delete (transforms have no trash — remove in Metabase). `get_content` on a transform reports source type, target, and latest run.

Folders, tags, permissions

  • `collection_id` files it in a transform folder; omit for the top of the transforms tree.
  • Transform folders are their own hierarchy — make one with `collection_write(method: "create", namespace: "transforms")`, and browse them with `browse_collection(namespace: "transforms")`.
  • `tag_ids` — **jobs select transforms by tag**, so tags are how a transform gets scheduled. Replaced wholesale: pass the full set, or `[]` to clear.
  • Needs transforms permission on the source database plus the transforms feature, enforced exactly as the REST API and UI enforce them.

Don't

  • Don't expect saving to run the transform — nothing executes until a job does.
  • Don't pass a partial `tag_ids` — the list is replaced wholesale and the rest are dropped.
  • Don't pass a partial `target` expecting a replacement — it is patched, so the old schema stays.
  • Don't rely on rows surviving between runs — the table is recreated in full each time.
Read more
Ships withmetabase

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

Get the whole plugin

Other skills on metabase.