Have you worked in a highly mature dbt project team? What did you notice? For me, the one thing that hit me immediately was:

Consistency is calming.

As the dbt team matures, with rising number of models, macros, tests, collaborators and interns, maintaining a unified style isn't just a luxury - it is essential. In fact, according to the official dbt style guide, "Everybody on your team needs to have a unified style... Consistency is key".

Who is this for?
Hands on DBT developers

TL;DR

  • Why styling matters more than just aesthetics
  • Modeling with intention
  • Weaving logic gracefully with SQL and Jinja
  • YAML style and validation
  • Level up your YAMLs with Jinja
  • Python Styling and Validation
  • Automated style checks with dbt-checkpoint
  • Styling isn't an afterthought, its part of Strategy
  • [Subscriber Exclusive Bonus] Boilerplate Starter Project

🙋 Why styling matters more than just aesthetics?

A clean well-styled codebase reduces cognitive load, eases onboarding, improves code review quality, and bolsters intra and cross team collaboration. When we implement style guides and checks in our team, we're aiming for clarity, readability, and trust. We make every line of SQL, Python, YAML and Jinja feel intuitive and purposeful.

Consistent naming, formatting and structure makes your codebase:

  • Readable and easier to understand for collaborators, reviewers and your future self.
  • Maintainable and less buggy, with clearer intent.
  • Collaborative and pleasant, when onboarding is so much more smoother and reviewing pull requests are a breeze.
🤠
My opinion
I've worked with numerous dbt data teams in the past, and I can testify from my own personal experience that teams that have strong unified opinion on styling and code quality tend to have a higher trust and reputation within the organisation, than the ones without any opinions.

If a mature dbt team has got checks and automations at every step in their development lifecycle, their data products tend to break less, have clear documentation, are easier to use and encourage collaboration.

Having some code styling definitions is better than having none. At the least, I would recommend starting off with the dbt recommended style guides as the starting point. Once your team evolves, you can make modifications to this style guide as needed.

😇 Modeling with Intention

I believe and preach:

  • Readability over brevity
  • Saving brainpower for deeper logic rather than abbreviations
  • Every model should be self-documenting
  • Your model should mirror how we talk, not just how machines parse

In this section, we will talk through some of the styling conventions that you can implement in your dbt projects. One thing to note is that these styles are not set in stone, and can be adjusted based on your team's preference. The important thing like we discussed earlier is consistency.

Model and Column Naming

dbt advocates for snake_case names, sensible singular or plural forms, and avoiding abbreviations. They note that model names should be plural—customers, orders, products—and identifiers named <entity>_id, like account_id.

Boolean and timestamp fields should follow clear naming patterns: is_active, created_at, updated_date; always in UTC, or annotated (e.g., created_at_aedt).

By sticking to conventions, every model becomes self-documenting.

Structure and Whitespace

dbt Labs loves whitespace. They advise four-space indentation, explicit AS for aliases, lowercase everything, and keeping lines under 80 characters. Yes, that means avoiding cust or c as aliases—clarity rules.

Example:

-- models/customers.sql
select
  customer_id,
  first_name,
  last_name,
  created_at,
  is_active
from {{ ref('stg_customers') }}

Model Versions & Naming Granularity

Avoid dots in names, some data platforms treat dots as database/schema separators. For staging, intermediate, and marts: use names that convey transformation intent

  • e.g. stg_stripe__payments, int_payments_pivoted_to_orders, orders

Use suffixes like _v1, _v2 for versioning models when business logic changes significantly.

Tools to automate consistency

The best part? You don’t need manual upkeep. Tools like SQLFluff, with dbt-flavored linting, ensure style rules automatically. If you're using SQLFluff, make sure you customise the .sqlfluff configuration file to your needs. A sample configuration is shown below for you to copy paste.

[sqlfluff]
dialect = postgres
templater = dbt
runaway_limit = 10
max_line_length = 80
indent_unit = space
sql_file_exts = .sql

[sqlfluff:indentation]
tab_space_size = 4

[sqlfluff:layout:type:comma]
spacing_before = touch
line_position = trailing

[sqlfluff:rules:capitalisation.keywords] 
capitalisation_policy = lower

[sqlfluff:rules:aliasing.table]
aliasing = explicit

[sqlfluff:rules:aliasing.column]
aliasing = explicit

[sqlfluff:rules:aliasing.expression]
allow_scalar = False

[sqlfluff:rules:capitalisation.identifiers]
extended_capitalisation_policy = lower

[sqlfluff:rules:capitalisation.functions]
capitalisation_policy = lower

[sqlfluff:rules:capitalisation.literals]
capitalisation_policy = lower

[sqlfluff:rules:ambiguous.column_references]  # Number in group by
group_by_and_order_by_style = implicit

# If using the dbt templater, we recommend setting the project dir.
[sqlfluff:templater:dbt]
project_dir = /usr/local/airflow/dbt/hello_world/
profiles_dir = /usr/local/airflow/dbt/hello_world/.dbt/
profile = default
target = dev

[sqlfluff:templater:jinja]
apply_dbt_builtins = True

