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
dbt best practices, implementation patterns, and updates for modern data teams.
No spam. Unsubscribe anytime.
🙋 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.
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.
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(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:
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.
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.
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:
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:
Keep scripts modular and focused
Use snake_case for function and variable names
Include docstrings and inline comments
Adhere to PEP‑8: 4‑space indent, ≤ 80 chars line length, explicit imports
Use linting tools (e.g. flake8, black, ruff) to enforce style.
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.
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.
Sign up for dbt Engineer
dbt best practices, implementation patterns, and updates for modern data teams.
No spam. Unsubscribe anytime.
🤔 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!
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!
Learn how to seamlessly run your dbt Core project with Apache Airflow. We cover the different methods to setup, code repo, video tutorial for local dev, and best practices to help you orchestrate dbt runs using Airflow: the easiest way to modernize your data workflows.
Struggling with "maximum recursion depth exceeded" in dbt macros? This article explains why it happens—usually due to missing or misused variables—and shows simple, beginner-friendly steps to fix it, ensuring your dbt projects run smoothly and error-free.
Subscribe to my Newsletter
Join 2000+ data engineers and developers discovering the latest in dbt and analytics engineering.