What is a dbt macro and how should you use it?

A dbt macro is a reusable, parameterized Jinja function that lets you generate SQL or YAML dynamically within a dbt project.

Sign up for the latest in SQL knowledge from the Galaxy Team!
Welcome to the Galaxy, Guardian!
Oops! Something went wrong while submitting the form.

Description

Table of Contents

What Is a dbt Macro?

A dbt macro is a reusable, parameter-driven Jinja function that dynamically generates SQL or YAML, enabling data teams to abstract repetitive logic, standardize patterns, and speed up development in dbt projects.

Why dbt Macros Matter

Modern analytics engineering relies on code reuse, consistency, and testability. As projects grow, so does the volume of boilerplate SQL—slowly eroding productivity and increasing the risk of logic drift. Macros tackle this pain by letting you write SQL that writes SQL. When you encapsulate complex joins, auditing columns, or naming conventions in a macro, every model that calls it inherits the exact same logic. That:

  • Reduces copy-paste errors
  • Simplifies large refactors (change the macro once, propagate everywhere)
  • Allows parameterization for edge cases without branching code
  • Enables advanced patterns like code generation, data-driven tests, and documentation scaffolding

How Macros Work Under the Hood

The Jinja Rendering Phase

dbt parses your project in two passes:

  1. Jinja rendering: Any file with a .sql, .yml, or .csv extension is treated as a Jinja template. Macros execute here, outputting raw SQL (or YAML).
  2. Database execution: dbt sends the rendered SQL to your warehouse.

Because macros run during rendering, they never touch the database directly; they simply produce the string that will later be executed.

Macro Anatomy

{% macro <package>.<macro_name>(param1, param2="default") %}
-- Jinja/SQL here
{% endmacro %}

Key parts:

  • Namespace: <package> lets you reference macros across packages (invoke_my_pkg.something()).
  • Arguments: Positional or keyword. Standard Python/Jinja rules apply (defaults, *args).
  • Return value: Whatever string is returned or printed becomes part of the rendered file.

Practical Examples

1. Dynamic Surrogate Keys

{% macro surrogate_key(cols) %}
sha2(concat({{ cols | join(", ") }}), 256)
{% endmacro %}

Usage inside a model:

select
{{ surrogate_key(["user_id", "account_id", "date" ]) }} as sk,
*
from {{ ref('fct_events') }}

Change the hashing function in the macro and every model immediately picks it up.

2. Reusable Column Audits

{% macro add_audit_columns(team) %}
, current_timestamp as {{ team }}_audit_ts
, '{{ team }}' as {{ team }}_audit_by
{% endmacro %}

Enforce uniform audit columns across 100+ models with a one-liner.

Authoring & Calling Macros

  1. Create a macros/ directory at your project root.
  2. Save the file with a descriptive name (hashing.sql, audits.sql).
  3. Use {{ my_macro(args) }} (double curly braces) within models, seeds, tests, or other macros.
  4. Run dbt run to render and execute.

Best Practices

Keep Business Logic Out

A macro should be infrastructure-oriented—queries, naming conventions, or utilities—not unique business rules. Test logic belongs in models or tests, where lineage is explicit.

Document via docs Blocks

Add inline documentation so developers can surface macro usage in dbt docs.

Fail Fast with exceptions.raise

Validate inputs to avoid silent failures:

{% if not cols %}
{{ exceptions.raise("surrogate_key macro requires at least one column") }}
{% endif %}

Version & Test Macros

Treat macros like production code—write unit tests using the dbt-unit-testing package or bespoke snapshots to confirm deterministic output.

Common Mistakes & How to Fix Them

Mistake 1 – Querying the Warehouse Inside a Macro

Why it’s wrong: Macros execute during compile time before any warehouse session exists, so database calls error out or return nothing.

Fix: Use the run_query() Jinja helper only when your macro is invoked in dbt run-operation or a post-hook.

Mistake 2 – Skipping Quoting Rules

Why it’s wrong: Hard-coding identifiers wreaks havoc on cross-database compatibility.

Fix: Use the built-in adapter.quote() or dbt_utils helpers like escape_single_quotes().

Mistake 3 – Hiding Complex Logic in a Single Macro Call

Why it’s wrong: Over-abstracting makes models unreadable to analysts who just want SQL.

Fix: Keep macros small and composable. Prefer multiple specialized macros over a monolith.

When to Reach for a Macro vs. a Model

If the pattern doesn’t produce a standalone dataset but rather shapes other SQL, choose a macro. For transformations with lineage, tests, or separate scheduling needs, stay with models.

Execution Patterns Beyond SQL Rendering

  • dbt run-operation: Call macros directly from the CLI for administrative tasks like backfills.
  • Hooks: Invoke macros before/after model runs for audit triggers or permissions.
  • Unit test generation: Write macros that iterate over YAML metadata to autogenerate tests.

Real-World Case Study

An e-commerce analytics team reduced their new-model onboarding time from ~3 hours to <1 hour by centralizing required joins and audit fields into macros. One update—adding GDPR pseudonymization—was implemented in 10 minutes across 400 models by changing a single macro.

Using Macros in Tandem with Galaxy

Galaxy’s blazing-fast SQL editor and AI copilot make macro authoring smoother:

  • The context-aware autocomplete shows available macros and their parameters as you type.
  • AI copilot can generate boilerplate macro code or refactor repeated SQL into a macro.
  • Collections let teams endorse core macros so everyone uses the canonical version.

While dbt itself renders macros, Galaxy’s development environment eliminates friction when writing and reviewing them.

Conclusion

dbt macros let you scale analytics engineering by abstracting repetitive patterns into clean, parameterized functions. Follow best practices—keep them small, documented, and tested—to unlock significant productivity gains and governance consistency.

Why dbt Macro is important

As data teams adopt dbt at scale, repetitive SQL patterns become a maintenance headache. Macros introduce code reuse, consistency, and rapid refactoring—all critical for keeping analytics reliable and agile. They also underpin advanced patterns like dynamic documentation and automated test generation, making them a foundational skill for every analytics engineer.

dbt Macro Example Usage


In a dbt model, call a macro that adds audit columns:

```sql
select *
     
from 
```

dbt Macro Syntax



Common Mistakes

Frequently Asked Questions (FAQs)

When should I create a macro instead of a model?

Use a macro when you need to generate SQL that embeds inside other models—e.g., reusable column lists, dynamic filters, or audit columns. If the logic produces its own dataset with lineage or tests, choose a model.

Can macros query the database at compile time?

Not during a normal dbt run. Macros render before any warehouse session exists. For database interaction, use dbt run-operation or post-hook macros where connections are available.

How does Galaxy help with dbt macro development?

Galaxy’s context-aware autocomplete lists your project’s macros and parameters, while the AI copilot can refactor repeated SQL into macros. Collections let you share and endorse canonical macros across the team.

Are macros testable?

Yes. You can call macros via dbt run-operation or leverage community packages like dbt-unit-testing to assert deterministic outputs.

Want to learn about other SQL terms?

Trusted by top engineers on high-velocity teams
Aryeo Logo
Assort Health
Curri
Rubie Logo
Bauhealth Logo
Truvideo Logo
Welcome to the Galaxy, Guardian!
Oops! Something went wrong while submitting the form.