A sample .sqlfluff configuration.

You can also exclude files and directories from linting using a standard .sqlfluffignore file. A sample configuration is shown below.

# Comments start with a hash.

# Ignore unnecessary dbt folders
target/
dbt_packages/
macros/

# Ignore unnecessary airflow folders
.astro/
dags/

A sample .sqlfluffignore configuration.

You can install sqlfluff by running this command:

pip install sqlfluff

Installing sqlfluff

Now you will be able to run the lint and fix commands from sqlfluff like shown below:

sqlfluff lint .
sqlfluff lint path/to/my/sqlfiles

Running sqlfluff lint command to check for linting errors.

sqlfluff fix .
sqlfluff fix path/to/my/sqlfiles

Running sqlfluff fix to fix the lint errors.

👩‍💻
Boilerplate Repo Attached!
We've got a boilerplate repo you can simply clone and use, with all the recommendations mentioned here, at the end of this post. Don't miss it!

You can read more about how dbt Labs styles their models here. You can read more about SQLFluff here.

🧵 Weaving logic gracefully with SQL and Jinja

Jinja lets dbt feel dynamic: configs, variables, macros - all in your SQL. Styling this layer requires restraint and clarity. You can go overboard with Jinja, and make your code unreadable. The key is in the balance, use it only where it helps keep your code DRY and readable at the same time.

Config blocks at the top

Inline config() macros keep execution context visible:

{{ config(materialized='table', tags=['core', 'orders']) }}

select
    o.order_id,
    o.order_date
from {{ ref('raw_orders') }} as o

This keeps intentions obvious—no hunting through dbt_project.yml.

Dynamic queries with macros

Macros are handy for DRY patterns, like auto-generating CTEs with similar names:

{% set email_companies_with_filters = [
  { "name": "google", "filter": "name like '%John%'" },
  { "name": "yahoo", "filter": "name like '%Jill%'" },
  { "name": "microsoft", "filter": "name like '%Doe%'" }
] %}

with
{% for company in email_companies_with_filters %}
  {{ company.name }}_cte as (
    select * from {{ ref('dynamic_case') }}
    where {{ company.filter }}
  ){% if not loop.last %},{% endif %}
{% endfor %}

select * from microsoft_cte

This reads like a story—dynamic, yet maintainable. We've covered writing Jinja patterns in detail, in a previous edition linked below:

8 Powerful Jinja Loop Patterns for dbt
Discover how to supercharge your dbt models using Jinja loops! This guide explores practical loop patterns for clean and DRY SQL transformations.

Favor Jinja in SQL over YAML

YAML files can’t yet use custom macros, though there’s a feature request. If you need environment-specific logic, put it in .sql with Jinja, not .yml.

