When to use funnel
Funnel is the right choice for conversion flows, fulfillment pipelines, or any process where order between steps and drop-off matters — site visitor → product view → cart → checkout → purchase, or order created → packed → shipped → delivered. Each row of the query is one stage.
Use bar instead when the categories are unordered or when you do not care about cumulative drop-off. Use grid when the audience needs the exact step counts and conversion rates side by side.
Mapping
mapping.stage— required. String field whose values become the per-stage labels.mapping.value— required. Numeric field for the per-stage size.
mapping:
stage: stage
value: order_count
The order of stages in the chart follows the order of rows in the query result. Sort in the underlying Malloy query to control sequence — the standard shape is one SELECT … UNION ALL per stage with an explicit ordering column:
# models/ec_fulfillment_funnel.malloy — one row per stage, in order.
##! experimental.parameters
source: ec_fulfillment_funnel() is ecommerce.sql("""
SELECT 'Created' AS stage, COUNT(*) AS order_count, 1 AS stage_order
FROM `bigquery-public-data.thelook_ecommerce.orders`
UNION ALL
SELECT 'Shipped', COUNT(*), 2
FROM `bigquery-public-data.thelook_ecommerce.orders`
WHERE status IN ('Shipped', 'Complete')
UNION ALL
SELECT 'Complete', COUNT(*), 3
FROM `bigquery-public-data.thelook_ecommerce.orders`
WHERE status = 'Complete'
""") extend {
view: fulfillment_funnel is {
select: stage, order_count, stage_order
order_by: stage_order asc
}
}
chart shortcuts
The chart block is typed and closed.
chart.percent_mode—"first"(default),"total", or"none". Picks the base for the percent shown next to each stage value:- first — relative to the first stage (conversion cascade: 100% → 80% → 60% …).
- total — relative to the sum of all stages (share of the funnel).
- none — no percentages, raw values only.
chart.show_value_labels— boolean. Turns stage labels on. Style them withchart.labelbelow.chart.cross_filter— boolean, defaulttrue. Set tofalseto suppress click emission for this viz (it still consumes pills set elsewhere).chart.height— pixel height of the viz container.
Pass-through layout fields:
chart.left/chart.top/chart.bottom— distance from the corresponding edge of the chart container.chart.width— funnel horizontal width (string percent or pixel number).chart.gap— pixels between stages.chart.min_size— minimum stage width (e.g."10%"). The narrowest stage will not get smaller than this even if its value is tiny.chart.max_size— maximum stage width.chart.sort— stage sort order:"ascending","descending", or"none". Omit the key (or setnone) to let the query's row order win; the other two make the renderer reorder by value.
Stage labels
chart.label styles each stage's label when chart.show_value_labels is on:
position—"inside"(label sits on top of the stage),"left","right","top","bottom".rotate,color,font_size,font_weight.formatter— string template. Use{b}for the stage name,{c}for the value,{d}for the percent (whenpercent_modeis"first"or"total").distance,align,vertical_align,clip.
Legend & tooltip
chart.legend and chart.tooltip share the same shape as on bar. For a single-funnel viz the legend is usually redundant; set chart.legend.show: false.
format
format.yorformat[<value_field_name>]— pattern for stage value labels and tooltip values.formatat the root — fallback.
Cross-filter behavior
Funnel participates fully in cross-filtering. Inside a dashboard:
- Emits — clicking a stage adds a pill
{ field: <the stage field>, value: <clicked stage name> }, providedchart.cross_filter !== falseAND the field is declared as a parameter on a source signature in this viz's own model file. - Consumes — pills set elsewhere, and dashboard-level filters, become parameters on the next run; the funnel's underlying query re-runs and the stages recompute accordingly.
- Opt-out — set
chart.cross_filter: falseto suppress click emission while still consuming pills.
Worked examples
Standard conversion funnel with first-stage percent:
id: ec_fulfillment_funnel
title: Fulfillment Funnel
query: "models/ec_fulfillment.malloy::funnel"
type: funnel
mapping:
stage: stage
value: order_count
chart:
height: 360
width: "80%"
show_value_labels: true
label:
position: inside
formatter: "{b}: {c} ({d}%)"
percent_mode: first
legend:
show: false
format:
order_count: "#,##0"
published: true
Total-share funnel (each stage shown as % of the sum):
chart:
percent_mode: total
show_value_labels: true
Compact funnel embedded in a dashboard row alongside KPIs:
chart:
height: 240
width: "100%"
gap: 6
min_size: "20%"
max_size: "100%"
show_value_labels: true
label:
position: right
formatter: "{b}: {c}"
percent_mode: none
Design notes
- The funnel renders stages in query-result order — sort in the Malloy query to control the sequence.
chart.min_sizekeeps small final stages visible.- Pick the percent mode deliberately:
"first"for conversion cascades (each stage ÷ first),"total"for share of total,"none"for raw counts. - On tight funnels, move stage labels outside (
chart.label.position: "right") or reducefont_size. - For A/B comparison, place two funnels side by side in a dashboard — one funnel shows one sequence.