When to use kpi
KPI is the right choice when the answer is a single number — revenue total, order count, conversion rate, error budget consumed. Add a delta or a secondary value to show direction or comparison alongside the headline. KPI is the typical "headline" of a dashboard: it does not emit clicks (there is no dimension to click on), but it does react to dashboard filters and cross-filter pills like every other viz — its underlying query re-runs with the active params and the headline number recomputes.
If you need a number per category instead of a single number, use bar. If you need a trend over time, use line. If you need many numbers in a tabular layout, use grid.
The type identifier is kpi — there is no number type; validation rejects it.
Mapping
The KPI reads the first row of the query result. Mapping fields name which columns of that row become which visual element:
mapping.value— required. The field whose value is the headline number.mapping.subtitle— string field for a label / period context line under the value.mapping.delta— numeric field shown as the change indicator. Sign drives the up/down arrow; the polarity setting controls the color.mapping.secondary_value— numeric field for a comparison value (e.g. "previous period") when there is nodelta. Map one or the other: when both are present the renderer shows the delta.mapping.secondary_label— caption forsecondary_value, written as a literal string (e.g."Invoices","Previous quarter"). It also accepts a field name: if the value matches a column in the result row, that column's value is used as the caption. Defaults to"Previous"when omitted.mapping.secondary_plain— plain-text field shown next to the headline when there is no delta or secondary value.mapping.comparison— numeric field driving thecomparisonblock below (period-over-period readings).
mapping:
value: revenue
subtitle: period_label
delta: revenue_delta_pct
The one-row rule means the Malloy side of a KPI is an aggregate:-only view. Producing the delta column is part of the query, not the viz — one working shape compares the window with the previous window of equal length:
# models/ec_performance.malloy (excerpt)
##! experimental.parameters
source: ec_performance() is ecommerce.sql("""
WITH windows AS (
SELECT
SUM(IF(created_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY),
sale_price, 0)) AS revenue,
SUM(IF(created_at < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND created_at >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 60 DAY),
sale_price, 0)) AS revenue_prev
FROM `bigquery-public-data.thelook_ecommerce.order_items`
)
SELECT
revenue,
'Last 30 days' AS period_label,
SAFE_DIVIDE(revenue - revenue_prev, revenue_prev) AS revenue_delta_pct
FROM windows
""") extend {
view: kpi is { select: * }
}
kpi block
KPI-specific options live under the top-level kpi block. KPI does not have a chart block.
kpi.comparison_label— caption shown beneath the delta (e.g."vs. last week"). When omitted, the renderer composes a default like "Up vs previous period" from the delta sign.kpi.delta_good_when—"increase"(default) or"decrease". Sets the polarity that colors the delta green vs red. Use"decrease"for metrics where lower is better — refunds, error rate, time-to-resolution.
comparison block
The top-level comparison block renders a period-over-period reading from the field named in mapping.comparison:
comparison.enabled— boolean.comparison.direction—"auto"derives the up/down arrow from the value's sign.comparison.period_label— caption for the comparison (e.g."vs budget to date").
mapping:
value: attainment_pct
comparison: attainment_pct
format:
value: "#,##0.00%"
comparison: "#,##0.00%"
comparison:
enabled: true
direction: auto
period_label: "vs budget to date"
Note the format entries above are slot-keyed (value, comparison) rather than field-keyed — both forms work, and slot keys read better when the same field fills two slots.
format
Number formatting is field-keyed. Per-field patterns win over the root pattern.
format.value— pattern for the headline number.format.delta— pattern for the delta indicator.format.secondary_value— pattern for the secondary comparison value.formatat the root — fallback when a field-specific entry is missing.
# headline as abbreviated currency, delta as percent
format:
revenue: "$#,##0a"
revenue_delta_pct: "#,##0.00%"
The full pattern grammar lives on the viz-types overview.
Cross-filter behavior
KPI participates in cross-filtering as a consumer only:
- Does not emit. Clicking a KPI does not add a pill — there is no dimension to click on.
- Does react. Pills set elsewhere on the dashboard, and any dashboard-level filter, become parameters on the next run, and the KPI's underlying query re-runs with them. The headline number recomputes accordingly.
If you want a "headline that always shows the dashboard-wide total" regardless of pills, write the underlying Malloy query so its parameter defaults ignore the pill values (e.g. accept the parameter but do not use it in the where-clause), or use two separate KPI items: one bound to a model that ignores the cross-filter parameter, one bound to a model that respects it.
Worked examples
Headline with delta and percent polarity favoring decrease (lower error rate is better):
id: ec_error_rate_kpi
title: Error Rate
query: "models/ec_quality.malloy::error_rate_kpi"
type: kpi
mapping:
value: error_rate
delta: error_rate_delta_pct
subtitle: period_label
format:
error_rate: "#,##0.00%"
error_rate_delta_pct: "#,##0.00%"
kpi:
comparison_label: "vs previous period"
delta_good_when: decrease
published: true
Headline with a secondary comparison value (no delta):
id: ec_revenue_vs_prev_kpi
title: Revenue
query: "models/ec_revenue.malloy::current_vs_previous"
type: kpi
mapping:
value: revenue_current
secondary_value: revenue_previous
secondary_label: "Previous quarter"
format:
revenue_current: "$#,##0a"
revenue_previous: "$#,##0a"
published: true
Headline with a plain-text annotation when the metric does not have a numeric comparison:
id: ec_top_brand_kpi
title: Top Brand
query: "models/ec_revenue.malloy::top_brand"
type: kpi
mapping:
value: brand_name
secondary_plain: market_share_label
published: true