👻
Did you know?
You can use Jinja comments ({# #}) for comments that should not be included in the compiled SQL.

You can read more about how dbt Labs styles their SQL and their recommendations here.

🐪 YAML Style and Validation

YAML drives core metadata: freshness, descriptions, tags. Modeling teams should treat .yaml with care:

  • Two-space indent;
  • Lists aligned;
  • Strings quoted if ambiguous;
  • Use Prettier for automated style.

Integrate your IDE with dbt JSON schema to get live validation in your IDE, and we recommend using a YAML formatter like Prettier to format them automagically.

Setting up dbt JSON Schema

  1. Install the VSCode-YAML extension
  2. Inside of your dbt project's directory, create a .vscode/settings.json file containing the following data. This is what tells the extension which schema to associate with each file.
{    
    "yaml.schemas": {
        "https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/dbt_yml_files-latest.json": [
            "/**/*.yml",
            "!profiles.yml",
            "!dbt_project.yml",
            "!packages.yml",
            "!selectors.yml",
            "!profile_template.yml",
            "!package-lock.yml"
        ],
        "https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/dbt_project-latest.json": [
            "dbt_project.yml"
        ],
        "https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/selectors-latest.json": [
            "selectors.yml"
        ],
        "https://raw.githubusercontent.com/dbt-labs/dbt-jsonschema/main/schemas/latest/packages-latest.json": [
            "packages.yml"
        ]
    },
}
  1. To prompt other users to install the YAML extension, create a .vscode/extensions.json file containing the following data inside of your dbt project's directory:
{
    "recommendations": [
        "redhat.vscode-yaml"
    ]
}

Setting up Prettier

You can install prettier by running:

npm install --global prettier

Add a .prettierrc file in your root to let your editor know that your are using Prettier. You can also add a .prettierignore to let your editor know which files not to touch.

Now you can check and format your YAML files using the commands:

prettier --check .

Check for linting issues.

prettier --write .

Fix linting issues.

YAML Tips for dbt Developers

  • You can use Jinja in almost every YAML file in dbt except the dependencies.yml file. This is because the dependencies.yml file doesn't support Jinja.
  • Use vars in any YAML file that supports Jinja (like schema.yml, snapshots.yml). However, note that:
    • In dbt_project.yml, packages.yml, and profiles.yml files, you must pass vars through the CLI using --vars, not defined inside the vars: block in the YAML file. This is because these files are parsed before Jinja is rendered.
  • You can use env_var() in all YAML files that support Jinja. Only profiles.yml and packages.yml support environment variables for secure values (using the DBT_ENV_SECRET_ prefix). These are masked in logs and intended for credentials or secrets.

🕹️ Level up your YAMLs with Jinja

While YAML is mainly static configuration, Jinja can level up the pythonization of configs.

dbt_project.yml: dynamic configuration at scale

You can use Jinja in dbt_project.yml to adapt resource configurations based on env variables. For example:

vars:
  default_schema: "{{ env_var('DBT_SCHEMA', 'analytics') }}"

models:
  my_project:
    staging:
      +materialized: "{{ 'table' if target.name == 'prod' else 'view' }}"

This gives flexibility without repeating directory overrides.

YAML property files: readability in metadata

Keep your schema.yml clean and formatted:

version: 2

models:
  - name: events
    description: "User events in UTC"
    columns:
      - name: event_id
        description: "Unique id"
        tests:
          - unique
          - not_null
      - name: event_time
        description: "When it occurred"
        tests:
          - not_null

Use two-space indentation, limit lines to 80 chars, and apply schema validation via dbt’s JSON schemas integrated in editors like VSCode.

🐍 Python Styling and Validation

dbt supports Python models—plus clarity matters here too.

Use consistent imports (stdlib first, then dbt modules), four-space indenting, explanatory docstrings, and clear logic flow. If your transformation is complex, split into functions. Always keep main logic wrapped in functions. Undefined text in dbt is a maintenance nightmare.

When using dbt with dbt_python, follow these guidelines:

  1. Keep scripts modular and focused
  2. Use snake_case for function and variable names
  3. Include docstrings and inline comments
  4. Adhere to PEP‑8: 4‑space indent, ≤ 80 chars line length, explicit imports
  5. Use linting tools (e.g. flake8, black, ruff) to enforce style.

Setting up ruff

Installing ruff is super easy.

uv tool install ruff@latest

Install ruff globally

Usage

To run Ruff as a linter, try any of the following:

ruff check                          # Lint all files in the current directory (and any subdirectories).
ruff check path/to/code/            # Lint all files in `/path/to/code` (and any subdirectories).
ruff check path/to/code/*.py        # Lint all `.py` files in `/path/to/code`.
ruff check path/to/code/to/file.py  # Lint `file.py`.

Linting python models using ruff

Or, to run Ruff as a formatter:

ruff format                          # Format all files in the current directory (and any subdirectories).
ruff format path/to/code/            # Format all files in `/path/to/code` (and any subdirectories).
ruff format path/to/code/*.py        # Format all `.py` files in `/path/to/code`.
ruff format path/to/code/to/file.py  # Format `file.py`.

Formatting python models using ruff

⚙️ Automated style checks with dbt-checkpoint

Writing style rules is one thing; enforcing them is another. You can use pre-commit hooks to automatically check your code for style violations (and often fix them automagically) before you commit. This is a great way to make sure all contributors follow your style guide. We recommend implementing this once you've settled on and published your style guide, and your codebase is conforming to it. This will ensure that all future commits follow the style guide.

Enter dbt-checkpoint: a suite of pre-commit hooks that protect schema integrity:

  • Check column descriptions never disappear;
  • Ensure model columns match properties files;
  • Validate tests, labels, contracts, and metadata consistency

Setting up pre-commit

Install pre-commit by running:

pip install pre-commit

Next, create a config file .pre-commit-config.yaml in your project root that calls the hooks from the dbt-checkpoint project.

repos:
  - repo: https://github.com/dbt-checkpoint/dbt-checkpoint
    rev: v1.2.1
    hooks:
      - id: dbt-parse
      - id: check-model-has-all-columns
      - id: check-model-columns-have-desc

Run pre-commit install and call pre-commit run --all-files to enforce across your codebase. It even works in CI pipelines via GitHub Actions.

🤔 Styling isn't an afterthought, its part of Strategy

From naming conventions to whitespace, Jinja clarity to YAML structure, dbt’s opinionated style guides help teams write maintainable, discoverable, and trustworthy pipelines. Automate enforcement with SQLFluff, Prettier, schema validation, and dbt-checkpoint.

Adopt conventions early. Automate style checks. Educate everyone. Your future self (and your colleagues) will thank you.

By following this Ultimate Guide, you’ll transform your dbt projects into narratives: clear, consistent, and human-centric, where every model and macro tells a story, not just executes transformations.

Happy modeling!

EOF

(Where I tend to share unrelated things).

Are you enjoying my writing? Leave me a comment on your favourite style guide, opinion or best practise that has saved you countless hours. Also if you can share this blog with a friend who is into analytics or dbt, that would be sweet!

🎁 Bonus: Download our Boilerplate Starter Project Repo

Congrats if you've reached this far. Now, when I was writing this blog post, I've realised that while there's so much documentation available on style guides and best practices, there weren't many boiler plate templates that I can just simply clone and use out of the box. So I've decided to build a template repo, which you guys can clone and start building locally. Check the video and link below for instructions on how to set it up!