Docs / Build Workflow

Models

Models are your semantic API

Put business logic in Malloy models, not in visualization YAML. If a metric is useful in more than one chart, define it once in the model layer and reuse it everywhere. Changing the definition of "revenue" should mean editing one line in one file, not hunting through visualization configs.

Minimal model you can ship today

source: sales is ecommerce.table('bigquery-public-data.thelook_ecommerce.order_items') extend {
  dimension: product is product_name
  dimension: order_date is created_at::date
  measure: sales_amount is sum(sale_price)
}

query: total_sales is sales -> {
  aggregate: sales_amount
}

query: sales_by_product is sales -> {
  group_by: product
  aggregate: sales_amount
  order_by: sales_amount desc
  limit: 10
}

The source alias (ecommerce) must match an alias defined in runtime/sources.runtime.yml. The query names (total_sales, sales_by_product) become the reference handles used in visualization YAML.

Views: the pattern used in ecommerce-showcase

For larger models, define named views inside the source using view: instead of top-level query: declarations. Views live inside the source and can reference its dimensions and measures directly.

source: ec_revenue is ecommerce.table('bigquery-public-data.thelook_ecommerce.order_items') extend {
  dimension: category is products.category
  dimension: order_month is created_at::month
  measure: revenue is sum(sale_price)
  measure: order_count is count(order_id)

  view: over_time is {
    group_by: order_month
    aggregate: revenue, order_count
    order_by: order_month asc
  }

  view: by_category is {
    group_by: category
    aggregate: revenue, order_count
    order_by: revenue desc
    limit: 12
  }
}

A visualization references a view exactly like a top-level query:

query: "models/ec_revenue.malloy::over_time"

Use views when all queries belong to the same semantic source. Use top-level queries when you need to reference multiple sources or run cross-source joins.

Parameters: wiring dashboard filters to model queries

Dashboard filters control queries through parameters. Declare the parameter in the model and the dashboard filter passes its value through at render time.

source: ec_orders is ecommerce.table('bigquery-public-data.thelook_ecommerce.order_items') extend {
  # declare a parameter with type and default
  declare:
    p_cutoff_date is @2024-01-01::date

  measure: revenue is sum(sale_price) ? created_at <= p_cutoff_date

  view: kpi is {
    aggregate: revenue
  }
}

The dashboard filter binds to the parameter by name:

# in the dashboard YAML
filters:
  - id: global_period
    type: cutoff_date
    granularity: year
    param: p_cutoff_date    # must match the parameter name in the model
    default: ""

When a user changes the filter, the parameter value is passed to every query in the dashboard that references it.

Reusing a base source across models

The ecommerce-showcase defines a parametric base source once and extends it in each domain model. This avoids duplicating join definitions, dimension declarations, and parameter declarations across every file.

# ec_orders_base.malloy — shared foundation
source: ec_orders_base is ecommerce.table('...order_items') extend {
  join_one: products is ecommerce.table('...products') on product_id = products.id
  measure: revenue is sum(sale_price)
  measure: order_count is count(order_id)
}

# ec_revenue.malloy — domain model extends the base
import "ec_orders_base.malloy"

source: ec_revenue is ec_orders_base extend {
  view: by_category is {
    group_by: products.category
    aggregate: revenue
    limit: 12
  }
}

Keep the base model stable. Add new views in domain-specific files, not in the base.

Model quality checklist

  • Source alias exists in runtime/sources.runtime.yml.
  • View and query names are stable — renaming them breaks visualization references.
  • Field names reflect business meaning, not chart formatting needs.
  • Parameters are declared with sensible defaults so queries work without a filter.
  • Each view or query can be reused by multiple visualizations.
looky validate
looky diff

Do not start visualization work until model validation is clean.