Global filters
A dashboard is most useful when the viewer can re-scope it without editing SQL — "show me this for the West region", "show me this for last quarter". Global filters are the controls at the top of a dashboard that do that. They're cross-tile: change a filter, every tile that references it re-runs.
Filter types
| Type | Input | Use for |
|---|---|---|
select | Single-choice dropdown | Region, status, channel — pick one |
multiSelect | Multi-choice dropdown | Several regions, several products — pick any combination |
dateRange | Two date pickers | Time-bounded queries |
date | Single date picker | Snapshot date |
text | Free-text input | LIKE search by name, ad-hoc filters |
Adding a filter
Open the dashboard in edit mode and click the Filters panel.
For each filter, configure:
- ID — the symbol you'll reference in tile SQL (e.g.
region,start_date). - Label — human-readable name shown above the input.
- Type — one of the table above.
- Default value — what's selected on first load.
- Options source (for
select/multiSelect):- Static — comma-separated list, or one-per-line. Best when the list is short and stable.
- SQL — a query returning rows of
(value, label). Best when options come from data and might change over time. Example:SELECT DISTINCT region AS value, region AS label
FROM customers
ORDER BY region;
Using the filter in tile SQL
Reference filters in SQL with ${filter_id}. QRY substitutes the current value at query time.
select and text — single value
SELECT *
FROM orders
WHERE region = '${region}';
If the user hasn't picked a value (and there's no default), the substitution is NULL-equivalent — guard with COALESCE or treat empty as "no filter":
WHERE ('${region}' = '' OR region = '${region}')
multiSelect — comma-separated list
SELECT *
FROM orders
WHERE region IN (${regions});
The ${regions} expands to a quoted list ('EMEA','NA','APAC'). Empty selection: same trick — (${regions}) IS NULL OR region IN (${regions}).
dateRange — two values
SELECT *
FROM orders
WHERE order_date BETWEEN '${start_date}' AND '${end_date}';
A dateRange filter exposes two ids, one with _start and one with _end suffix (or whatever you named them).
date — single value
SELECT *
FROM customers
WHERE snapshot_date = '${snapshot_date}';
Per-tile opt-out
Not every tile needs every filter. Reference only the filters you want; tiles that don't reference ${region} ignore changes to the region filter.
For complete opt-out (e.g. a header KPI that always shows the unfiltered total) just write the SQL without any ${...} substitution.
Gotchas
- The id is case-sensitive.
${Region}and${region}are different filters; if you copy a filter, double-check you don't have leftover references to the old id. - SQL injection isn't possible via filter values — QRY parameterises substitution server-side, not via raw string interpolation. But the surrounding SQL is your responsibility.
- Empty
multiSelectreturns no rows with a literalIN (). Handle with the empty-guard trick above. dateRangedefaults respect the tenant's timezone — if your data uses UTC dates and your tenant defaults to Europe/Madrid, off-by-one issues are easy. Be explicit.
Common issues
A filter's options dropdown is empty. Static: you didn't enter values. SQL: the source query returned no rows, or it errored. Test the source query in a chat / SQL cell first.
${filter_id} shows up literally in the tile output.
The id doesn't match exactly. Open the Filters panel, copy the id, paste — typos and case mismatches are the usual cause.
Tiles re-run on every keystroke of a text filter.
By default text filters trigger on debounced change (~400ms). For very expensive queries, consider a select from a finite list instead.
Default values aren't loading on first open. Defaults respect the user's last-used filter values via localStorage. If you switched defaults but stale localStorage is overriding them, hard refresh.
See also
- Creating a dashboard — pre-requisite walkthrough.
- Tile types — what each tile is good for.
- Dashboards reference — full feature reference.