Version: 1.0.0
Metabase represents user-created content as a tree of YAML files. Each file represents one entity (a collection, card, dashboard, etc.). The format is designed to be portable across Metabase instances: numeric database IDs are replaced with human-readable names and entity IDs.
This specification covers user-created content entities. Database metadata entities (Database, Table, Field) are synced from connected databases and are outside the scope of this specification; they appear here only as foreign key references within user content.
- Entity Keys
- Folder Structure
- MBQL Query
- Native Query
- Visualization Settings
- Click Behavior
- Parameter
- Collection
- Card
- Dashboard
- Document
- Segment
- Measure
- Snippet
- Transform
Metabase uses two ways of identifying entities: entity_id (NanoID) and natural entity keys.
entity_id is a 21-character NanoID string (alphabet: A-Za-z0-9_-). It is the primary portable identifier used in cross-references. Once assigned, it does not change — the entity can be renamed or moved, but the entity_id remains stable. Entity IDs must be unique per entity type within an instance — no two entities of the same type may share the same entity_id.
Generate a NanoID with the nanoid CLI:
npx nanoid
# → LZfXLFzPPR4NNrgjlWDxnOr in Bash:
head -c 21 /dev/urandom | base64 | tr -dc 'A-Za-z0-9_-' | head -c 21
# → LZfXLFzPPR4NNrgjlWDxnUser content entities reference database objects using natural keys:
| Reference | Format | Example |
|---|---|---|
| Database FK | database name | "Sample Database" |
| Table FK | [database, schema, table] |
["Sample Database", "PUBLIC", "ORDERS"] |
| Field FK | [database, schema, table, field, ...] |
["Sample Database", "PUBLIC", "ORDERS", "TOTAL"] |
| Collection FK | entity_id of collection | "M-Q4pcV0qkiyJ0kiSWECl" |
| Card FK | entity_id of card | "f1C68pznmrpN1F5xFDj6d" |
| Dashboard FK | entity_id of dashboard | "Q_jD-f-9clKLFZ2TfUG2h" |
| User FK | email address | "internal@metabase.com" |
For schemaless databases, the schema component is null (e.g., ["My Database", null, "my_table"]).
For JSON-unfolded fields, the Field FK extends beyond 4 elements with the nested path: ["Sample Database", "PUBLIC", "EVENTS", "DATA", "user", "name"] represents the JSON path DATA.user.name.
Every entity includes a serdes/meta array that encodes the entity's identity path. Each entry contains an id and model field. Entities identified by NanoID also include a label (slugified name).
serdes/meta:
- id: NDzkGoTCdRcaRyt7GOepg
label: my_entity_name
model: CardImportant: Metabase ignores directory structure when importing — all collection relationships are determined solely by each entity's collection_id field. The layout below is how Metabase represents user content when exporting; it mirrors the collection hierarchy on disk for readability, but only collection_id is authoritative.
Metabase only checks for importable YAML files in these top-level directories: collections/, databases/ (only segments/ and measures/ subdirectories), python_libraries/ (also accepted as python-libraries/), and transforms/. Files outside these directories are ignored during import.
Collections are organized by namespace. The main namespace holds regular content (cards, dashboards, etc.), snippets holds SQL snippet collections, and transforms holds transform entities. Subcollections must set parent_id to the entity_id of their parent collection. All entity types within a collection are stored flat in the same folder — there are no cards/, dashboards/ subdirectories.
export-root/
├── settings.yaml
├── collections/
│ ├── main/ # Main namespace (regular content)
│ │ ├── {slug}.yaml # Entities in root collection
│ │ ├── {collection_slug}.yaml # Collection definition (sibling of its folder)
│ │ └── {collection_slug}/ # Collection contents
│ │ ├── {card_slug}.yaml # Cards, dashboards, documents, etc.
│ │ ├── {dashboard_slug}.yaml # — all flat in the same folder
│ │ ├── {child_slug}.yaml # Child collection definition
│ │ └── {child_slug}/ # Child collection contents
│ │ └── ...
│ ├── snippets/ # Snippets namespace
│ │ ├── {snippet_slug}.yaml # Snippets in root snippet collection
│ │ ├── {collection_slug}.yaml # Snippet collection definition
│ │ └── {collection_slug}/ # Snippet collection contents
│ │ └── {snippet_slug}.yaml
│ └── transforms/ # Transforms namespace
│ └── {transform_slug}.yaml
├── databases/
│ └── {database_slug}/
│ ├── {database_slug}.yaml
│ ├── schemas/
│ │ └── {schema_slug}/
│ │ └── tables/
│ │ └── {table_slug}/
│ │ ├── {table_slug}.yaml
│ │ ├── segments/
│ │ │ └── {slug}.yaml
│ │ └── measures/
│ │ └── {slug}.yaml
│ └── tables/ # Schemaless databases
│ └── {table_slug}/
│ ├── {table_slug}.yaml
│ ├── segments/
│ │ └── {slug}.yaml
│ └── measures/
│ └── {slug}.yaml
├── python_libraries/
│ └── {slug}.yaml
└── transforms/ # Transform jobs and tags
├── transform_jobs/
│ └── {slug}.yaml
└── transform_tags/
└── {slug}.yaml
- Entity files are named
{slug}.yamlwhere slug is the slugified entity name (lowercase, spaces to underscores). - Collection hierarchy is reflected in directory nesting within a namespace.
- A collection's definition file (
{slug}.yaml) is placed outside its folder, as a sibling: e.g.,main/my_collection.yamldefines the collection whose contents live inmain/my_collection/. - All entity types within a collection (cards, dashboards, documents, etc.) are stored flat in the same folder — no type-specific subdirectories.
- Collections are partitioned by namespace:
main/for regular content,snippets/for SQL snippets,transforms/for transforms.
Critical: Every entity's logical position in the collection hierarchy is determined exclusively by its
collection_idfield, not the folder structure on disk. The folder layout is for human organization only; Metabase imports entities based solely on theircollection_id. An entity withoutcollection_id(or withcollection_id: null) will appear in the root collection. If you want a card, dashboard, document, snippet, or transform to be inside a specific collection, you must set itscollection_idto theentity_idof that collection. Cards nested under a dashboard or document must also setcollection_idto match thecollection_idof their parent dashboard or document. Similarly, subcollections must setparent_idto theentity_idof their parent collection — withoutparent_id, a collection is treated as a root-level collection regardless of its position in the directory tree.
Dashboards and documents act as containers for cards: a card with dashboard_id set is owned by that dashboard, and a card with document_id set is owned by that document. Container-owned cards behave as if the dashboard or document were a subcollection:
collection_id(required for all collection items) — Places the entity in a collection.nullor omitted means root collection. Must always be set to place an entity in a specific collection. Even whendashboard_idordocument_idis set,collection_idmust be set and must match thecollection_idof the parent dashboard or document.dashboard_id— Nests the card under a dashboard. The card should only be used within that dashboard. To reuse a card outside its dashboard, unsetdashboard_idand place it directly in a collection.document_id— Nests the card under a document. Same semantics asdashboard_id: the card should only be used within that document.
When a dashboard or document moves collections, all cards nested under it move too. A card should never have both dashboard_id and document_id set.
On disk, cards nested under a dashboard or document are placed in a subfolder matching the parent's slug (e.g., my_dashboard/card.yaml as a sibling of my_dashboard.yaml, within the same collection folder).
- Segments and measures live under their table's directory in the
databases/tree. - Database, schema, and table folder names are slugified (e.g.,
test-data (h2)becomestest_data__h2_). - Slashes in names are escaped as
__SLASH__, backslashes as__BACKSLASH__.
MBQL (Metabase Query Language) queries are constructed via the graphical query editor. Prefer MBQL queries when possible since they are portable across database engines. Use native queries when something is not supported in MBQL.
"lib/type": mbql/query
database: Sample Database # Database FK
stages:
- "lib/type": mbql.stage/mbql
source-table: # Table FK
- Sample Database
- PUBLIC
- PRODUCTSThis is equivalent to SELECT * FROM PUBLIC.PRODUCTS.
source-table specifies a physical table as the data source using a Table FK (array).
source-table:
- Sample Database
- PUBLIC
- PRODUCTSsource-card specifies a saved card (question or model) as the data source using its Card entity_id (string). Fields from the card's results are referenced by column name (string) rather than a Field FK:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-card: f1C68pznmrpN1F5xFDj6d # entity_id of a saved card
filters:
- - ">"
- {}
- - field
- base-type: type/Float
- PRICE
- 50A query can have multiple stages, where each stage operates on the results of the previous one. Stages are a flat array — there is no nesting. Fields from a previous stage's results are referenced by column name (string) rather than a Field FK. Stages can be stacked to arbitrary depth.
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- ORDERS
aggregation:
- - count
- "lib/uuid": 11111111-1111-1111-1111-111111111111
breakout:
- - field
- temporal-unit: month
- - Sample Database
- PUBLIC
- ORDERS
- CREATED_AT
- "lib/type": mbql.stage/mbql
filters:
- - ">"
- {}
- - field
- base-type: type/Integer
- count
- 10This is equivalent to SELECT * FROM (SELECT DATE_TRUNC('month', CREATED_AT), COUNT(*) AS count FROM ORDERS GROUP BY 1) WHERE count > 10.
Fields are referenced using a field clause with options as the second argument and a Field FK as the third:
- field
- {} # field options (always a map, never null)
- - Sample Database # database name
- PUBLIC # schema (null for schemaless)
- ORDERS # table name
- TOTAL # field nameField options (second argument) is always a map (use {} when no options are needed):
| Option | Type | Description |
|---|---|---|
base-type |
string | Base type hint (e.g., type/Float, type/Integer) |
temporal-unit |
string | Temporal bucketing unit (see Temporal Bucketing) |
join-alias |
string | Alias of the join this field belongs to |
binning |
map | Binning strategy (see Binning) |
source-field |
array | Implicit join: FK field reference in the source table (see below) |
source-field-name |
string | Implicit join: FK field by name, for multi-stage queries |
source-field-join-alias |
string | Implicit join: join-alias when the FK table is explicitly joined |
Fields from a related table can be referenced without an explicit joins clause by specifying how to traverse the foreign key relationship. This is called an implicit join.
Use source-field to specify the FK field in the source table that links to the target table:
# Get PRODUCTS.TITLE via ORDERS.PRODUCT_ID (implicit join)
- field
- source-field:
- Sample Database
- PUBLIC
- ORDERS
- PRODUCT_ID
- - Sample Database
- PUBLIC
- PRODUCTS
- TITLEThe source-field value is a raw Field FK ([database, schema, table, field]), not a field clause.
For multi-stage queries (when source-card is set), additionally set source-field-name to reference the FK column by its string name in the previous stage's results. This is needed when the source query returns multiple fields that are both the same FK:
- field
- source-field:
- Sample Database
- PUBLIC
- ORDERS
- PRODUCT_ID
source-field-name: PRODUCT_ID
- - Sample Database
- PUBLIC
- PRODUCTS
- TITLEWhen the source (FK) table is itself joined via an explicit joins clause, use source-field-join-alias to disambiguate which join the FK field comes from. The value must match the alias of the corresponding join:
- field
- source-field:
- Sample Database
- PUBLIC
- ORDERS
- PRODUCT_ID
source-field-join-alias: Joined Orders
- - Sample Database
- PUBLIC
- PRODUCTS
- TITLEExpression references use the expression keyword:
- expression
- {}
- ProfitAggregation references use the aggregation keyword with a UUID that matches the lib/uuid of the referenced aggregation clause:
- aggregation
- {}
- "11111111-1111-1111-1111-111111111111"Restricts which columns are included in the results. Each item is a field or expression reference:
fields:
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- CREATED_AT
- - expression
- {}
- ProfitWhen fields is omitted, all columns are included. If fields is present and the stage has expressions, every expression must be included in fields as an expression reference:
fields:
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
- - expression
- {}
- Profit
expressions:
- - "-"
- "lib/expression-name": Profit
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, TAX]Joins combine data from multiple tables. Each join has its own stages array (defining the joined data source) and a conditions array (one or more join conditions):
joins:
- stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- PRODUCTS
conditions:
- - =
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- PRODUCT_ID
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- ID
alias: Products
strategy: left-join
fields: all| Property | Type | Required | Description |
|---|---|---|---|
stages |
array | Yes | Array of stage objects defining the joined data source |
conditions |
array | Yes | Array of join conditions, each a filter clause |
alias |
string | Yes | Join alias (used in field references) |
strategy |
string | Yes | "left-join", "right-join", "inner-join", "full-join" |
fields |
any | No | "all", "none", or list of field clauses |
Joined fields are referenced with a join-alias option:
- field
- join-alias: Products
- - Sample Database
- PUBLIC
- PRODUCTS
- TITLEComputed columns defined as an array of clauses. Each expression operator's options map includes lib/expression-name to name the resulting column:
expressions:
- - "-"
- "lib/expression-name": Profit
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
- - field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- TAXSee Expression Operators for the full operator reference.
filters is an array of filter clauses that restrict which rows are included. Multiple clauses are implicitly ANDed together. To use OR logic, include an explicit [or, {}, ...] clause as one of the array items.
filters:
- - <operator>
- {}
- <column reference>
- <value>Multiple filter clauses (implicitly ANDed):
filters:
- - ">="
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- PRICE
- 10
- - "<"
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- PRICE
- 100To use OR, place an explicit or clause as an item in the array:
filters:
- - or
- {}
- - =
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
- Widget
- - =
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
- GadgetSee Filter Operators for the full operator reference.
Breakouts group results by columns (like GROUP BY):
breakout:
- - field
- temporal-unit: month
- - Sample Database
- PUBLIC
- ORDERS
- CREATED_ATAggregations compute summary values. Multiple aggregations can be combined. When an aggregation is referenced elsewhere (e.g., in order-by or a later stage), its options map must include a lib/uuid:
aggregation:
- - count
- {}
- - sum
- {}
- - field
- base-type: type/Float
- - Sample Database
- PUBLIC
- ORDERS
- TOTALSee Aggregation Functions for the full reference.
order-by:
- - asc # "asc" or "desc"
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- PRICESort by aggregation result (using the UUID from the aggregation's lib/uuid):
order-by:
- - desc
- {}
- - aggregation
- {}
- "11111111-1111-1111-1111-111111111111"limit: 10The temporal-unit field option groups a datetime column into time buckets. This is commonly used in breakouts to group results by month, quarter, etc.
Bucketing units: default, millisecond, second, minute, hour, day, week, month, quarter, year.
Extraction units (return an integer component): minute-of-hour, hour-of-day, day-of-week, day-of-week-iso, day-of-month, day-of-year, week-of-year, week-of-year-iso, month-of-year, quarter-of-year, year-of-era, second-of-minute.
# Breakout by month
breakout:
- - field
- temporal-unit: month
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
# Breakout by day of week
breakout:
- - field
- temporal-unit: day-of-week
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]Bucketing units truncate the datetime (e.g., month groups 2024-03-15 into 2024-03-01). Extraction units extract a numeric component (e.g., day-of-week returns 1–7).
The binning field option groups a numeric or coordinate column into bins. This is commonly used in breakouts for histograms or geographic grids.
Three strategies are available:
| Strategy | Properties | Description |
|---|---|---|
num-bins |
num-bins (integer) |
Split into a fixed number of equal-width bins |
bin-width |
bin-width (number) |
Each bin has a fixed width |
default |
— | Let Metabase choose an appropriate binning |
# 10 equal bins
breakout:
- - field
- binning:
strategy: num-bins
num-bins: 10
- [Sample Database, PUBLIC, PRODUCTS, PRICE]
# Bins of width 25
breakout:
- - field
- binning:
strategy: bin-width
bin-width: 25
- [Sample Database, PUBLIC, PRODUCTS, PRICE]| Operator | Arguments | Description |
|---|---|---|
and |
2+ boolean clauses | Logical AND |
or |
2+ boolean clauses | Logical OR |
not |
1 boolean clause | Logical NOT |
# AND
- and
- {}
- - ">"
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]
- 50
- - "!="
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, CATEGORY]
- Doohickey| Operator | Arguments | Description |
|---|---|---|
= |
2+ comparable values | Equals (multi-value = IN) |
!= |
2+ comparable values | Not equals (multi-value = NOT IN) |
< |
2 orderable values | Less than |
> |
2 orderable values | Greater than |
<= |
2 orderable values | Less than or equal |
>= |
2 orderable values | Greater than or equal |
between |
expr, min, max | Inclusive range check |
inside |
lat, lon, lat-max, lon-min, lat-min, lon-max | Geographic bounding box |
# Equals
- =
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, CATEGORY]
- Widget
# Multi-value equals (IN)
- =
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, CATEGORY]
- Widget
- Gadget
- Gizmo
# Between
- between
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]
- 10
- 100
# Inside (bounding box)
- inside
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, LATITUDE]
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, LONGITUDE]
- 40.8 # north latitude
- -74.1 # west longitude
- 40.6 # south latitude
- -73.9 # east longitude| Operator | Arguments | Description |
|---|---|---|
is-null |
1 expression | Is NULL |
not-null |
1 expression | Is not NULL |
is-empty |
1 string expression | Is NULL or empty string |
not-empty |
1 string expression | Is not NULL and not empty string |
- is-null
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, DISCOUNT]
- not-empty
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, EMAIL]All string filter operators accept a case-sensitive option (default: true). They are N-ary — multiple values are combined with OR. The options map is always in the second position (after the operator, before the field).
| Operator | Arguments | Description |
|---|---|---|
contains |
2+ string values | Contains substring |
does-not-contain |
2+ string values | Does not contain substring |
starts-with |
2+ string values | Starts with prefix |
ends-with |
2+ string values | Ends with suffix |
# Single value, case-insensitive
- contains
- case-sensitive: false
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, TITLE]
- widget
# Multiple values (empty options)
- starts-with
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, NAME]
- John
- Jane
# Multiple values, case-insensitive
- starts-with
- case-sensitive: false
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, NAME]
- John
- Jane
- Charlie| Operator | Arguments | Description |
|---|---|---|
time-interval |
temporal-field, n, unit | Relative time interval. n can be an integer, current, last, or next. |
relative-time-interval |
temporal-field, value, bucket, offset-value, offset-bucket | Relative interval with offset |
Valid units for time-interval and relative-time-interval: millisecond, second, minute, hour, day, week, month, quarter, year (truncation units only).
# Last 30 days
- time-interval
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- -30
- day
# Current month
- time-interval
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- current
- month
# Last 30 days, offset by 1 month
- relative-time-interval
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- -30
- day
- -1
- monthReference a saved segment by entity_id:
- segment
- {}
- aB3kLmN9pQrStUvWxYz1a| Function | Arguments | Returns | Description |
|---|---|---|---|
count |
none or 1 expression | integer | Count rows (with arg: count non-NULL) |
sum |
1 numeric | numeric | Sum of values |
avg |
1 numeric | float | Average |
min |
1 orderable | same type | Minimum value |
max |
1 orderable | same type | Maximum value |
distinct |
1 expression | integer | Count of distinct values |
# Count all rows
aggregation:
- - count
- {}
# Sum with field
aggregation:
- - sum
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]
# Multiple aggregations
aggregation:
- - count
- {}
- - avg
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]| Function | Arguments | Returns | Description |
|---|---|---|---|
cum-count |
none or 1 expression | integer | Running count |
cum-sum |
1 numeric | numeric | Running sum |
aggregation:
- - cum-sum
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]| Function | Arguments | Returns | Description |
|---|---|---|---|
stddev |
1 numeric | float | Standard deviation |
var |
1 numeric | float | Variance |
median |
1 numeric | numeric | Median value |
percentile |
numeric, p (0.0–1.0) | numeric | Percentile value |
# 90th percentile
aggregation:
- - percentile
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- 0.9| Function | Arguments | Returns | Description |
|---|---|---|---|
count-where |
1 boolean clause | integer | Count rows matching condition |
sum-where |
numeric, boolean clause | numeric | Sum where condition is true |
distinct-where |
expression, boolean clause | integer | Count distinct where condition is true |
share |
1 boolean clause | float (0–1) | Proportion of rows matching condition |
# Count where
aggregation:
- - count-where
- {}
- - ">"
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- 100
# Share
aggregation:
- - share
- {}
- - =
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, CATEGORY]
- WidgetAggregations can have a custom display name by setting display-name and/or name directly in the aggregation clause's options:
aggregation:
- - sum
- display-name: Total Revenue
name: total_revenue
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]A metric clause references a saved metric (a card with type: metric) by its entity_id:
aggregation:
- - metric
- {}
- f1C68pznmrpN1F5xFDj6d # entity_id of a metric cardA measure clause references a saved measure by its entity_id. Measures can reference other measures but cannot reference metrics:
aggregation:
- - measure
- {}
- xK7mPqR2sT4uVwXyZ9a1b # entity_id of a saved measure| Operator | Arguments | Returns | Description |
|---|---|---|---|
+ |
2+ numeric (or temporal + interval) | numeric / temporal | Addition |
- |
1+ numeric (or temporal − interval) | numeric / interval | Subtraction (unary = negation) |
* |
2+ numeric | numeric | Multiplication |
/ |
2+ numeric | float | Division (always returns float) |
Note: The - operator must be quoted as "-" in YAML when it appears as the first element of a list, to avoid being parsed as a list indicator.
# Subtraction: TOTAL - TAX
expressions:
- - "-"
- "lib/expression-name": Profit
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, TAX]
# Date arithmetic: CREATED_AT + 7 days
expressions:
- - +
- "lib/expression-name": Due Date
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- - interval
- {}
- 7
- day| Operator | Arguments | Returns | Description |
|---|---|---|---|
abs |
1 numeric | same type | Absolute value |
ceil |
1 numeric | integer | Round up to integer |
floor |
1 numeric | integer | Round down to integer |
round |
1 numeric | integer | Round to nearest integer |
power |
base, exponent | numeric | Raise to power |
sqrt |
1 numeric | float | Square root |
exp |
1 numeric | float | Exponential (e^x) |
log |
1 numeric | float | Natural logarithm |
# Absolute value
- abs
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, DISCOUNT]
# Power
- power
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, RATING]
- 2
# Square root
- sqrt
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]| Operator | Arguments | Returns | Description |
|---|---|---|---|
concat |
2+ expressions | text | Concatenate strings |
substring |
str, start, length? | text | Extract substring (1-indexed) |
replace |
str, find, replace | text | Replace all occurrences |
regex-match-first |
str, regex | text | Extract first regex match |
split-part |
str, delimiter, position | text | Split and get Nth part |
trim |
1 string | text | Trim whitespace (both ends) |
ltrim |
1 string | text | Trim leading whitespace |
rtrim |
1 string | text | Trim trailing whitespace |
upper |
1 string | text | Convert to uppercase |
lower |
1 string | text | Convert to lowercase |
length |
1 string | integer | String length |
host |
1 string (URL) | text | Extract host from URL |
domain |
1 string (URL) | text | Extract domain from URL |
subdomain |
1 string (URL) | text | Extract subdomain from URL |
path |
1 string (URL) | text | Extract path from URL |
# Concat
- concat
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, NAME]
- " <"
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, EMAIL]
- ">"
# Substring (characters 1-3)
- substring
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, TITLE]
- 1
- 3
# Replace
- replace
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, EMAIL]
- "@example.com"
- "@company.com"
# Regex match
- regex-match-first
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, EMAIL]
- "^[^@]+"
# Domain from URL
- domain
- {}
- - field
- {}
- [Sample Database, PUBLIC, PEOPLE, SOURCE]| Operator | Arguments | Returns | Description |
|---|---|---|---|
now |
none | datetime | Current date and time |
today |
none | date | Today's date |
interval |
amount, unit | interval | Create a temporal interval |
datetime-add |
temporal, amount, unit | temporal | Add interval to date/time |
datetime-subtract |
temporal, amount, unit | temporal | Subtract interval from date/time |
datetime-diff |
datetime1, datetime2, unit | integer | Difference between two dates |
convert-timezone |
temporal, target-tz, source-tz? | temporal | Convert timezone |
get-year |
1 temporal | integer | Extract year |
get-quarter |
1 temporal | integer | Extract quarter (1–4) |
get-month |
1 temporal | integer | Extract month (1–12) |
get-day |
1 temporal | integer | Extract day of month |
get-hour |
1 temporal | integer | Extract hour (0–23) |
get-minute |
1 temporal | integer | Extract minute (0–59) |
get-second |
1 temporal | integer | Extract second (0–59) |
get-day-of-week |
temporal, mode? | integer | Day of week. Mode: iso (Mon=1), us (Sun=1), instance |
get-week |
temporal, mode? | integer | Week number. Mode: iso, us, instance |
temporal-extract |
temporal, unit, mode? | integer | Generic extraction (see units below) |
month-name |
1 integer (1–12) | text | Month name from number |
quarter-name |
1 integer (1–4) | text | Quarter name from number |
day-name |
1 integer | text | Day name from number |
Interval units for datetime-add, datetime-subtract: year, quarter, month, week, day, hour, minute, second, millisecond.
Difference units for datetime-diff: year, quarter, month, week, day, hour, minute, second.
Extraction units for temporal-extract: year-of-era, quarter-of-year, month-of-year, week-of-year-iso, week-of-year-us, week-of-year-instance, day-of-month, day-of-week, day-of-week-iso, hour-of-day, minute-of-hour, second-of-minute.
# Add 7 days
- datetime-add
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- 7
- day
# Difference in months
- datetime-diff
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- - now
- {}
- month
# Extract year
- get-year
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
# Convert timezone
- convert-timezone
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, CREATED_AT]
- America/New_York
- UTC| Operator | Arguments | Returns | Description |
|---|---|---|---|
integer |
string or numeric | integer | Convert to integer |
float |
string | float | Convert to float |
text |
1 expression | text | Convert to text |
- integer
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]| Operator | Arguments | Returns | Description |
|---|---|---|---|
case |
pairs of [condition, value], optional default expression | value type | Conditional expression (if/then/else). The default value is supplied as a 4th positional argument. |
if |
same as case |
value type | Alias for case |
coalesce |
2+ expressions | first non-null type | First non-null value |
# Case expression
expressions:
- - case
- "lib/expression-name": Price Tier
- - - - ">"
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]
- 100
- Premium
- - - "<="
- {}
- - field
- {}
- [Sample Database, PUBLIC, PRODUCTS, PRICE]
- 20
- Budget
- Standard # default (4th positional arg)
# Coalesce
- coalesce
- {}
- - field
- {}
- [Sample Database, PUBLIC, ORDERS, DISCOUNT]
- 0Window functions can only be used inside the aggregation clause.
| Operator | Arguments | Returns | Description |
|---|---|---|---|
offset |
expression, n | same type | Value from n rows before (negative) or after (positive). |
aggregation:
- - sum
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- - offset
- {}
- - sum
- {}
- - field
- base-type: type/Float
- [Sample Database, PUBLIC, ORDERS, TOTAL]
- -1Native queries use plain SQL with Metabase template tags for dynamic values.
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/native
native: SELECT * FROM PRODUCTS
template-tags: {}Template tags are placeholders in native SQL queries ({{tag_name}}) that become interactive filters or dynamic references. They are defined in the template-tags map, where each key must match the tag's name property.
All template tags share these properties:
| Property | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Tag type: text, number, date, boolean, dimension, temporal-unit, card, snippet, table |
name |
string | Yes | Tag name — must match the key in template-tags and the {{name}} in the SQL |
id |
string | Yes | UUID identifier |
display-name |
string | Yes | Label shown in the UI |
A string variable. Metabase wraps the value in single quotes in the compiled SQL.
| Property | Type | Required | Description |
|---|---|---|---|
default |
string | No | Default value |
required |
boolean | No | Whether a value must be provided |
native:
query: "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}}"
template-tags:
category:
type: text
name: category
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Category
default: Widget
required: trueCompiled SQL (value Widget): SELECT * FROM PRODUCTS WHERE CATEGORY = 'Widget'
A numeric variable. The value is inserted as-is (no quoting).
| Property | Type | Required | Description |
|---|---|---|---|
default |
number | No | Default value |
required |
boolean | No | Whether a value must be provided |
native:
query: "SELECT * FROM PRODUCTS WHERE PRICE > {{min_price}}"
template-tags:
min_price:
type: number
name: min_price
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Minimum Price
default: nullCompiled SQL (value 50): SELECT * FROM PRODUCTS WHERE PRICE > 50
A date variable. The value is wrapped in single quotes.
| Property | Type | Required | Description |
|---|---|---|---|
default |
string | No | Default date value (ISO format) |
required |
boolean | No | Whether a value must be provided |
native:
query: "SELECT * FROM ORDERS WHERE CREATED_AT > {{after_date}}"
template-tags:
after_date:
type: date
name: after_date
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: After Date
default: nullCompiled SQL (value 2024-01-01): SELECT * FROM ORDERS WHERE CREATED_AT > '2024-01-01'
A boolean variable. Metabase replaces the tag with 1 = 1 (true) or 1 <> 1 (false). When no value is provided, defaults to 1 = 1.
| Property | Type | Required | Description |
|---|---|---|---|
default |
boolean | No | Default value |
required |
boolean | No | Whether a value must be provided |
native:
query: "SELECT * FROM PRODUCTS WHERE {{is_active}}"
template-tags:
is_active:
type: boolean
name: is_active
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Is Active
default: trueCompiled SQL (true): SELECT * FROM PRODUCTS WHERE 1 = 1
Compiled SQL (false): SELECT * FROM PRODUCTS WHERE 1 <> 1
A field filter that maps a template tag to a specific database field. Metabase generates smart filter widgets (date pickers, category dropdowns) and replaces the tag with the appropriate SQL expression. The tag must be used in a WHERE clause context.
When no value is provided, the entire WHERE {{tag}} clause is omitted (the query runs unfiltered).
| Property | Type | Required | Description |
|---|---|---|---|
dimension |
array | Yes | Field clause: [field, options, Field FK] (see Field References) |
widget-type |
string | Yes | Filter widget type — any value from Parameter Types |
default |
any | No | Default filter value |
required |
boolean | No | Whether a value must be provided |
options |
map | No | Options appended to the generated filter clause (e.g., {case-sensitive: false}) |
native:
query: "SELECT * FROM PRODUCTS WHERE {{category_filter}}"
template-tags:
category_filter:
type: dimension
name: category_filter
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Category
dimension:
- field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
widget-type: string/=
default: nullCompiled SQL (widget-type: string/=, value Widget): SELECT * FROM PRODUCTS WHERE CATEGORY = 'Widget'
Compiled SQL (widget-type: date/range, value 2024-01-01~2024-12-31): SELECT * FROM ORDERS WHERE CREATED_AT >= '2024-01-01' AND CREATED_AT < '2025-01-01'
A temporal grouping variable. Metabase replaces the tag with a DATE_TRUNC(unit, column) expression. The user selects a temporal granularity (month, quarter, year, etc.) from a dropdown.
| Property | Type | Required | Description |
|---|---|---|---|
dimension |
array | Yes | Field clause: [field, options, Field FK] — the temporal column to group |
default |
string | No | Default temporal unit (e.g., month) |
alias |
string | No | Overrides the SQL column name used inside the generated expression. By default Metabase uses the column name from dimension (e.g., CREATED_AT). When the SQL uses a table alias, set alias to match so the generated expression references the correct name. |
Without alias — Metabase uses the column name from dimension (CREATED_AT):
native:
query: "SELECT {{created_at}} AS created_at, COUNT(*) FROM ORDERS GROUP BY {{created_at}}"
template-tags:
created_at:
type: temporal-unit
name: created_at
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Created At
default: month
dimension:
- field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- CREATED_ATCompiled SQL (value month): SELECT DATE_TRUNC('month', CREATED_AT) AS created_at, COUNT(*) FROM ORDERS GROUP BY DATE_TRUNC('month', CREATED_AT)
With alias — when the query uses a table alias (o), set alias so the generated expression uses o.CREATED_AT instead of the fully-qualified column name:
native:
query: "SELECT {{created_at}} AS created_at, COUNT(*) FROM ORDERS o GROUP BY {{created_at}}"
template-tags:
created_at:
type: temporal-unit
name: created_at
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Created At
default: month
alias: o.CREATED_AT
dimension:
- field
- {}
- - Sample Database
- PUBLIC
- ORDERS
- CREATED_ATCompiled SQL (value month): SELECT DATE_TRUNC('month', o.CREATED_AT) AS created_at, COUNT(*) FROM ORDERS o GROUP BY DATE_TRUNC('month', o.CREATED_AT)
Reference a saved card (question) as a CTE subquery using {{#<numeric_id>-<slug>}} syntax. The SQL template uses the card's numeric ID; the card-id property stores the card's entity_id (NanoID) for portability. Metabase replaces the tag with the card's query wrapped in a WITH clause.
| Property | Type | Required | Description |
|---|---|---|---|
card-id |
string | Yes | Card FK (entity_id of the referenced card) |
Note: default and required are not applicable for card tags.
native:
query: "SELECT * FROM {{#42-products_question}} WHERE PRICE > 50"
template-tags:
"#42-products_question":
type: card
name: "#42-products_question"
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Products Question
card-id: f1C68pznmrpN1F5xFDj6dCompiled SQL (assuming the card's query is SELECT * FROM PUBLIC.PRODUCTS):
WITH products_question AS (SELECT * FROM PUBLIC.PRODUCTS)
SELECT * FROM products_question WHERE PRICE > 50Reference a reusable SQL snippet using {{snippet: Snippet Name}} syntax. Metabase replaces the tag with the snippet's SQL content inline.
| Property | Type | Required | Description |
|---|---|---|---|
snippet-name |
string | Yes | Name of the snippet |
snippet-id |
string | No | Snippet FK (entity_id of the snippet) |
Note: default and required are not applicable for snippet tags.
native:
query: "SELECT * FROM ORDERS WHERE {{snippet: Active Order Filter}}"
template-tags:
"snippet: Active Order Filter":
type: snippet
name: "snippet: Active Order Filter"
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: "Snippet: Active Order Filter"
snippet-name: Active Order Filter
snippet-id: xK7mPqR2sT4uVwXyZ9a1bCompiled SQL (snippet content: STATUS = 'active' AND TOTAL > 0):
SELECT * FROM ORDERS WHERE STATUS = 'active' AND TOTAL > 0Reference a table dynamically. The user selects a table from a dropdown and Metabase replaces the tag with the fully qualified table name.
| Property | Type | Required | Description |
|---|---|---|---|
table-id |
array | Yes | Table FK [database, schema, table] |
emit-alias |
boolean | No | Whether to emit the table name as an alias |
native:
query: "SELECT * FROM {{source_table}}"
template-tags:
source_table:
type: table
name: source_table
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
display-name: Source Table
table-id:
- Sample Database
- PUBLIC
- PRODUCTSCompiled SQL (with PUBLIC.PRODUCTS selected): SELECT * FROM PUBLIC.PRODUCTS
Visualization settings control how query results are displayed. They are stored in the visualization_settings field of Cards and DashboardCards. DashboardCard visualization settings override the card's own settings and can additionally include click behaviors.
| Setting | Type | Description |
|---|---|---|
column_settings |
map | Per-column formatting keyed by column reference string |
"dashcard.background" |
boolean | Show/hide dashcard background (dashcards only) |
Apply to line, bar, area, combo, scatter, waterfall, row, boxplot displays.
| Setting | Type | Description |
|---|---|---|
"graph.show_values" |
boolean | Show values on data points |
"graph.label_values_frequency" |
string | Value label frequency: "fit", "all" |
"graph.show_stack_values" |
string | "total", "individual", "all" |
"graph.x_axis.title_text" |
string | X-axis title |
"graph.x_axis.scale" |
string | "ordinal", "histogram", "timeseries", "linear", "pow", "log" |
"graph.x_axis.axis_enabled" |
boolean/string | true, false, "compact", "rotate-45", "rotate-90" |
"graph.y_axis.title_text" |
string | Y-axis title |
"graph.y_axis.scale" |
string | "linear", "pow", "log" |
"graph.y_axis.auto_range" |
boolean | Auto-scale Y axis |
"graph.y_axis.min" |
number | Y-axis minimum (when auto_range is false) |
"graph.y_axis.max" |
number | Y-axis maximum |
"graph.show_goal" |
boolean | Show goal line |
"graph.goal_value" |
number | Goal line value |
"graph.goal_label" |
string | Goal line label |
"graph.show_trendline" |
boolean | Show trend line |
"graph.dimensions" |
array | Dimension column names |
"graph.metrics" |
array | Metric column names |
"graph.series_order" |
array | Series display order |
"graph.max_categories_enabled" |
boolean | Limit number of categories |
"graph.max_categories" |
number | Maximum categories shown |
"graph.other_category_aggregation_fn" |
string | "sum", "avg", "min", "max" |
"stackable.stack_type" |
string | null, "stacked", "normalized" |
Per-series overrides keyed by series name:
series_settings:
Revenue:
display: line
color: "#509EE3"
"line.style": solid # "solid", "dashed", "dotted"
"line.size": normal # "S", "M", "L"
"line.interpolate": linear # "linear", "cardinal", "step-before", "step-after"
"line.missing": interpolate # "interpolate", "zero", "none"
"line.marker_enabled": true
axis: left # "left", "right"
show_series_values: true| Setting | Type | Description |
|---|---|---|
"table.columns" |
array | Column order and visibility — each entry: {name, enabled} |
"table.column_formatting" |
array | Conditional formatting rules |
"table.cell_column" |
string | Column to use for cell values (in pivot mode) |
"table.pivot" |
boolean | Enable pivot mode |
"table.pivot_column" |
string | Column to pivot on |
Each rule in "table.column_formatting":
table.column_formatting:
- columns:
- Total
type: single # "single" or "range"
operator: ">" # "=", "!=", "<", ">", "<=", ">=", "is-null", "not-null"
value: 100
color: "#84BB4C"
highlight_row: false
- columns:
- Rating
type: range
colors:
- "#ED6E6E"
- "#F9CF48"
- "#84BB4C"
min_type: custom # "min", "max", "custom"
min_value: 1
max_type: custom
max_value: 5| Setting | Type | Description |
|---|---|---|
"pivot_table.column_split" |
object | {rows: [...column_names], columns: [...column_names], values: [...column_names]} |
"pivot_table.collapsed_rows" |
object | {rows: [...collapsed_keys], value: []} |
"pivot_table.show_row_totals" |
boolean | Show row totals |
"pivot_table.show_column_totals" |
boolean | Show column totals |
| Setting | Type | Description |
|---|---|---|
"pie.dimension" |
string | Dimension column |
"pie.metric" |
string | Metric column |
"pie.show_legend" |
boolean | Show legend |
"pie.show_total" |
boolean | Show total in center |
"pie.percent_visibility" |
string | "off", "legend", "inside", "both" |
"pie.slice_threshold" |
number | Minimum percentage to show as separate slice |
"pie.colors" |
object | Color map keyed by dimension value |
| Setting | Type | Description |
|---|---|---|
"scalar.field" |
string | Field to display |
"scalar.switch_positive_negative" |
boolean | Invert positive/negative colors |
"scalar.compact_primary_number" |
string | "auto", "yes", "no" |
| Setting | Type | Description |
|---|---|---|
"scalar.comparisons" |
array | Comparison definitions (see below) |
Comparison types:
scalar.comparisons:
- id: comp1
type: previousPeriod # vs. previous time period
- id: comp2
type: previousValue # vs. previous value
- id: comp3
type: periodsAgo # vs. N periods ago
value: 12
- id: comp4
type: staticNumber # vs. fixed number
value: 1000
label: Target| Setting | Type | Description |
|---|---|---|
"gauge.segment_colors" |
array | Segment colors |
"gauge.segments" |
array | Gauge segments with min, max, color, label |
| Setting | Type | Description |
|---|---|---|
"map.type" |
string | "region", "pin", "grid" |
"map.latitude_column" |
string | Latitude column name |
"map.longitude_column" |
string | Longitude column name |
"map.metric_column" |
string | Metric column for coloring |
"map.region" |
string | Region map identifier |
"map.colors" |
array | Color scale |
"map.zoom" |
number | Initial zoom level |
"map.center_latitude" |
number | Center latitude |
"map.center_longitude" |
number | Center longitude |
"map.pin_type" |
string | "tiles", "markers", "heat" |
| Setting | Type | Description |
|---|---|---|
"funnel.dimension" |
string | Dimension column |
"funnel.metric" |
string | Metric column |
"funnel.type" |
string | "funnel" or "bar" |
"funnel.rows" |
array | Row order definitions |
| Setting | Type | Description |
|---|---|---|
"waterfall.increase_color" |
string | Color for increases |
"waterfall.decrease_color" |
string | Color for decreases |
"waterfall.total_color" |
string | Color for total bar |
"waterfall.show_total" |
boolean | Show total bar |
| Setting | Type | Description |
|---|---|---|
"sankey.source" |
string | Source column |
"sankey.target" |
string | Target column |
"sankey.value" |
string | Value column |
"sankey.node_align" |
string | "left", "right", "center", "justify" |
"sankey.show_edge_labels" |
boolean | Show labels on edges |
| Setting | Type | Description |
|---|---|---|
"boxplot.whisker_type" |
string | "min-max", "tukey", "percentile" |
"boxplot.points_mode" |
string | "none", "outliers", "all" |
"boxplot.show_mean" |
boolean | Show mean marker |
Per-column formatting stored in column_settings, keyed by column name (e.g., ["name","COLUMN_NAME"]):
column_settings:
'["name","TOTAL"]':
number_style: currency
currency: USD
currency_style: symbol # "symbol", "code", "name"
number_separators: ".," # decimal + thousands separator
decimals: 2
scale: 1 # multiply values by this factor
prefix: ""
suffix: ""
column_title: "Total Revenue"
'["name","CREATED_AT"]':
date_style: "MMMM D, YYYY" # moment.js format
date_separator: "/"
date_abbreviate: false
time_enabled: null # null, "minutes", "seconds", "milliseconds"
time_style: "h:mm A" # "HH:mm", "h:mm A", etc.
'["name","EMAIL"]':
view_as: link # "link", "image", "email", "auto"
link_text: "Send email"
link_url: "mailto:{{value}}"For dashcards with card_id: null:
# Heading
visualization_settings:
virtual_card:
display: heading
text: "Section Title"
# Text (markdown)
visualization_settings:
virtual_card:
display: text
text: "**Bold** and _italic_ markdown content"
# Text with parameter placeholders. Each `{{name}}` is wired to a dashboard
# parameter through `parameter_mappings` on the dashcard, with target
# `[text-tag, name]`. At render time the placeholder is replaced with the
# parameter's current value.
visualization_settings:
virtual_card:
display: text
text: "Showing results for {{product_category}}"
# Link (URL)
visualization_settings:
virtual_card:
display: link
link:
url: "https://example.com"
# Link (entity reference)
visualization_settings:
virtual_card:
display: link
link:
entity:
id: f1C68pznmrpN1F5xFDj6d
model: question # "question", "dashboard", "collection", "database", "table"
# iFrame
visualization_settings:
virtual_card:
display: iframe
iframe: '<iframe src="https://example.com/embed"></iframe>'
# Placeholder
visualization_settings:
virtual_card:
display: placeholderClick behaviors define what happens when a user clicks on a dashboard card or a specific column within a table. They are stored in visualization_settings.click_behavior on dashcards, or per-column in visualization_settings.column_settings[column].click_behavior.
| Type | Description |
|---|---|
actionMenu |
Default drill-through menu (no explicit config needed) |
crossfilter |
Filter the dashboard using the clicked value |
link |
Navigate to a URL, question, or dashboard |
Maps clicked column values to dashboard parameters to filter other cards:
click_behavior:
type: crossfilter
parameterMapping:
a1b2c3d4-uuid-of-param:
id: a1b2c3d4-uuid-of-param
source:
id: CATEGORY
name: Category
type: column
target:
id: a1b2c3d4-uuid-of-param
type: parameterNavigate to an arbitrary URL. Supports template variables:
{{column_name}}— value of the clicked row's column{{filter:parameter_name}}— dashboard parameter value
click_behavior:
type: link
linkType: url
linkTemplate: "https://example.com/orders/{{ORDER_ID}}?status={{filter:status}}"
linkTextTemplate: "View Order {{ORDER_ID}}"Navigate to another dashboard, optionally mapping values to the target dashboard's parameters:
click_behavior:
type: link
linkType: dashboard
targetId: Q_jD-f-9clKLFZ2TfUG2h # entity_id of target dashboard
parameterMapping:
target-param-uuid:
id: target-param-uuid
source:
id: USER_ID
name: User ID
type: column
target:
id: target-param-uuid
type: parameterNavigate to another question/card:
click_behavior:
type: link
linkType: question
targetId: f1C68pznmrpN1F5xFDj6d # entity_id of target card
parameterMapping:
target-dimension:
id: target-dimension
source:
id: PRODUCT_ID
name: Product ID
type: column
target:
id: target-dimension
type: dimension
dimension:
- dimension
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- IDEach entry in parameterMapping:
| Field | Type | Description |
|---|---|---|
id |
string | Parameter ID or dimension reference |
source |
object | Where the value comes from |
source.id |
string | Column name or parameter ID |
source.name |
string | Display name |
source.type |
string | "column" or "parameter" |
target |
object | Where the value goes |
target.id |
string | Target parameter ID or dimension |
target.type |
string | "parameter", "dimension", or "variable" |
target.dimension |
array | Parameter target (same format as dashboard parameter mapping targets — see Parameter Targets) |
A parameter is a filter control on a dashboard or card. Parameters are not standalone entities — they are embedded in the parameters array of their parent.
On dashboards, parameters define filter controls shown at the top of the dashboard. They are wired to card columns via parameter_mappings on each dashcard.
On cards, parameters are typically empty [] for MBQL queries. For native queries, they expose template tag variables as filter controls.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier within the dashboard or card. UUIDs are recommended, but any unique non-empty string is accepted (e.g., 9d9cddd4). |
name |
string | Yes | Display name |
slug |
string | Yes | URL-friendly identifier |
type |
string | Yes | Filter widget type (see below) |
default |
any | No | Default value |
required |
boolean | No | Whether a value is required |
sectionId |
string | No | Parameter section grouping |
temporal_units |
array | No | Allowed temporal units (for temporal-unit type) |
values_query_type |
string | No | "list", "search", or "none" — controls how values are fetched |
values_source_type |
string | No | null, "card", or "static-list" — where values come from |
values_source_config |
map | No | Source configuration (see below) |
When values_source_type is "static-list", the config provides inline values:
values_source_type: static-list
values_source_config:
values:
- [1, "One"]
- [2, "Two"]When values_source_type is "card", the config references a card:
values_source_type: card
values_source_config:
card_id: f1C68pznmrpN1F5xFDj6d
value_field:
- field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- ID
label_field:
- field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- TITLE| Property | Type | Description |
|---|---|---|
values |
array | Static list of [value, label] pairs (for static-list) |
card_id |
string | Card entity_id to source values from (for card) |
value_field |
array | Field clause for extracting values from card results |
label_field |
array | Field clause for extracting display labels from card results |
| Type | Description |
|---|---|
string/= |
String equals |
string/!= |
String not equals |
string/contains |
String contains |
string/does-not-contain |
String does not contain |
string/starts-with |
String starts with |
string/ends-with |
String ends with |
number/= |
Number equals |
number/!= |
Number not equals |
number/>= |
Number greater than or equal |
number/<= |
Number less than or equal |
number/between |
Number between |
date/single |
Single date |
date/range |
Date range |
date/month-year |
Month and year |
date/quarter-year |
Quarter and year |
date/relative |
Relative date (e.g., "last 7 days") |
date/all-options |
All date filter options |
boolean/= |
Boolean equals |
temporal-unit |
Temporal unit selector |
none |
No filter widget (unconfigured) |
The sectionId property restricts which columns are available for mapping in the UI. It is optional — when omitted, Metabase infers it from the parameter type.
| sectionId | Available columns | Typical parameter types |
|---|---|---|
string |
Text columns | string/=, string/!=, string/contains, etc. |
number |
Numeric columns | number/=, number/!=, number/between, etc. |
date |
Date/time columns | date/single, date/range, date/relative, etc. |
boolean |
Boolean columns | boolean/= |
temporal-unit |
Temporal unit selector | temporal-unit |
id |
Only PK and FK columns | number/= or string/= with sectionId: id |
location |
Only location columns (country, city, etc.) | string/= with sectionId: location |
Use sectionId: id to make a number/= or string/= parameter map only to primary key and foreign key columns. Use sectionId: location to restrict mapping to location-typed columns.
Parameter targets specify which column or variable a parameter maps to. The outer wrapper is dimension, variable, or text-tag:
dimension— for MBQL column references (field,expression) and for native template tags of typedimensionortemporal-unitvariable— for native template tags of typetext,number,date, orbooleantext-tag— for placeholders inside text/heading virtual cards (see Virtual Card Settings)
An optional third element {stage-number: N} or null can specify which query stage the target belongs to (0 = first stage).
Important: Field and expression references inside parameter targets use legacy format ([field, Field-FK, null-or-options], [expression, name]), not pMBQL format. This differs from references inside dataset_query.stages, which use pMBQL format ([field, options, Field-FK]).
MBQL — field reference:
target:
- dimension
- - field
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
- nullMBQL — multi-stage field reference (column name, not Field FK):
target:
- dimension
- - field
- CATEGORY
- null
- stage-number: 1MBQL — expression reference:
target:
- dimension
- - expression
- ProfitNative — field filter (dimension) or time grouping (temporal-unit) template tag:
target:
- dimension
- - template-tag
- category_filterNative — other template tags (text, number, date, boolean):
target:
- variable
- - template-tag
- min_priceText card placeholder:
target:
- text-tag
- product_categoryA collection is a folder-like container for organizing cards, dashboards, and other entities. Collection hierarchy is reflected in the directory structure.
Critical: Subcollections must set
parent_idto theentity_idof their parent collection. Withoutparent_id, a collection is treated as a root-level collection regardless of where its file is located on disk. All items inside a collection (cards, dashboards, documents, etc.) must set theircollection_idto that collection'sentity_idto appear within it.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Collection name |
entity_id |
string | Yes | NanoID identifier |
serdes/meta |
array | Yes | Identity path with model: Collection |
description |
string | No | Description |
slug |
string | No | URL-friendly name |
archived |
boolean | No | Whether archived (default: false) |
archived_directly |
boolean | No | Archived directly vs. inherited |
type |
string | No | null or "instance-analytics" |
namespace |
string | No | null, "transforms", or "snippets" |
authority_level |
string | No | null or "official" |
parent_id |
string | No | Collection FK (entity_id of parent). Must be set for subcollections; null/omitted = root-level collection |
personal_owner_id |
string | No | User FK (email) for personal collections |
is_sample |
boolean | No | Sample collection flag |
created_at |
string | No | ISO 8601 timestamp |
Root collection:
name: Minimal
entity_id: cOlMiNiMaL000ExAmPlx2
slug: minimal
serdes/meta:
- id: cOlMiNiMaL000ExAmPlx2
label: minimal
model: CollectionSubcollection (with parent_id):
name: Reports
entity_id: cOlRePorTs000ExAmPlx2
parent_id: cOlMiNiMaL000ExAmPlx2
serdes/meta:
- id: cOlRePorTs000ExAmPlx2
label: reports
model: CollectionA card represents a Question, Model, or Metric in Metabase. Cards are the primary way to save and share queries. Each card holds a dataset_query — see MBQL Query and Native Query.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Card name |
entity_id |
string | Yes | NanoID identifier |
display |
string | Yes | Visualization type (see below) |
creator_id |
string | Yes | User FK (email) |
dataset_query |
object | Yes | Query definition — MBQL or native |
visualization_settings |
map | Yes | Display settings (can be empty {}) |
serdes/meta |
array | Yes | Identity path with model: Card |
description |
string | No | Description |
archived |
boolean | No | Whether archived (default: false) |
archived_directly |
boolean | No | Archived directly vs. inherited |
type |
string | No | "question", "model", or "metric" |
collection_id |
string | No | Collection FK (entity_id). Set this to place the card in a collection; null/omitted = root collection. When dashboard_id or document_id is set, must match the parent's collection_id |
collection_position |
integer | No | Position within collection |
collection_preview |
boolean | No | Show preview in collection (default: true) |
dashboard_id |
string | No | Dashboard FK (entity_id). Card's collection_id must match the dashboard's collection_id |
document_id |
string | No | Document FK (entity_id). Card's collection_id must match the document's collection_id |
database_id |
string | No | Database FK (database name). Only included when not derivable from dataset_query (e.g., empty query); re-derived from the query on import when present. |
parameters |
array | No | Card parameters (see Parameter) |
parameter_mappings |
array | No | Unused, always empty [] |
result_metadata |
array | No | Query result column metadata |
enable_embedding |
boolean | No | Embedding enabled |
embedding_params |
map | No | Embedding parameter config |
embedding_type |
string | No | null, "sdk", "standalone" |
public_uuid |
string | No | Public sharing UUID |
made_public_by_id |
string | No | User FK (email) |
metabase_version |
string | No | Metabase version that created the card |
card_schema |
integer | No | Internal card schema version |
created_at |
string | No | ISO 8601 timestamp |
table, bar, line, area, row, pie, scalar, smartscalar, combo, pivot, funnel, map, scatter, waterfall, progress, gauge, object, list, heading, text, link, iframe, action, sankey, boxplot, number.
Card parameters define what filter inputs the card accepts. For MBQL queries, parameters is typically empty []. For native queries, card parameters expose template tag variables as filter controls with the same shape as dashboard parameters (see Parameter).
name: Products question
entity_id: f1C68pznmrpN1F5xFDj6d
display: table
creator_id: internal@metabase.com
type: question
dataset_query:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- PRODUCTS
visualization_settings: {}
collection_id: M-Q4pcV0qkiyJ0kiSWECl
parameters: []
parameter_mappings: []
serdes/meta:
- id: f1C68pznmrpN1F5xFDj6d
label: products_question
model: CardA dashboard is a collection of cards arranged in a grid layout. Dashboards contain dashboard cards (dashcards), parameters for filtering, and optional tabs.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Dashboard name |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
serdes/meta |
array | Yes | Identity path with model: Dashboard |
description |
string | No | Description |
archived |
boolean | No | Whether archived (default: false) |
archived_directly |
boolean | No | Archived directly vs. inherited |
collection_id |
string | No | Collection FK (entity_id). Set this to place the dashboard in a collection; null/omitted = root collection |
collection_position |
integer | No | Position within collection |
position |
integer | No | Display position |
auto_apply_filters |
boolean | No | Auto-apply filter changes (default: true) |
width |
string | No | "fixed" or "full" |
enable_embedding |
boolean | No | Embedding enabled |
embedding_params |
map | No | Embedding parameter config |
embedding_type |
string | No | null, "sdk", "standalone" |
public_uuid |
string | No | Public sharing UUID |
made_public_by_id |
string | No | User FK (email) |
show_in_getting_started |
boolean | No | Show in getting started (default: false) |
caveats |
string | No | Known limitations |
points_of_interest |
string | No | Noteworthy features |
initially_published_at |
string | No | ISO 8601 timestamp |
parameters |
array | No | Dashboard filter parameters (see Parameter) |
tabs |
array | No | Dashboard tabs (see below) |
dashcards |
array | No | Dashboard cards (see below) |
created_at |
string | No | ISO 8601 timestamp |
The dashboard uses a 24-column grid. Cards are positioned using col (0–23) and row (0+) with sizes size_x and size_y in grid units. Cards cannot overlap. Constraint: col + size_x <= 24.
Default card sizes by visualization type:
| Display | Default (w × h) | Minimum (w × h) |
|---|---|---|
table, list, pivot, object |
12 × 9 | 4 × 3 (list: 12 × 6) |
bar, line, area, row, scatter, combo, funnel, progress, boxplot |
12 × 6 | 4 × 3 |
pie |
12 × 8 | 4 × 3 |
waterfall |
14 × 6 | 4 × 3 |
sankey |
16 × 10 | 4 × 3 |
map, gauge |
12 × 6 | 4 × 3 |
iframe |
12 × 8 | 4 × 3 |
scalar, smartscalar |
6 × 3 | 2 × 2 |
number |
6 × 3 | 2 × 2 |
heading |
24 × 1 | 1 × 1 |
text |
12 × 3 | 1 × 1 |
link |
8 × 1 | 1 × 1 |
action |
4 × 1 | 1 × 1 |
Tabs organize dashboard content into separate pages. Each dashcard can be assigned to a tab via dashboard_tab_id.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID identifier |
name |
string | Yes | Tab name |
position |
integer | Yes | Display order (ascending) |
Deleting a tab deletes all dashcards assigned to it.
Dashboard parameters define filter controls that appear at the top of the dashboard. They are wired to specific card columns via parameter_mappings on each dashcard. See Parameter for the full schema.
A dashboard card places a card (question) on the dashboard grid. Most dashboard cards reference an existing card via card_id.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID identifier |
card_id |
string | No | Card FK (entity_id), null for virtual cards (text, heading, link, iframe, placeholder) |
row |
integer | Yes | Grid row position (0+) |
col |
integer | Yes | Grid column position (0–23) |
size_x |
integer | Yes | Width in grid units (1–24) |
size_y |
integer | Yes | Height in grid units (1+) |
serdes/meta |
array | Yes | Identity path: Dashboard → DashboardCard |
dashboard_tab_id |
string | No | Tab entity_id, null for untabbed |
inline_parameters |
array | No | List of parameter UUIDs to display directly on this dashcard |
parameter_mappings |
array | No | Parameter-to-card mappings (see below) |
series |
array | No | Overlay series (see below) |
visualization_settings |
map | No | Display settings |
created_at |
string | No | ISO 8601 timestamp |
Connects a dashboard parameter to a specific card column or variable. Each mapping lives in the parameter_mappings array of a DashboardCard. The target field specifies which column or variable the parameter maps to — see Parameter Targets.
| Field | Type | Required | Description |
|---|---|---|---|
card_id |
string | No | Card FK (entity_id). Omit for mappings on virtual cards (e.g., text-tag placeholders). |
parameter_id |
string | Yes | Matches a dashboard parameter's id |
target |
array | Yes | Parameter target |
Overlays additional cards on the same dashboard card visualization (e.g., multiple lines on one chart).
| Field | Type | Required | Description |
|---|---|---|---|
card_id |
string | Yes | Card FK (entity_id of the series card) |
position |
integer | Yes | Display order (starting at 0) |
name: Orders Overview
entity_id: Q_jD-f-9clKLFZ2TfUG2h
creator_id: internal@metabase.com
width: fixed
auto_apply_filters: true
parameters:
- id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
name: Category
slug: category
type: string/=
tabs:
- entity_id: tAb1dEntIdHere1234x5
name: Overview
position: 0
- entity_id: tAb2dEntIdHere1234x5
name: Details
position: 1
dashcards:
- entity_id: UkpFcfUZMZt9ehChwnrAO
card_id: f1C68pznmrpN1F5xFDj6d
dashboard_tab_id: tAb1dEntIdHere1234x5
row: 0
col: 0
size_x: 12
size_y: 6
parameter_mappings:
- card_id: f1C68pznmrpN1F5xFDj6d
parameter_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
target:
- dimension
- - field
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
- null
series:
- card_id: OMuZ0wHe2O5Z_59-cLmn4
position: 0
visualization_settings: {}
serdes/meta:
- id: Q_jD-f-9clKLFZ2TfUG2h
model: Dashboard
- id: UkpFcfUZMZt9ehChwnrAO
model: DashboardCard
serdes/meta:
- id: Q_jD-f-9clKLFZ2TfUG2h
label: orders_overview
model: DashboardA document is a rich-text page that can contain prose, headings, lists, embedded cards/queries, and references to other entities. Documents use a ProseMirror-compatible tree structure stored as JSON.
Cards can be nested under a document via card.document_id, similar to how cards nest under dashboards. Embedded cards appear inline within the document content as cardEmbed nodes.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Document name (1-254 characters) |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
document |
object | Yes | ProseMirror AST (see Document Nodes below) |
serdes/meta |
array | Yes | Identity path with model: Document |
content_type |
string | No | Always "application/json+vnd.prose-mirror" |
description |
string | No | Description |
collection_id |
string | No | Collection FK (entity_id). Set this to place the document in a collection; null/omitted = root collection |
collection_position |
integer | No | Position within collection |
archived |
boolean | No | Whether archived (default: false) |
archived_directly |
boolean | No | Archived directly vs. inherited |
public_uuid |
string | No | Public sharing UUID |
made_public_by_id |
string | No | User FK (email) |
view_count |
integer | No | Number of times viewed |
created_at |
string | No | ISO 8601 timestamp |
The document field contains a recursive tree of nodes. The root node is always type: doc.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Node type name (see Node Types below) |
content |
array | No | Child nodes (recursive) |
attrs |
map | No | Node-specific attributes (see Node Types below) |
text |
string | No | Text content (only for text nodes) |
marks |
array | No | Inline formatting marks (only for text nodes) |
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Mark type: bold, italic, code, link, underline, strike |
attrs |
map | No | Mark attributes (e.g., href for link marks) |
Block nodes (no attrs):
| Node Type | Description |
|---|---|
doc |
Root node |
paragraph |
Text block |
blockquote |
Quoted text |
codeBlock |
Code block |
bulletList |
Unordered list (contains listItem nodes) |
orderedList |
Ordered list (contains listItem nodes) |
listItem |
List item (contains paragraphs or other blocks) |
table |
Data table (contains tableRow nodes) |
tableRow |
Table row (contains tableCell or tableHeader nodes) |
tableCell |
Table cell |
tableHeader |
Table header cell |
Block nodes (with attrs):
| Node Type | Description | Required Attributes |
|---|---|---|
heading |
Heading block | level (integer 1–6) |
image |
Image embed | src (string). Optional: alt, title |
cardEmbed |
Embedded card/query | id (card reference path, see below). Optional: name |
smartLink |
Reference to another entity | entityId (entity reference path), model (string) |
Inline nodes:
| Node Type | Required Fields | Description |
|---|---|---|
text |
text (string) |
Inline text content. May have marks for formatting. |
hardBreak |
— | Line break within a paragraph |
The id attribute of a cardEmbed node is a serdes path — an array with exactly one entry containing model: Card and the card's entity_id:
- type: cardEmbed
attrs:
id:
- model: Card
id: h5F2EjHsRd73Dqqh8sAtd
name: My CardThe smartLink node uses the same path format for entityId, but the model can be any entity type (card, dashboard, collection, table, etc.).
name: Product Analysis Report
entity_id: dOc1PrOdAnAlYsIsRpTx2
creator_id: internal@metabase.com
document:
type: doc
content:
- type: heading
attrs:
level: 1
content:
- type: text
text: Product Analysis Report
- type: paragraph
content:
- type: text
text: "Overview of product performance metrics."
- type: cardEmbed
attrs:
id:
- model: Card
id: h5F2EjHsRd73Dqqh8sAtd
name: Basic Aggregations
- type: bulletList
content:
- type: listItem
content:
- type: paragraph
content:
- type: text
text: Revenue increased 15% quarter over quarter
- type: listItem
content:
- type: paragraph
content:
- type: text
text: Widget category remains the top performer
content_type: "application/json+vnd.prose-mirror"
collection_id: null
serdes/meta:
- id: dOc1PrOdAnAlYsIsRpTx2
label: product_analysis_report
model: DocumentA segment is a saved filter definition. Segments allow reusable filters that can be applied across multiple questions and dashboards.
Each segment holds a definition that specifies the source table and filter criteria. See MBQL Query for filter syntax.
Segments are stored under their table's directory: databases/{db_slug}/schemas/{schema_slug}/tables/{table_slug}/segments/.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Segment name |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
definition |
object | Yes | Filter definition with "lib/type": mbql/query, database, and stages containing source-table and filters |
serdes/meta |
array | Yes | Identity path with model: Segment |
table_id |
array | No | Table FK [database, schema, table]. Only included when not derivable from definition (e.g., empty or broken definition); re-derived from the definition on import when present. |
description |
string | No | Description |
archived |
boolean | No | Whether archived (default: false) |
created_at |
string | No | ISO 8601 timestamp |
name: Widget products
entity_id: aB3kLmN9pQrStUvWxYz1a
creator_id: internal@metabase.com
definition:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- PRODUCTS
filters:
- - =
- {}
- - field
- {}
- - Sample Database
- PUBLIC
- PRODUCTS
- CATEGORY
- Widget
serdes/meta:
- id: aB3kLmN9pQrStUvWxYz1a
label: widget_products
model: SegmentA measure is a saved aggregation definition. Measures allow reusable aggregations that can be applied across multiple questions and dashboards.
Each measure holds a definition that specifies the database and aggregation clause. See MBQL Query for aggregation syntax.
Measures are stored under their table's directory: databases/{db_slug}/schemas/{schema_slug}/tables/{table_slug}/measures/.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Measure name |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
definition |
object | Yes | Aggregation definition with "lib/type": mbql/query, database, and stages containing source-table and exactly one aggregation. Measures cannot use filters. |
serdes/meta |
array | Yes | Identity path with model: Measure |
table_id |
array | No | Table FK [database, schema, table]. Only included when not derivable from definition (e.g., empty or broken definition); re-derived from the definition on import when present. |
description |
string | No | Description |
archived |
boolean | No | Whether archived (default: false) |
created_at |
string | No | ISO 8601 timestamp |
name: Total revenue
entity_id: xK7mPqR2sT4uVwXyZ9a1b
creator_id: internal@metabase.com
definition:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- ORDERS
aggregation:
- - sum
- {}
- - field
- base-type: type/Float
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
serdes/meta:
- id: xK7mPqR2sT4uVwXyZ9a1b
label: total_revenue
model: MeasureA snippet is a reusable SQL fragment that can be referenced in native queries using {{snippet: Snippet Name}}. Snippets are stored under collections/snippets/, organized by snippet collection hierarchy.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Snippet name (used in {{snippet: Name}} references) |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
content |
string | Yes | SQL content of the snippet |
serdes/meta |
array | Yes | Identity path with model: NativeQuerySnippet |
description |
string | No | Description |
archived |
boolean | No | Whether archived (default: false) |
collection_id |
string | No | Collection FK (entity_id). Set this to place the snippet in a snippet collection; null/omitted = root snippet collection |
template_tags |
map | No | Template tag definitions (usually empty {}) |
created_at |
string | No | ISO 8601 timestamp |
name: Active Order Filter
entity_id: xK7mPqR2sT4uVwXyZ9a1b
creator_id: internal@metabase.com
content: "STATUS = 'active' AND TOTAL > 0"
description: Filter for active orders with positive totals
archived: false
collection_id: Y6d4QwJgGKw-X1tRh3ir2
template_tags: {}
serdes/meta:
- id: xK7mPqR2sT4uVwXyZ9a1b
label: active_order_filter
model: NativeQuerySnippetA transform generates a table in the database by running a query or Python script. Transforms allow materializing results as persistent database tables. Transform entities are stored under collections/transforms/. Transform jobs and tags are stored separately under the top-level transforms/ directory.
The source defines how data is produced — either an MBQL/native query (type: query) or a Python script (type: python). The target specifies where the resulting table is written.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Transform name |
entity_id |
string | Yes | NanoID identifier |
creator_id |
string | Yes | User FK (email) |
source_database_id |
string | Yes | Database FK (database name) |
source |
object | Yes | Source definition — query or Python (see below) |
target |
object | Yes | Target table: database (Database FK), type ("table"), schema, name |
serdes/meta |
array | Yes | Identity path with model: Transform |
description |
string | No | Description |
collection_id |
string | No | Collection FK (entity_id). Set this to place the transform in a collection; null/omitted = root collection |
tags |
array | No | Transform tags (see below) |
created_at |
string | No | ISO 8601 timestamp |
When source.type is query, the source wraps an MBQL or native query. See MBQL Query for query syntax.
source:
type: query
query:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- PRODUCTSWhen source.type is python, the source contains a Python script that receives source tables as pandas DataFrames and must return a DataFrame as the result.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | "python" |
body |
string | Yes | Python source code |
source-tables |
array | Yes | Source tables available to the script |
source-database |
string | No | Database FK (database name) |
source-incremental-strategy |
object | No | Incremental execution strategy |
Each entry in source-tables:
| Field | Type | Required | Description |
|---|---|---|---|
alias |
string | Yes | Variable name for the table in Python |
database_id |
string | Yes | Database FK (database name) |
schema |
string | No | Schema name |
table |
string | No | Table name |
table_id |
integer | No | Metabase table ID |
source:
type: python
body: |-
import pandas as pd
def transform(products):
return products.groupby('CATEGORY').agg(
count=('ID', 'count'),
avg_price=('PRICE', 'mean')
).reset_index()
source-tables:
- alias: products
database_id: Sample Database
schema: PUBLIC
table: PRODUCTS
source-database: Sample DatabasePython libraries (see PythonLibrary) are available as imports within the script.
Tags categorize transforms for scheduling and organization. Each tag association on a transform references a TransformTag by its entity_id:
tags:
- entity_id: TUtH6I5SqautNtUZoZ6Ti
position: 0
tag_id: hourlyhourlyhourlyxxx # entity_id of the TransformTag
serdes/meta:
- id: TUtH6I5SqautNtUZoZ6Ti
model: TransformTransformTagA transform tag is a label for categorizing transforms. Tags can be built-in (hourly, daily, weekly, monthly) or custom. Stored in transforms/transform_tags/.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID identifier |
name |
string | Yes | Tag name (e.g., "hourly", "custom-etl") |
serdes/meta |
array | Yes | Identity path with model: TransformTag |
built_in_type |
string | No | Built-in category: "hourly", "daily", "weekly", "monthly", or null for custom |
created_at |
string | No | ISO 8601 timestamp |
A transform job is a scheduled task that executes transforms matching specific tags. Stored in transforms/transform_jobs/.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID identifier |
name |
string | Yes | Job name (e.g., "Hourly job") |
schedule |
string | Yes | Cron expression (e.g., "0 0 * * * ? *") |
serdes/meta |
array | Yes | Identity path with model: TransformJob |
description |
string | No | Human-readable description |
built_in_type |
string | No | Built-in category: "hourly", "daily", "weekly", "monthly", or null for custom |
ui_display_type |
string | No | "cron/builder" or null |
job_tags |
array | No | Tag associations (see below) |
created_at |
string | No | ISO 8601 timestamp |
Each entry in job_tags connects the job to a TransformTag:
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID of this job-tag association |
tag_id |
string | Yes | TransformTag entity_id |
position |
integer | Yes | Ordering position |
serdes/meta |
array | Yes | Identity path with model: TransformJobTransformTag |
job_tags:
- entity_id: BPhRX8sTqcG5tZrXKeQuP
position: 0
tag_id: mXacguzCHQ5bBhqQPt3kd # entity_id of the "daily" tag
serdes/meta:
- id: BPhRX8sTqcG5tZrXKeQuP
model: TransformJobTransformTagA job can reference multiple tags. Transforms tagged with any of the job's tags will be executed when the job runs.
A shared Python source file available to transforms. Stored in python_libraries/.
| Field | Type | Required | Description |
|---|---|---|---|
entity_id |
string | Yes | NanoID identifier |
path |
string | Yes | Python file path (e.g., "common.py") |
source |
string | Yes | Python source code |
serdes/meta |
array | Yes | Identity path with model: PythonLibrary |
created_at |
string | No | ISO 8601 timestamp |
name: Product summary
entity_id: rT5vWxYz1aBcDeFgHiJkL
creator_id: internal@metabase.com
source_database_id: Sample Database
source:
type: query
query:
"lib/type": mbql/query
database: Sample Database
stages:
- "lib/type": mbql.stage/mbql
source-table:
- Sample Database
- PUBLIC
- PRODUCTS
target:
database: Sample Database
type: table
schema: PUBLIC
name: product_summary
collection_id: M-Q4pcV0qkiyJ0kiSWECl
serdes/meta:
- id: rT5vWxYz1aBcDeFgHiJkL
label: product_summary
model: Transform- 1.0.0: Initial release
- Entity key system (NanoID and foreign key references)
- Folder structure specification with namespace-based collection layout
- Collection, Card, Dashboard, Document, Segment, Measure, Snippet, Transform, TransformJob, TransformTag, PythonLibrary
See LICENSE file for details.