ServicesAboutNotesContact Get in touch →
EN FR
Note

dbt Macro Deprecation Pattern

How to change macro behavior without breaking callers — the staged deprecation pattern using exceptions.warn() that dbt-utils demonstrates.

Planted
dbtdata engineeringdata modeling

When a macro’s behavior needs to change — different rounding, a renamed parameter, a new approach — and it’s called across many models, updating all call sites at once creates risk. The staged deprecation pattern, used by dbt-utils, handles this by keeping the old macro functional while signaling migration.

The Pattern

-- The new macro with improved behavior
{% macro cents_to_dollars_v2(column_name, scale=2, round_mode='half_up') %}
{# New version with configurable rounding mode #}
ROUND({{ column_name }} / 100.0, {{ scale }})
-- (round_mode handling depends on warehouse)
{% endmacro %}
-- The old macro, preserved but loudly deprecated
{% macro cents_to_dollars(column_name, scale=2) %}
{{ exceptions.warn("cents_to_dollars is deprecated. Use cents_to_dollars_v2 instead.") }}
ROUND({{ column_name }} / 100.0, {{ scale }})
{% endmacro %}

The four-step sequence:

  1. Create a new macro with the improved behavior and a clear name
  2. Keep the old macro but add a deprecation warning using exceptions.warn()
  3. Document the migration path in your changelog or README
  4. Remove the old macro in the next major version or after all callers are updated

The warning appears in dbt’s compilation output every time the deprecated macro is called. Developers see it during dbt run, during CI, during development builds. It’s impossible to miss if you’re watching your logs. This creates a natural forcing function for migration without breaking anything immediately.

When This Formality Is Worth It

For internal projects with small teams, you usually don’t need this. If your team is three people and you can update all call sites in one PR, just change the macro and fix everything at once. The overhead of maintaining two versions of a macro for a week isn’t worth it.

The deprecation pattern earns its complexity when:

  • The macro is used across many models (say, ten or more)
  • Different team members or teams own different models
  • The macro is part of a dbt package used by other projects
  • The change is breaking enough that testing all downstream models in one PR is genuinely risky

The dbt-utils package must use this pattern because they can’t know who’s calling their macros or when those callers will update. Internal projects have more control, so they can apply judgment about when a coordinated update is simpler.

Coordinated Updates vs. Staged Deprecation

The alternative to staged deprecation is coordinated update: one PR that changes the macro and updates every call site simultaneously. This is often the right call.

Coordinated update works well when:

  • You can identify all call sites with a grep
  • The change is mechanical (rename a parameter, not restructure the logic)
  • You have a test suite that will catch regressions
  • The team is small enough to review one big change safely

Staged deprecation works better when:

  • Call sites are owned by different teams who need time to update on their own schedule
  • The new behavior has different semantics and callers need to make conscious decisions about whether to adopt it
  • You’re publishing a package and can’t coordinate updates

Communicating Changes

The biggest source of friction in macro changes is surprise, not the change itself. Communicate changes proactively — a Slack message, PR description, or changelog entry — even when using exceptions.warn(). This prevents the “why is my model suddenly producing warnings?” confusion during unrelated deployments.

For packages, a CHANGELOG.md with a “Breaking Changes” section in each release is the standard. For internal projects, a note in the PR description that links to the affected models is usually enough.

What Not to Do

Don’t silently change behavior. A macro that produced ROUND(x / 100.0, 2) that now produces FLOOR(x / 100.0 * 100) / 100 changes rounding behavior for edge cases. If you make this change without signaling it, finance discovers the difference when they reconcile month-end numbers.

Don’t use version numbers in production macro names indefinitely. cents_to_dollars_v2 is appropriate during migration. Once all callers have updated and cents_to_dollars is removed, rename v2 to the canonical name. Version suffixes in production names are a sign that migration got stuck.

Don’t deprecate and wait indefinitely. If the old macro has a warning and nothing’s being done about it, the warning loses signal value. Set a timeline: “we’ll remove the old macro in six weeks.” Put it on the calendar.