What a visualization YAML is for
A visualization file binds one Malloy query to one chart or table. Every transformation belongs in the model query; the visualization YAML is presentation only — chart type, which fields go where, formatting, optional emphasis. For how to shape that Malloy layer (dimensions, measures, views, modular files), see Models.
For the per-type reference (every chart.* property each viz type accepts), see Visualization Types and the page for each type underneath.
Required top-level fields
id— workspace-unique identifier. Stable over time: dashboards reference it, so renaming breaks dashboards.title— human-readable title shown in the dashboard.type— viz type discriminator. Must be one of the supported types listed in the viz-types index; anything else is rejected at validation time.query— Malloy query reference, exactly in the formatpath/to/file.malloy::query_name. The path is from the workspace root; both halves must exist or validation fails.
Other top-level fields
mapping— field-role assignments. Shape varies per viz type — see the per-type page.chart— typed config block for ECharts-based types (bar, line, pie, scatter, heatmap, funnel). Closed surface: only the keys listed in the per-type reference are accepted.kpi,grid,matrix— type-specific blocks for the DOM-based types (kpi, grid/table, report_matrix). Use these instead ofchart.pagination— for grid and report_matrix.filters— list of user-facing controls. See Filters.format— field-keyed number / date format patterns; field name → pattern string.emphasis— declarative highlight rule (per supported type — see the per-type pages).execution— query execution hints (timeout, concurrency, cache override). Permissive shape today.tags— free-form labels for search.published— boolean. Onlytruevisualizations appear in dashboards.
The query reference format
Every visualization points to a Malloy query with this exact format:
query: "models/ec_revenue.malloy::by_category"
The part before :: is the path to the .malloy file from the workspace root. The part after :: is the view or query name defined inside that file. Both must match exactly.
Local validation (looky validate) checks that the file exists and the reference uses the :: separator. Server-side validation additionally compiles the model and dry-runs the query; if the view name doesn't exist, or the model fails to compile, you get a clear error before push.
Worked examples
Minimal KPI:
id: ec_revenue_kpi
title: Revenue
query: "models/ec_revenue.malloy::kpi"
type: kpi
mapping:
value: revenue
delta: revenue_delta_pct
format:
revenue: "$#,##0a"
revenue_delta_pct: "#,##0.00%"
published: true
Bar with one series — note mapping.series[] (legacy mapping.y is no longer accepted on bar):
id: ec_revenue_by_category_bar
title: Revenue by Category
query: "models/ec_revenue.malloy::by_category"
type: bar
mapping:
x: category
series:
- field: revenue
label: Revenue
chart:
show_value_labels: true
value_label:
position: top
format:
revenue: "$#,##0.00"
published: true
Line with dual axis:
id: ec_revenue_over_time_line
title: Revenue Over Time
query: "models/ec_revenue.malloy::over_time"
type: line
mapping:
x: order_month
y: revenue
y2: order_count
series_label: Revenue
series_label_2: Orders
format:
revenue: "$#,##0"
order_count: "#,##0"
published: true
For every other shape (grouped bars, percent-stacked, donut pie, scatter, heatmap, funnel, grid with pagination, report_matrix with totals) see the per-type pages under Visualization Types.
Cross-filtering at a glance
Inside a dashboard, clicking a chart can add a "pill" that narrows every other viz on the page. Per-type emit behavior:
- Emit clicks: bar, line, pie, scatter, funnel, grid, report_matrix, heatmap (the latter requires
chart.cross_filter_emitset to"x"or"y"). - Don't emit but consume pills: kpi (no categorical click target by structure).
- Per-viz opt-out:
chart.cross_filter: falsefor ECharts-based vizs (bar, line, pie, scatter, heatmap, funnel);grid.cross_filter: falsefor grid;matrix.cross_filter: falsefor report_matrix. KPI doesn't emit by structure.
Full mechanism at Cross-filtering.
What validation actually checks (and what it does not)
Validation is two-pass — local then server-side. Per visualization, the checks are:
- Local: YAML parses;
id,title,type,queryare present and non-empty;queryuses the::separator; the model file exists; theidis unique across the workspace; thechartblock is schema-valid (every property is recognised, values are in their allowed range — errorVZ020). - Server-side: the underlying Malloy model compiles, sources are reachable, and the query dry-runs against the runtime engine (free on BigQuery, EXPLAIN-only on Postgres in
--strict).
Validation does not verify that every field in mapping exists in the query result, that every field in format appears in mapping, or that the chart "looks right". Those issues surface only at render time. Open the visualization detail page after push to catch them.
looky validate
looky list visualizations