Docs / Build Workflow

Filter — select

When to use select

Use select when the user needs to pick one value from a known set: a status, a region, a brand, a category. The dropdown reflects either a static option list declared in YAML or a dynamic list resolved by running a Malloy query at page load.

Reach for one of the date filters instead when the input is a date or a date range. Reach for cross-filtering when the user is meant to drill in by clicking a chart rather than picking from a dropdown.

Required fields

  • type: select

Either options or options_query must be present (and one of them only).

Optional fields

  • id — internal identifier; also the default parameter name when param is not set.
  • label — display label above the dropdown.
  • param — the Malloy parameter name to bind the selected value to. Defaults to id.
  • default — initial selected value. Must match the id of one of the options.
  • options — array of {id, label} objects. Use this for a static option list.
  • options_query — string in the format models/<file>.malloy::<view_name> (same shape as a viz query reference). Looky runs this query at page load and turns the rows into options.

Static options

Declare the list inline. Best for small, stable enumerations — statuses, on/off toggles, periodicities.

filters:
  - type: select
    id: status
    label: Status
    param: status
    default: all
    options:
      - { id: all,        label: All }
      - { id: active,     label: Active }
      - { id: cancelled,  label: Cancelled }
      - { id: refunded,   label: Refunded }

Each option must have an id (the value sent to the query) and a label (the text shown in the dropdown). The id is what your Malloy model receives — design the model parameter to accept the same shape.

Dynamic options (options_query)

Use when the option list comes from data — brands, customers, countries, anything that changes over time. The options_query runs at page load, so keep it a light aggregation (a group_by over the dimension), or cache it with a sidecar so the dropdown opens instantly.

filters:
  - type: select
    id: brand
    label: Brand
    param: brand
    options_query: "models/ec_revenue.malloy::brand_options"
    default: all

The query brand_options must return rows with id and label columns (an optional sort_order column controls ordering). A complete options model:

##! experimental.parameters

# models/ec_brand_options.malloy — feeds the Brand dropdown.
source: ec_brand_options() is ecommerce.table('bigquery-public-data.thelook_ecommerce.products') extend {
  view: brand_options is {
    group_by:
      id is brand
      label is brand
    order_by: brand asc
  }
}

To include an "all" sentinel in a dynamic list (recommended as the default), build the list in a raw-SQL source and union the sentinel row in — the sort_order column pins it to the top:

##! experimental.parameters

source: ec_brand_options() is ecommerce.sql("""
  SELECT 'all' AS id, 'All brands' AS label, 0 AS sort_order
  UNION ALL
  SELECT brand AS id, brand AS label, 1 AS sort_order
  FROM `bigquery-public-data.thelook_ecommerce.products`
  WHERE brand IS NOT NULL
  GROUP BY 1, 2
""") extend {
  view: brand_options is {
    select: id, label, sort_order
    order_by: sort_order asc, label asc
  }
}

Each row of the query becomes one option. Looky normalises every row to an {id, label}:

  • id — the id column (legacy aliases indicator_code / group_code are also recognised).
  • label — the label column (aliases indicator_label / group_label). Falls back to the id when empty.
  • sort key — if a row has sort_order (aliases indicator_order / group_order), options are ordered numerically by that field; otherwise they are ordered alphabetically by label.

Rows missing both an id and a label are dropped.

Default value

If default is set, it is the value of the parameter when the page loads. The reset button restores this value. If no default is set, the parameter is unset until the user picks a value, and the underlying query must accept the parameter's absence (typically by declaring its own default).

For dynamic option lists, declaring an "all" sentinel option and using it as the default is a common pattern — your Malloy model treats "all" as "no filter". Include the sentinel in the option list — add it to the query result, or declare it in options alongside options_query.

How the value reaches the Malloy query

The chosen option's id is sent as the named parameter — by default named after the filter's id, or param if you set it. The Malloy model declares a parameter with the matching name (after the p_ prefix is stripped — see Malloy support) and uses it in the query. Matching is exact, including case — when the data is mixed-case, normalise both sides in the model (lower(status) = lower(p_status)).

# in the Malloy model
##! experimental.parameters

source: ec_orders(
  p_status::string is "all"
) is ecommerce.table('bigquery-public-data.thelook_ecommerce.order_items') extend {
  view: detail is {
    where:
      p_status = "all" or status = p_status
    select:
      *
  }
}

Adapter differences

select values are typically strings or short identifiers; all three adapters bind them identically. The only edge case is when the parameter on the model side is declared as a date or timestamp — that pattern follows the rules at Source adapter differences.

Worked examples

Static enumeration with an "all" sentinel:

filters:
  - type: select
    id: status
    label: Status
    param: status
    default: all
    options:
      - { id: all,        label: All }
      - { id: active,     label: Active }
      - { id: cancelled,  label: Cancelled }

Dynamic from a query, with a custom parameter binding — the filter's id is free, and param names the model parameter's external name (brand binds to p_brand on the source signature):

filters:
  - type: select
    id: brand_filter
    label: Brand
    param: brand
    options_query: "models/ec_brand_options.malloy::brand_options"
    default: all

Multiple selects on the same dashboard, each filtering a different dimension:

filters:
  - type: select
    id: country
    label: Country
    options_query: "models/ec_revenue.malloy::country_options"
    default: all
  - type: select
    id: channel
    label: Channel
    options:
      - { id: all,      label: All channels }
      - { id: organic,  label: Organic }
      - { id: paid,     label: Paid }
      - { id: direct,   label: Direct }
    default: all