docs-mintlify/docs/data-modeling/dimensions.mdx
Dimensions represent attributes of individual rows in your data. They are
the fields you group by and filter on — things like status, city,
product_name, or created_at. Each dimension maps to a column or SQL
expression in your data source.
See the dimensions reference for the full list of parameters and configuration options.
</Note>A dimension specifies the SQL expression and its type:
<CodeGroup>cubes:
- name: orders
sql_table: orders
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: status
sql: status
type: string
- name: created_at
sql: created_at
type: time
cube(`orders`, {
sql_table: `orders`,
dimensions: {
id: { sql: `id`, type: `number`, primary_key: true },
status: { sql: `status`, type: `string` },
created_at: { sql: `created_at`, type: `time` }
}
})
| Data type in SQL | Dimension type in Cube |
|---|---|
timestamp, date, time | time |
text, varchar | string |
integer, bigint, decimal | number |
boolean | boolean |
Every cube that participates in joins should define a
primary_key dimension. Cube uses primary keys to avoid
fanouts — when rows get duplicated during joins and aggregates are
over-counted. Composite primary keys can be created by concatenating columns:
dimensions:
- name: composite_key
sql: "CONCAT({CUBE}.order_id, '-', {CUBE}.product_id)"
type: string
primary_key: true
Time dimensions are dimensions of the time type. They enable
grouping by time granularity (year, quarter, month, week, day, hour, minute,
second) and are essential for time-series analysis.
dimensions:
- name: created_at
sql: created_at
type: time
When queried, you can group by any built-in granularity without defining additional dimensions.
You can define custom granularities for time dimensions when the built-in ones don't fit — for example, weeks starting on Sunday or fiscal years:
<CodeGroup>cubes:
- name: orders
# ...
dimensions:
- name: created_at
sql: created_at
type: time
granularities:
- name: sunday_week
interval: 1 week
offset: -1 day
- name: fiscal_year
interval: 1 year
offset: 1 month
cube(`orders`, {
// ...
dimensions: {
created_at: {
sql: `created_at`,
type: `time`,
granularities: {
sunday_week: { interval: `1 week`, offset: `-1 day` },
fiscal_year: { interval: `1 year`, offset: `1 month` }
}
}
}
})
Time dimensions are essential for performance features like partitioned pre-aggregations and incremental refreshes.
<Note>See the following recipes:
Proxy dimensions reference dimensions from the same cube or other cubes, providing a way to reuse existing definitions and reduce code duplication.
Reference existing dimensions to build derived ones without duplicating SQL:
<CodeGroup>cubes:
- name: users
sql_table: users
dimensions:
- name: initials
sql: "SUBSTR(first_name, 1, 1)"
type: string
- name: last_name
sql: "UPPER(last_name)"
type: string
- name: full_name
sql: "{initials} || '. ' || {last_name}"
type: string
cube(`users`, {
sql_table: `users`,
dimensions: {
initials: { sql: `SUBSTR(first_name, 1, 1)`, type: `string` },
last_name: { sql: `UPPER(last_name)`, type: `string` },
full_name: { sql: `${initials} || '. ' || ${last_name}`, type: `string` }
}
})
If cubes are joined, you can bring a dimension from one cube into another. Cube generates the necessary joins automatically:
<CodeGroup>cubes:
- name: orders
sql_table: orders
joins:
- name: users
sql: "{CUBE}.user_id = {users.id}"
relationship: many_to_one
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: user_name
sql: "{users.name}"
type: string
cube(`orders`, {
sql_table: `orders`,
joins: {
users: {
sql: `${CUBE}.user_id = ${users.id}`,
relationship: `many_to_one`
}
},
dimensions: {
id: { sql: `id`, type: `number`, primary_key: true },
user_name: { sql: `${users.name}`, type: `string` }
}
})
When referencing a time dimension, you can specify a granularity to create a proxy dimension at that specific granularity — including custom granularities:
dimensions:
- name: created_at
sql: created_at
type: time
granularities:
- name: sunday_week
interval: 1 week
offset: -1 day
- name: created_at_year
sql: "{created_at.year}"
type: time
- name: created_at_sunday_week
sql: "{created_at.sunday_week}"
type: time
Subquery dimensions reference measures from other cubes, effectively turning an aggregate into a per-row value. This enables nested aggregations — for example, calculating the average of per-customer order counts.
<CodeGroup>cubes:
- name: orders
sql_table: orders
joins:
- name: users
sql: "{users}.id = {CUBE}.user_id"
relationship: many_to_one
dimensions:
- name: id
sql: id
type: number
primary_key: true
measures:
- name: count
type: count
- name: users
sql_table: users
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: name
sql: name
type: string
- name: order_count
sql: "{orders.count}"
type: number
sub_query: true
measures:
- name: avg_order_count
sql: "{order_count}"
type: avg
cube(`orders`, {
sql_table: `orders`,
joins: {
users: {
sql: `${users}.id = ${CUBE}.user_id`,
relationship: `many_to_one`
}
},
dimensions: {
id: { sql: `id`, type: `number`, primary_key: true }
},
measures: {
count: { type: `count` }
}
})
cube(`users`, {
sql_table: `users`,
dimensions: {
id: { sql: `id`, type: `number`, primary_key: true },
name: { sql: `name`, type: `string` },
order_count: {
sql: `${orders.count}`,
type: `number`,
sub_query: true
}
},
measures: {
avg_order_count: {
sql: `${order_count}`,
type: `avg`
}
}
})
The order_count subquery dimension computes the order count per user.
The avg_order_count measure then averages those per-user values. Cube
implements this as a correlated subquery via joins for optimal performance.
See the following recipes:
Dimensions can declare links — clickable navigation targets that supporting tools (such as Cube Cloud Workbooks) surface next to the dimension's values.
links is a list, so a single dimension can declare any number of
links — they all appear together in the cell menu. Each link points either to
an external URL (url) or to another Cube Cloud dashboard (dashboard,
a drill-in), and its URL is built per row from the dimension's data. The example
below declares two links on one dimension (an external search and a drill-in).
Dimension links require Cube v1.6.53 or newer.
links is a list of link objects. Each link accepts:
| Parameter | Required? | Description |
|---|---|---|
name | Required | Identifier, unique within the dimension. Also used in the synthetic dimension name (see Behavior). |
label | Required | The text shown for the link in the UI. |
url | Either url or dashboard | SQL expression that builds an external URL per row. May reference columns and other dimensions. |
dashboard | Either url or dashboard | The target Cube Cloud dashboard's slug (a drill-in). Each link sets exactly one of url or dashboard — never both — but different links on the same dimension can mix the two. |
icon | Optional | A Tabler icon name (see Icons). Defaults to a generic link icon. |
target | Optional | Where to open the link: blank (default — new tab) or self (same tab). Applies to external links only — drill-ins always navigate in-app (see Behavior). |
params | Optional | Extra per-row parameters. For url: they are appended as query parameters. For dashboard: they become equality filters on the target dashboard — each key is a member of the target dashboard's view (a cube path such as orders.status is auto-resolved to the matching view member), and value is the per-row value. |
cubes:
- name: orders
sql_table: orders
dimensions:
- name: status
sql: status
type: string
links:
# External link — opens a URL built from the row's value
- name: search
label: Search the web
url: "CONCAT('https://www.google.com/search?q=order+', {CUBE}.status)"
icon: brand-google
target: blank
# Drill-in link — opens another Cube Cloud dashboard, filtered by the row
- name: details
label: Open order details
dashboard: orders-detail # the target dashboard's slug
params:
- key: orders_view.status
value: "{CUBE}.status"
cube(`orders`, {
sql_table: `orders`,
dimensions: {
status: {
sql: `status`,
type: `string`,
links: [
{
name: `search`,
label: `Search the web`,
url: `CONCAT('https://www.google.com/search?q=order+', ${CUBE}.status)`,
icon: `brand-google`,
target: `blank`
},
{
name: `details`,
label: `Open order details`,
dashboard: `orders-detail`,
params: [{ key: `orders_view.status`, value: `${CUBE}.status` }]
}
]
}
}
})
icon accepts any name from the Tabler icon set — the
kebab-case name without any prefix, for example brand-google,
external-link, layout-dashboard, or send. Browse and search the available
names at tabler.io/icons. If icon is omitted, a default link
icon is shown.
A dashboard: link targets another dashboard by its slug — a short, stable,
human-readable identifier (e.g. orders-detail). The slug is portable across
environments: it is resolved within the current deployment, so the same model
works in development and production without hardcoding dashboard IDs.
To set a slug in Cube Cloud, open the target dashboard, open its options
sidebar, and fill the Slug field (see
Dashboards → Dashboard slug).
Slugs are unique per deployment, and the dashboard: value in your link must
match the slug exactly.
There is no required order. You can write the dashboard: slug in the model
first (it won't break anything — a link whose slug doesn't yet resolve is simply
skipped in the cell menu) and set the dashboard's slug later, or set the
dashboard slug first and reference it from the model afterwards. The link starts
working as soon as both sides use the same slug.
url (and its params) is evaluated for
every row, so the destination reflects the clicked cell. Internally a link
compiles to a hidden synthetic dimension named
<dimension>___link_<name>_url holding the resolved URL; it is added to the
query automatically and is never shown as a column.dashboard: link navigates in-app, in the same
tab (Cmd/Ctrl-click opens a new tab), ignoring target; a url: link opens
per its target (blank by default).dashboard: links, each params entry becomes an
equality filter on the target (the key is a view member, the value is
the per-row value). An unknown or inaccessible target slug is skipped
gracefully — the user is notified and no navigation happens.See the links reference for the canonical parameter
list.
Dimensions can be organized into hierarchies to define drill-down paths (e.g., Country → State → City):
cubes:
- name: users
# ...
dimensions:
- name: country
sql: country
type: string
- name: state
sql: state
type: string
- name: city
sql: city
type: string
hierarchies:
- name: location
levels:
- country
- state
- city