Skip to content

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. optionsselect, min/maxslider, boolean defaultcheckbox). The compiler:

  • derives a title (label) and placeholder from the slugified name unless you override them,
  • validates that the chosen input matches the source type,
  • keeps default undefined 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: orders.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/stepslider, 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

The table may be qualified when it does not live in the connection's default schema — column: gis.fact_sales.property_type, or column: warehouse.gis.fact_sales.property_type. Everything before the last dot is the table reference; the last segment is the column.

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 board 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. dbt charts 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 names a dbt model column. Option values are resolved from column exactly as a regular column variable is — the table half of the dotted reference — so the two forms behave the same.

variables:
  territory:
    model: analytics.orders
    column: orders.territory
    input: select

Additional Options

  • default: initial value before user interaction
  • placeholder: override the auto-generated hint
  • required: block rendering until a value is supplied
  • options: static list or dynamic_query reference for select/multiselect
  • min/max/step: required for sliders
  • operator: controls how the value becomes a filter expression

When a required variable is missing, dbt charts 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

Or reference a variable directly with no helper, when you don't need null-handling:

queries:
  sales:
    sql: |
      SELECT * FROM orders
      WHERE region = '{{ region }}'
    source: warehouse

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 states its current variable values as ordinary SVG text, and that is all a standalone artifact carries. Changing a value re-runs the queries behind it, so the controls themselves are served by a host that can do that: dct serve and dbt charts Cloud lay them over the dashboard's variables strip.

SVG, HTML, PNG, and PDF therefore share one rendered dashboard. A file you exported — an .svg, .png, .pdf, or a dct render .html — shows the filter state it was rendered with, and never a control that cannot filter.

How It Works

  1. Each variable is drawn into the dashboard as a labelled control showing its current value — a dropdown, a field, a checkbox, a slider — in a strip the dashboard measures and reserves exactly enough room for
  2. On dct serve or Cloud, a small runtime binds those controls: menus open, values commit, the dashboard re-renders. It adds behaviour, not elements — the controls are already in the board
  3. Everywhere else — a PNG export, a PDF, a plain SVG viewer — the same controls render as a picture of the dashboard's filter state
  4. Variable values can be used in chart titles, queries, and conditional logic via Jinja templates

Supported Interactive Input Types

Input Interaction Description
select Dropdown menu Single-choice selection
text / input Type in place Free-form text entry
number Type in place Numeric input with min/max/step
slider / range Click or arrow keys Slider with value display
checkbox Click or space Boolean toggle
date / datepicker Type in place 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 board 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 board's location on disk.

Name Type Description
this_dir object The directory containing the board file
parent_dir object or None The parent directory; None at the project root
siblings list Entries in the board'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. charts/reports)
url Serve URL with trailing slash (e.g. /charts/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 charts/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 board's own directory — there is no way to point at a different directory in v1 (deferred).
  • The label field uses the filename stem only. Reading each sibling's frontmatter title: 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 declare variables: 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.

  • 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