What a Macro Is
A macro is a reusable chunk of Jinja + SQL defined with {% macro macro_name(args) %} ... {% endmacro %} and saved in a .sql file inside the macros/ directory, callable from any model using {{ macro_name(args) }} just like a function call. Macros exist to eliminate copy-pasted SQL patterns — instead of writing the same CASE WHEN currency-conversion logic in fifteen different models, you write it once as a macro and call it everywhere it's needed.
Cricket analogy: This is like a bowling coach codifying a signature slower-ball technique once and teaching it to every bowler on the team, rather than each bowler independently reinventing their own version of the same delivery from scratch.
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name, decimal_places=2) %}
round({{ column_name }} / 100.0, {{ decimal_places }})
{% endmacro %}
-- usage in a model
select
order_id,
{{ cents_to_dollars('amount_cents') }} as amount_usd
from {{ ref('stg_orders') }}Built-in Macros and dbt_utils
dbt ships several built-in macros you use constantly without writing them yourself, like {{ dbt_utils.generate_surrogate_key([...]) }} from the popular dbt_utils package for hashing multiple columns into a single primary key, or {{ dbt_utils.date_spine(...) }} for generating a calendar table. Installing a community package like dbt_utils via packages.yml and running dbt deps gives your whole team access to dozens of battle-tested macros for common patterns like pivoting, unioning tables with different schemas, and testing, without anyone having to reinvent them.
Cricket analogy: This is like a cricket board adopting the internationally standardized DRS (Decision Review System) technology rather than every individual league inventing its own incompatible review process from scratch.
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: [">=1.1.0", "<2.0.0")]Run dbt deps after adding a package to packages.yml to install it into the dbt_packages/ directory — this must be re-run any time you change a package version or add a new dependency.
Macro Scope and Overriding Materializations
Macros can accept default argument values, call other macros, and even use {% set %} to define local variables, giving them real function-like behavior beyond simple text substitution — but unlike a model, a macro produces no table or view on its own; it only exists to be called from somewhere else. Advanced teams can even override built-in dbt materializations themselves, such as redefining the incremental materialization's default merge behavior, by creating a macro with the exact reserved name dbt expects, like default__incremental, inside their own project or a package.
Cricket analogy: This is like a franchise's specialist fielding-drill routine that only exists to be run during training sessions — it produces no match result on its own, but every actual match performance depends on players having internalized it.
Overriding a built-in materialization macro like default__incremental is powerful but risky — it changes behavior for every incremental model in the project (and any package that depends on it), so test thoroughly in a non-production environment before adopting it.
- A macro is reusable Jinja + SQL defined with {% macro %}...{% endmacro %} and called like a function.
- Macros eliminate copy-pasted SQL by centralizing common patterns in one place.
- dbt_utils and other community packages provide battle-tested macros installed via packages.yml and dbt deps.
- Macros support default argument values, local {% set %} variables, and calling other macros.
- A macro produces no table/view on its own — it only exists to be invoked from a model or another macro.
- Advanced teams can override built-in materialization macros, such as default__incremental, to change dbt's core behavior.
Practice what you learned
1. How is a macro defined in a dbt project?
2. What is the main purpose of writing a macro instead of duplicating SQL across models?
3. What must you run after adding a new package to packages.yml?
4. What does a macro produce when it is defined but never called from a model?
Was this page helpful?
You May Also Like
Jinja Templating in dbt
Learn how dbt uses the Jinja templating engine to add loops, conditionals, and variables to plain SQL, turning static queries into reusable, environment-aware code.
Materializations: View, Table, Incremental
Understand how dbt's view, table, and incremental materializations trade off build cost against query performance, and when to use each.
SQL Models Basics
Learn how dbt models turn a plain SQL SELECT statement into a managed table or view, and how to organize them into a clean, layered project structure.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
ProgrammingPowerShell Study Notes
Programming · 30 topics