A month without sales should show 0, not a gap in the chart, and a region without returns should show a 0% return rate, not an empty cell. The Semantic Layer has two settings for this, and they solve different halves of the problem.

Copy and paste

    metrics:
      - name: revenue
        type: simple
        agg: sum
        expr: amount
        fill_nulls_with: 0          # empty values become 0
        join_to_timespine: true     # a row for every period, with or without orders

A version that also works on dbt Core 1.12:

metrics:
  - name: revenue_or_zero
    type: derived
    expr: "coalesce(revenue_spine, 0)"
    input_metrics:
      - name: revenue_spine          # a simple metric with join_to_timespine: true

What each setting does

The orders have no sales in August 2025. Queried by month from July to September:

Month no settings fill_nulls_with: 0 join_to_timespine: true both
2025-07 80 80 80 80
2025-08 (no row) (no row) (empty) 0
2025-09 120 120 120 120

fill_nulls_with alone is useful when the row exists for another reason: in a query of order_count and returned_orders by month, every month has orders, and returned_orders shows 0 instead of an empty cell in the months without returns. A ratio built on a filled numerator then returns 0 too.

dbt Core 1.12 drops fill_nulls_with

With the latest YAML spec, dbt Core 1.12.6 parses fill_nulls_with without complaint and then leaves it out of semantic_manifest.json, where MetricFlow reads it. In the manifest every metric showed "fill_nulls_with": null, and no query filled anything. join_to_timespine was kept.

The same project parsed with dbt v2 2.0.9 kept fill_nulls_with: 0 on the simple metrics, and MetricFlow then returned the zeros in the table above. So:

python -c "import json; m=json.load(open('target/semantic_manifest.json')); \
print([(x['name'], x['type_params']['fill_nulls_with']) for x in m['metrics']])"

Derived, ratio and conversion metrics

Zero or nothing

A filled zero claims that the value is known to be zero. That is right for sales in a month with no orders, and wrong for a day the pipeline failed to load. When missing data is possible, keep the gap, or fill it only in metrics where an absence of rows really means none.

Tested with dbt Core 1.12.6, dbt v2 2.0.9 and MetricFlow 0.213 on DuckDB.

References