Skip to main content

dbt Integration

dfmt has a native Jinja evaluator and dbt compiler -- it does not shell out to dbt compile. This makes it fast and dependency-free.

How It Works

When dfmt detects a dbt project (by finding dbt_project.yml), it:

  1. Loads the project: reads dbt_project.yml, discovers models and macros, resolves packages
  2. For each model template:
    • Parses Jinja into a template AST
    • Evaluates the template with a dbt-compatible context (ref(), source(), config(), var(), macros)
    • Parses the compiled SQL into a full SQL AST
    • Restores Jinja constructs by matching source positions -- {{ ref('users') }} appears in the output exactly as written
    • Formats the SQL while preserving all Jinja

What Gets Preserved

All Jinja constructs are preserved verbatim in the formatted output:

ConstructExamplePreserved?
Expressions{{ ref('model') }}Yes
Filters{{ col | upper }}Yes
Config blocks{{ config(materialized='table') }}Yes
If blocks{% if is_incremental() %}...{% endif %}Yes
For loops{% for col in columns %}...{% endfor %}Yes
Set blocks{% set query %}...{% endset %}Yes
Macro calls{{ dbt_utils.star(ref('model')) }}Yes
Cross-token{{ schema }}.{{ table }}Yes

dbt Features Supported

  • ref() and source() resolution
  • config() with all standard parameters
  • var() with defaults
  • is_incremental() detection
  • Custom macros from project and packages
  • Adapter-specific macros (dispatch)
  • {% materialization %} blocks
  • Package macros (dbt_utils, dbt_expectations, etc. -- requires dbt deps)

Configuration

.dfmt.json in a dbt project

Place .dfmt.json at your dbt project root (next to dbt_project.yml):

{
"dialect": "snowflake",
"keywordCase": "lower",
"maxLineWidth": 100,
"include": ["models/**/*.sql"],
"exclude": ["models/staging/legacy/**"]
}

The dialect can be omitted -- dfmt infers it from the dbt adapter type in profiles.yml or dbt_project.yml.

Formatting a single model

dfmt --model dim_customers --write

Formatting only changed models

dfmt --changed-since main --write

Skipping dbt Detection

If you want to format .sql files in a dbt project as plain SQL (ignoring Jinja):

dfmt --no-jinja --write

Package Dependencies

If your dbt project uses packages (packages.yml), run dbt deps first so the package macros are available. dfmt reads macros from the dbt_packages/ directory.

Limitations

Some Jinja patterns are not fully supported:

  • Runtime-dependent loops: {% for %} loops where the iteration count depends on runtime data may not format optimally
  • Complex nested conditionals: Deeply nested {% if %} blocks (3+ levels) may fall back to verbatim output
  • Custom materializations: Supported but may produce warnings
  • env_var(): References to environment variables use dummy values during formatting (the formatted SQL is the same regardless)

When dfmt encounters a pattern it can't handle, it returns the template verbatim (unmodified) and reports a warning. It never produces incorrect output.