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 (typically by order_index or by descending value).
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. Default lets row order win; set to a known value if you want the renderer to reorder.
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 in at least one model on the dashboard. - 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
Common pitfalls
- Stages are in the wrong order. The funnel respects query-result order. Sort in the Malloy query to control sequence.
- Tiny last stages disappear. Set
chart.min_sizeto enforce a minimum visible width. - Percent mode produces unexpected numbers. Pick deliberately:
"first"for conversion cascades (each stage ÷ first stage),"total"for share of total (each stage ÷ sum),"none"for raw counts. - Stage labels overlap the bars. Move the labels outside (
chart.label.position: "right") or reducefont_size. - Clicking a stage does nothing. Funnel emits cross-filter pills only inside a dashboard, and only if (a)
chart.cross_filteris notfalse, and (b) the stage field is declared as a parameter in at least one model on the dashboard. Add the parameter declaration in the underlying Malloy model to enable the pill. - Multi-funnel comparison. Not supported in one viz; place two side-by-side funnels in a dashboard for A/B comparison.