Variables¶
Variables describe the filters and inputs that live inside the dashboard shell. Each definition renders a labeled control, and the runtime keeps queries and charts in sync when values change. Variables must be explicitly referenced in query filters so behavior stays predictable.
Variable names must be unique within a file and across all imported files.
How Variables are Defined¶
Every entry under variables needs a name and a source (column, query, MetricFlow, or dbt). The input widget is optional — when omitted, the compiler auto-detects it from the other fields (e.g. options → select, min/max → slider, boolean default → checkbox). The compiler:
- derives a
title(label) andplaceholderfrom the slugified name unless you override them, - validates that the chosen
inputmatches the source type, - keeps
defaultundefined unless you explicitly set it.
Variables do not auto-apply themselves. Wire them in your query filters (plain names, Jinja expressions, helper functions) so you stay in control.
variables: region: column: orders.region input: select product_family: query: queries.product_family_list input: multiselect location: dimension: geography.region input: select profit_metric: measure: financial.profit input: select territory: model: analytics.orders column: territory input: select
Inputs and Type Mapping¶
The input field is optional. When omitted, the compiler auto-detects the widget from the variable's other fields (options → select, min/max/step → slider, boolean default → checkbox, list default → multiselect, hidden with no other signals → text). You can still set input explicitly to override auto-detection. The table below captures the available types; daterange is the default for date or timestamp sources so you can work with start/end values in expressions.
For a complete reference of all input types and their options, see UI Elements.
| Input | Description | Default Type |
|---|---|---|
select |
Single-choice dropdown | string |
multiselect |
Dropdown with multiple choices | array / string[] |
input / text |
Free-form text | string |
textarea |
Multi-line text | string |
slider |
Numeric slider (min, max, step required) |
number |
datepicker |
Single date picker | date (for rare single-date filters) |
daterange |
Date range picker | date / timestamp (preferred default) |
checkbox |
Boolean toggle | boolean |
radio |
Small set of explicit options | string |
Column Binding (most common)¶
column binds a variable to a table column (<tablename>.<columnname>). The compiler reads the column’s metadata (type, enums, nullability) to populate options, determine operators, and suggest placeholder text. Column bindings still require explicit wiring inside query filters.
variables: region: column: orders.region input: select
Add operator or default only if you need to override the default behavior.
Variables are optional by default; add required: true only when the dashboard
cannot render without a value.
required: true is a generic render-time contract, not a chart-specific
feature. Any query or chart that depends on that variable will fail to render
until the value is supplied, typically through a URL query parameter or a
variable default: in the face YAML.
Query-Powered Variables¶
query draws variable options from custom SQL. Pass either an existing query name (query: queries.regions) or inline SQL. The result must include at least one column for the value; additional label or description columns improve the UI.
variables: brand: query: | SELECT brand_id AS value, brand_name AS label FROM brands ORDER BY brand_name input: select
MetricFlow Integration¶
Use dimension or measure to reference MetricFlow entities. Dataface fetches type, values, descriptions, and roll-ups from the semantic layer so you rarely duplicate metadata.
variables: geography: dimension: geography.region input: select revenue_metric: measure: revenue.total input: select
dbt Integration¶
model + column refers to a dbt model column. The manifest resolves the model and metadata, giving you the same experience as a regular column.
variables: territory: model: analytics.orders column: territory input: select
Additional Options¶
default: initial value before user interactionplaceholder: override the auto-generated hintrequired: block rendering until a value is suppliedoptions:staticlist ordynamic_queryreference for select/multiselectmin/max/step: required for slidersoperator: controls how the value becomes a filter expression
When a required variable is missing, Dataface returns the normal structured
render error. The fix is to either open the board with the needed query
parameter, or define default: for that variable.
Wiring Variables to Queries¶
Variables must be referenced explicitly in query filters (plain name, Jinja expression, or helper function). The compiler turns those references into SQL filters, and the runtime refetches whenever a referenced variable changes.
When a variable is unset or null, filters should gracefully handle this by returning 1=1 (always true) so no filtering is applied. This is the default behavior when using the filter() helper macro.
queries: sales: sql: | SELECT * FROM orders WHERE {{ filter('region', '=', region) }} AND {{ filter_date_range('order_date', date_range) }} source: my_postgres
For simple filter references (non-SQL queries), you can use direct variable references:
queries: sales: filters: region: region date: "{{ date_range.start }} to {{ date_range.end }}"
For formatting, conditional logic, and helper macros, see Expressions.
Reactive Behavior¶
- Queries refresh when their referenced variables change.
- Charts redraw when their queries finish.
- Debouncing, optimistic loading, and errors are handled automatically.
Example: Column-bound Filter¶
By default, variables are unset (null) until a user selects a value. Use the filter() macro in SQL queries to handle this gracefully—when the variable is null, it returns 1=1 (no filter applied).
variables: region: column: orders.region input: select # No default - variable starts as null/unset queries: sales: sql: | SELECT region, SUM(revenue) as total_revenue FROM orders WHERE {{ filter('region', '=', region) }} GROUP BY region source: my_postgres
When region is null/unset, the filter returns 1=1 and all regions are shown. When a user selects a region, the filter becomes region = 'North' (or whatever was selected).
For more examples and details on the filter() macro, see Expressions.
Interactive Variables¶
Every rendered dashboard includes the current variable values as ordinary SVG text.
Browsers progressively enhance those values into interactive controls. If JavaScript
does not run or the SVG consumer does not support foreignObject, the read-only values
remain visible instead.
SVG, HTML, PNG, and PDF therefore share the same rendered dashboard. PNG and PDF conversion uses the read-only presentation, while browsers reveal the controls and allow users to change the values.
How It Works¶
- Each variable renders its current value as SVG text
- In a browser, each variable with an
inputtype becomes an HTML form control - When the user changes a value, the dashboard re-renders with the new variable value
- Variable values can be used in chart titles, queries, and conditional logic via Jinja templates
Supported Interactive Input Types¶
| Input | HTML Control | Description |
|---|---|---|
select |
<select> dropdown |
Single-choice selection |
text / input |
<input type="text"> |
Free-form text entry |
number |
<input type="number"> |
Numeric input with min/max/step |
slider / range |
<input type="range"> |
Slider with value display |
checkbox |
<input type="checkbox"> |
Boolean toggle |
date / datepicker |
<input type="date"> |
Date selection |
Example: Interactive Dashboard¶
title: Sales Dashboard variables: region: label: Region input: select options: static: [North, South, East, West] # No default - starts unset, shows all regions min_revenue: label: Min Revenue input: number min: 0 max: 10000 step: 100 # No default - starts unset, no minimum filter queries: sales: sql: | SELECT product, SUM(revenue) as revenue FROM orders WHERE {{ filter('region', '=', region) }} AND {{ filter('revenue', '>=', min_revenue) }} GROUP BY product source: my_postgres charts: sales_chart: title: "Sales{% if region %} for {{ region }} Region{% endif %}" type: bar query: sales x: product y: revenue rows: - sales_chart
Static Options¶
For interactive-only variables (not bound to a data source), use options.static. Note: variables don't need an "All" option—when unset, they simply don't apply any filter.
variables: category: input: select options: static: [Electronics, Accessories, Tools] # No default - starts unset, shows all categories queries: products: sql: | SELECT * FROM products WHERE {{ filter('category', '=', category) }} source: my_postgres
These variables don't need a column, query, or other source—the options are defined statically in the YAML. When the variable is unset (no selection), the filter() macro returns 1=1 and all categories are shown.
Using Variables in Templates¶
Variables can be used anywhere Jinja templates are supported:
- Chart titles:
title: "Sales for {{ region }}" - Query SQL:
WHERE region = '{{ region }}' - Conditional rendering:
visible: show_details
Built-in Directory Variables¶
Four variables are automatically injected into the Jinja render context for every
dashboard, scoped to the face file's own directory. They require no variables:
declaration and are always available in text: blocks and titles.
These are author-injected built-ins, distinct from the declared variables:
block covered above. They have no UI control and are computed at render time from
the face's location on disk.
| Name | Type | Description |
|---|---|---|
this_dir |
object | The directory containing the face file |
parent_dir |
object or None |
The parent directory; None at the project root |
siblings |
list | Entries in the face's own directory (one level) |
tree |
string | Pre-rendered markdown indented listing of the directory |
Object shapes¶
this_dir and parent_dir are plain objects with three fields:
| Field | Value |
|---|---|
name |
Directory name (e.g. reports) |
path |
Path relative to the project root (e.g. faces/reports) |
url |
Serve URL with trailing slash (e.g. /faces/reports/) |
siblings is a list; each entry has:
| Field | Value |
|---|---|
name |
File or directory name (e.g. summary.yml) |
url |
Serve URL (extension stripped for files; trailing slash for directories) |
is_dir |
true for directories, false for files |
ext |
File extension (.yml, .yaml, .md) or empty string for directories |
label |
Filename stem — use this for display (e.g. summary for summary.yml) |
tree is a pre-rendered markdown string of the directory tree, depth-bounded to
avoid pathological trees. Drop it directly into a text block or a code fence.
Example: landing page that lists its siblings¶
A dashboard at faces/reports/index.yml can list its neighbors:
title: Reports text: | ## {{ this_dir.name }} {% for s in siblings %} - [{{ s.label }}]({{ s.url }}) {% endfor %}
Or show the full tree:
title: Reports text: | {{ tree }}
Or link to the parent directory:
text: | {% if parent_dir %} ← [Back to {{ parent_dir.name }}]({{ parent_dir.url }}) {% endif %}
Scope and limits¶
- Variables are scoped to the face's own directory — there is no way to point at a different directory in v1 (deferred).
- The
labelfield uses the filename stem only. Reading each sibling's frontmattertitle:is not done at this stage because compiling every sibling is expensive. - The variable name set is closed:
this_dir,parent_dir,siblings,tree. Do not declarevariables:entries with these names — user-declared variables would shadow the built-ins (user wins, per Jinja merge order).
Advanced Topics¶
For nested dependencies, conditional filters, and layout orchestration driven by variables, see Advanced Variables.
Related¶
- UI Elements – Complete reference for all input types and options
- Expressions – Formatting, conditional logic, and the
filter()macro - Advanced Variables – Dependent variables and conditional layouts
- Queries – How variables plug into query filters
- Charts – Interactions that set variables or add filters