dbt Project Standard Template (Analytics Engineering)
Layers and folder layout, naming, sources and freshness, tests, contracts, versions, groups and access, docs, macros, environments, slim CI, review checklist, cost guardrails
使用方法: Use it when you set or refresh the conventions every pull request in a dbt project must follow. Fill the principles and the layer rules first, then the test minimums per layer and the access level of each public model. The main pitfall is making every model public, so nothing can change without breaking a consumer.
预览
dbt Project Standard: [Project name]
Analytics engineering conventions for one dbt project or a group of related projects: layers and folder layout, naming, sources and freshness, model design and materialisation, tests (generic, singular and unit tests), model contracts and versions, groups and access, documentation, macros and packages, environments and targets, CI with state comparison and deferral, the code review checklist and cost guardrails. Written for dbt Core 1.11 and 1.12 (stable lines in October 2026, 1.12 marked Latest) and kept neutral where dbt Core 2.0 (announced as an alpha in June 2026, with 2.0.x releases tagged since September 2026) or another engine differs; check docs.getdbt.com for the support status of the release you run. Replace every
[bracketed]value and every row marked Example.
Document control
| Field | Value |
|---|---|
| Document ID | [DBT-STD-001] |
| Project | [corvid_analytics] (Example) |
| Repository | [git URL] |
| Data platform and adapter | [Lakehouse engine and dbt adapter, with version] |
| dbt release line | [dbt Core 1.12.x / other engine and version] |
| Version | [1.0] |
| Status | [Draft / Approved / Superseded] |
| Owner | [@analytics-engineering-lead] |
| Approvers/reviewers | [@data-platform-lead], [@data-governance-lead], [@security-reviewer] |
| Classification | [Internal] |
| Last updated | YYYY-MM-DD |
| Next review | YYYY-MM-DD (at least every [12] months or on a major dbt upgrade) |
| Related documents | [Data platform architecture], [Data quality standard], [CI/CD pipeline design] |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Analytics engineering lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Data platform lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Data governance lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@author] | First draft |
| 1.0 | YYYY-MM-DD | [@author] | Approved for use |
Contents
- 1. Scope and principles
- 2. Layers and folder layout
- 3. Naming conventions
- 4. Sources and freshness
- 5. Model design and materialisation
- 6. Testing
- 7. Contracts, versions, groups and access
- 8. Documentation
- 9. Macros and packages
- 10. Environments and targets
- 11. CI and deployment
- 12. Code review checklist
- 13. Cost guardrails
- 14. Exceptions and compliance
- 15. Glossary and references
1. Scope and principles
This standard applies to [all dbt projects in the corvid-analytics
organisation] and to every person who opens a pull request against them.
It applies to models, seeds, snapshots, sources, tests, macros,
semantic definitions and exposures. Orchestration, ingestion and storage
design are out of scope and live in the documents named in the guidance
above.
| ID | Principle | What it means in practice |
|---|---|---|
| DBT-P-01 | Source text is reviewed code | Every change goes through a pull request, CI and one approving review |
| DBT-P-02 | One way in, one way out | Raw data enters only through declared sources; consumers read only marts or public models |
| DBT-P-03 | Transform once | A business rule lives in exactly one model; downstream models reuse it with ref |
| DBT-P-04 | Explicit interfaces | Models used outside their group have an enforced contract, an owner and a version policy |
| DBT-P-05 | Tested before trusted | No model reaches a mart without primary key tests and the tests its layer requires |
| DBT-P-06 | Cheapest correct build | Choose the materialisation and CI selection that give a correct result for the least compute |
| DBT-P-07 | Engine neutral where possible | Prefer standard SQL and cross-database macros; isolate engine-specific SQL in macros |
Rules are written as "must" (blocking in review) or "should" (expected, an exception needs a short reason in the pull request). Section 14 says how to record a standing exception.
2. Layers and folder layout
The project uses three model layers. Each layer may read only from the layer before it, except that marts may also read other marts of the same group.
| Layer | Purpose | May read from | May do | Default materialisation |
|---|---|---|---|---|
| Staging | One cleaned model per source table: rename, cast, standardise units and time zones, light deduplication | Sources only | Rename, cast, basic computation, filter deleted records | View |
| Intermediate | Reusable building blocks: joins, pivots, fan-out and re-grain steps that serve several marts | Staging and other intermediate models | Join, aggregate, apply business rules | Ephemeral or view |
| Marts | Business-facing entities and facts at a declared grain, organised by domain | Intermediate, staging, other marts in the group | Final business logic, denormalisation for consumers | Table or incremental |
Rules:
- DBT-001 A staging model must select from exactly one source table and must not join.
- DBT-002 Only staging models may call
source(). - DBT-003 Intermediate models must not be exposed to BI tools or other projects; set their access to private or protected (section 7).
- DBT-004 A mart must declare its grain in its description, for example "one row per order line".
- DBT-005 Seeds hold small, static reference data maintained by people (country codes, channel mappings). Seeds must not hold data exported from a system.
Folder layout (Example):
corvid_analytics/
dbt_project.yml
packages.yml
models/
staging/
shop_db/ # one folder per source system
_shop_db__sources.yml
_shop_db__models.yml
stg_shop_db__orders.sql
stg_shop_db__order_lines.sql
payments_api/
stg_payments_api__payments.sql
intermediate/
orders/
int_orders__lines_joined_to_payments.sql
marts/
orders/ # one folder per domain or group
_orders__models.yml
fct_orders.sql
dim_customers.sql
checkout/
fct_checkout_sessions.sql
semantic/
orders_semantic.yml
snapshots/
seeds/
macros/
tests/ # singular tests
analyses/
The semantic/ folder holds semantic definitions governed by Semantic
Model Specification (data/semantic-model-specification). Analyses are
for ad hoc SQL that is versioned but never materialised.
3. Naming conventions
| Object | Pattern | Example |
|---|---|---|
| Staging model | stg_<source>__<entity> (double underscore) | stg_shop_db__orders (Example) |
| Intermediate model | int_<domain>__<verb or description> | int_orders__lines_joined_to_payments (Example) |
| Fact | fct_<business process> (plural) | fct_orders (Example) |
| Dimension | dim_<entity> (plural) | dim_customers (Example) |
| Wide mart for a tool | rpt_<audience>__<subject> | rpt_finance__daily_revenue (Example) |
| Snapshot | snp_<source>__<entity> | snp_shop_db__products (Example) |
| Seed | seed_<subject> | seed_channel_mapping (Example) |
| YAML properties file | _<folder>__models.yml, _<folder>__sources.yml | _orders__models.yml (Example) |
| Macro | verb first, snake case | cents_to_currency (Example) |
Column rules:
- DBT-010 Use snake case for every model and column name.
- DBT-011 Name primary keys
<entity>_idand keep the same name for the foreign key in every other model. - DBT-012 Prefix booleans with
is_orhas_. - DBT-013 Suffix timestamps with
_atand store them in UTC; suffix dates with_date. State the time zone in the description when a local date is required by the business. - DBT-014 Put the unit in the name of every measure whose unit is not
obvious:
amount_eur,duration_seconds. - DBT-015 Do not use abbreviations that are not in the glossary in
section 15 or in the Data Dictionary (
architecture/data-dictionary). - DBT-016 Do not use reserved words of any supported engine as column names.
4. Sources and freshness
- DBT-020 Declare every raw table in a sources file next to its
staging models, with a description, an owner in
meta(placed underconfig, which current releases document and which supports inheritance) and the loader that writes it. - DBT-021 Every source table that is loaded incrementally must have a loaded-at field and a warn and error freshness threshold.
- DBT-022 Run the source freshness check before each production build and publish the result to the alert channel of the source owner.
- DBT-023 A source that has a Data Contract (
data/data-contract) must reference the contract ID inmeta, and its tests must cover the contract's quality rules.
| Source | Owner | Loaded-at field | Warn after | Error after | On error (Example) |
|---|---|---|---|---|---|
shop_db.orders (Example) | [@orders-platform] | _loaded_at | 2 hours | 6 hours | Skip downstream marts, alert owner |
payments_api.payments (Example) | [@payments-team] | _ingested_at | 4 hours | 12 hours | Build with warning banner |
| [source.table] | [@owner] | [column] | [interval] | [interval] | [Action] |
Sources file sketch (Example):
sources:
- name: shop_db
description: Operational order database, replicated by CDC.
config:
meta:
owner: "@orders-platform"
contract_id: DC-ORD-001
loaded_at_field: _loaded_at
freshness:
warn_after: {count: 2, period: hour}
error_after: {count: 6, period: hour}
tables:
- name: orders
columns:
- name: order_id
data_tests: [not_null, unique]
Property keys move between dbt releases: freshness moved under
config in 1.9, loaded_at_field in 1.10, and column-level meta in
1.10 (backported to 1.9). Top-level meta is still accepted for
backward compatibility but does not inherit, so write new properties
under config. Check the property reference for your release before
copying a sketch.
5. Model design and materialisation
Model file rules:
- DBT-030 Start each model with import CTEs, one per
reforsource, then logical CTEs, then a finalselect. - DBT-031 Select explicit columns in marts;
select *is allowed only in import CTEs. - DBT-032 Use
refandsourcefor every relation; hard-coded schema or table names are forbidden. - DBT-033 Keep one business rule per CTE and name the CTE after the rule.
- DBT-034 Format SQL with the project's linter and formatter configuration; CI fails on lint errors.
| Materialisation | Use when | Rules |
|---|---|---|
| View | Staging, light transformations, small data | Default for staging |
| Ephemeral | A small intermediate step used by one or two models | Avoid when the step is reused widely or needs tests on its output |
| Table | Marts that are read often and rebuilt within the window | Default for marts under [50 GB] (Example) |
| Incremental | Large, append-heavy facts | Must declare unique_key, an incremental strategy and a look-back window for late data; must have a documented full-refresh procedure |
| Snapshot | Type 2 history of a mutable source table | Use timestamp strategy where a reliable updated-at column exists |
| Materialised view or dynamic table | Engine-managed refresh is cheaper than scheduled rebuilds | Approval of the data platform lead; model contracts are not supported on materialised views, so record an exception (section 14) with a compensating test |
- DBT-035 Every incremental model must handle late-arriving records with a look-back window of at least [3 days] (Example) and must say in its description how deletes in the source are propagated.
- DBT-036 Schedule a full refresh of each incremental model at least [monthly] (Example) or document why it is unsafe.
- DBT-037 Python models need approval from the data platform lead and must state why SQL is not sufficient.
6. Testing
The project uses four kinds of tests. Data tests run against built data; unit tests run against small fixed inputs before data is built (available from dbt 1.8).
| Kind | What it checks | Where defined | Runs in |
|---|---|---|---|
| Generic data test | A reusable rule on a column or model: not null, unique, accepted values, relationships, or a custom generic test | Properties YAML | CI and production |
| Singular data test | A one-off SQL query that returns failing rows | tests/ folder | CI and production |
| Unit test | Model logic on mocked input rows with expected output rows | Properties YAML under models/ | Development and CI only |
| Source freshness | Data arrived on time | Sources YAML | Production, before the build |
Minimum tests per layer:
| Layer | Required | Recommended |
|---|---|---|
| Staging | Primary key not_null and unique; accepted_values on status columns | Row count within an expected range |
| Intermediate | Primary key tests where the grain changes | Unit test for any fan-out or re-grain logic |
| Marts | Primary key tests; relationships for each foreign key to a dimension; tests for each rule in an attached contract | Unit tests for every non-trivial calculation; reconciliation test against a finance or source total |
- DBT-040 A test that fails in production with severity error stops
downstream models in the same run and raises an alert for the model
owner, using the severity scale in Incident Severity Matrix
(
ops/incident-severity-matrix) when consumers are affected. - DBT-041 Warning-level tests must have an owner and a review date; a warning ignored for [30 days] (Example) is either fixed, promoted to error or deleted.
- DBT-042 Unit tests must not run in production builds; exclude them by resource type in production jobs.
- DBT-043 Store failing rows for marts tests in a dedicated audit schema with a retention of [14 days] (Example).
- DBT-044 Write a unit test for every bug fix to a calculation, so the bug cannot return.
Organisation-wide thresholds (for example acceptable null rates per data
classification) come from Data Quality Standard
(data/data-quality-standard); this section only says how they are
implemented in dbt.
7. Contracts, versions, groups and access
A model contract guarantees the column names and data types of a
model's output and fails the build when they differ. Model versions let
a breaking change ship as a new version while consumers migrate. Groups
assign models to an owning team, and access levels limit who may ref a
model.
| Access level | Who may reference | Use for |
|---|---|---|
| Private | Models in the same group | Intermediate steps |
| Protected | Models in the same project | Staging and internal marts (default) |
| Public | Any group or project (tools read through warehouse grants, not through access) | Marts that are interfaces for other teams, BI or data products |
Rules:
- DBT-050 Every model belongs to a group, and every group has an owner with a contact in the group definition.
- DBT-051 Every public model must have an enforced contract, a description, a declared grain and tests on its primary key.
- DBT-052 A breaking change to a public model (removing or renaming a column, changing a type, changing the grain) must ship as a new model version. Keep the previous version for at least [90 days] (Example) and set its deprecation date.
- DBT-053 Announce a new version and the deprecation date to every consumer listed in exposures and in the related data contract before merge.
- DBT-054 Non-breaking additions (a new nullable column) do not need a new version but must be listed in the changelog of the data product.
- DBT-055 Declare exposures for each dashboard, application or data product that reads a public model, with the owner and URL.
| Model | Group | Access | Contract | Latest version | Deprecated versions (Example) |
|---|---|---|---|---|---|
fct_orders (Example) | orders | Public | Enforced | v2 | v1, deprecation date YYYY-MM-DD |
dim_customers (Example) | orders | Public | Enforced | v1 | None |
int_orders__lines_joined_to_payments (Example) | orders | Private | Not required | Not versioned | None |
Contracts are supported on some materialisations only: tables, views
(column names and types, without constraints) and incremental models
whose schema change policy is append_new_columns or fail. They are
not supported on materialised views, ephemeral models, Python models,
custom materialisations unless the author added support, or on seeds,
snapshots and sources. Check the contract page for your release and
adapter, and record unsupported cases in section 14.
Warehouse access is separate from dbt access: the access level controls
which groups and projects may ref a model, while BI tools and other
readers are governed by the grants and masking policies set per target
(section 10).
8. Documentation
- DBT-060 Every model and every column of a mart has a description. Descriptions say what the value means for the business, its unit, and any rule that changes it.
- DBT-061 Reuse descriptions of shared columns through doc blocks so
customer_idmeans the same thing everywhere. - DBT-062 Record in
config.metafor each mart: owner, data classification per Data Classification Policy (data/data-classification-policy), contains personal data (yes or no), and the related data product ID. - DBT-063 Columns that hold personal data carry a tag that drives
masking policies and the retention rules in Data Retention and Deletion
Standard (
data/data-retention-deletion-standard). - DBT-064 Generate and publish project documentation from the production build only, so it describes what consumers actually read.
- DBT-065 Keep business glossary terms in the Data Dictionary
(
architecture/data-dictionary) and link to them; do not maintain a second glossary inside the project.
9. Macros and packages
- DBT-070 Write a macro only when the same logic appears in three or more models or when engine-specific SQL must be isolated.
- DBT-071 Each macro has a description, documented arguments and at least one model or unit test that exercises it.
- DBT-072 Prefer the cross-database macros shipped with dbt and the adapter over custom engine-specific SQL.
- DBT-073 Pin every package to an exact version or a narrow range in
packages.yml, commit the lock file, and upgrade through a pull request that runs the full CI. - DBT-074 Approve new packages through the organisation's dependency review: licence, maintenance activity, and whether the package executes SQL with elevated grants.
- DBT-075 Override
generate_schema_nameonly in one place, and document the resulting schema names per target in section 10.
| Package | Version | Purpose | Approved by (Example) |
|---|---|---|---|
| [package name] | [exact version] | [Generic tests, date spine, cross-database helpers] | [@data-platform-lead] |
10. Environments and targets
| Target | Purpose | Catalogue and schema pattern | Credentials | Who runs it | Data (Example) |
|---|---|---|---|---|---|
dev | Personal development | dev catalogue, dbt_<username> | Personal, read on raw, write on own schema | Each developer | Production data limited by a date filter of [30 days] |
ci | Pull request checks | ci catalogue, pr_<number> | Service account | CI runner | Deferred to production artefacts, sampled where needed |
staging | Release candidate verification | staging catalogue, production schema names | Service account | Release job | Full copy or zero-copy clone |
prod | Consumer-facing build | prod catalogue, production schema names | Service account, no human write | Scheduler only | Full |
- DBT-080 Credentials are never stored in
profiles.ymlin the repository; read them from environment variables or the secret store. - DBT-081 Personal development builds must limit data volume with a target-aware filter macro.
- DBT-082 Masking and row access policies apply in every target that
holds production personal data, including
dev. - DBT-083 Drop CI schemas automatically when the pull request is closed and drop idle development schemas after [14 days] (Example).
11. CI and deployment
The pull request job uses "slim CI": it compares the project with the manifest of the latest production run (state comparison), builds only modified models and their children, and defers unmodified parents to the production relations so they are not rebuilt.
| Step | Rule | Blocking |
|---|---|---|
| Lint | SQL linter and YAML schema validation pass | Yes |
| Selection | Modified models plus downstream children, selected by state | Yes |
| Deferral | Unmodified parents resolve to production relations | Yes |
| Breaking changes | Contract errors on public models fail the job | Yes |
| Tests | All error-severity tests pass on built models; unit tests pass | Yes |
| Documentation | New models and mart columns have descriptions | Yes |
| Cost | Bytes scanned or credits used above [threshold] post a warning | No |
- DBT-090 The production job publishes its manifest to a fixed location after every successful run so CI can compare state.
- DBT-091 If the production manifest is missing, state comparison cannot run: CI builds and tests the whole project (or the paths changed in the pull request), never skipping tests.
- DBT-092 Merges to the main branch follow Branching Strategy
(
dev/branching-strategy); deployments run through the pipeline described in CI/CD Pipeline Design (dev/ci-cd-pipeline-design). - DBT-093 Upgrades of the dbt engine or adapter run the full project
in
stagingand compare row counts and key aggregates of every public model before production. - DBT-094 Reruns after a failure use the retry capability for the failed nodes rather than a manual partial build.
12. Code review checklist
| ID | Check | Rule |
|---|---|---|
| CR-01 | The pull request states the business reason and links the ticket | DBT-P-01 |
| CR-02 | New or changed models are in the right layer and folder | DBT-001 to DBT-005 |
| CR-03 | Names follow section 3, including keys, booleans and units | DBT-010 to DBT-016 |
| CR-04 | No hard-coded relations; import CTEs at the top | DBT-030 to DBT-032 |
| CR-05 | Grain is declared and joins cannot fan out unexpectedly | DBT-004 |
| CR-06 | Required tests for the layer exist; calculation changes have a unit test | DBT-040 to DBT-044 |
| CR-07 | Public model changes are additive, or ship as a new version with a deprecation date | DBT-051 to DBT-054 |
| CR-08 | Descriptions, owner, classification and personal data tags are present | DBT-060 to DBT-063 |
| CR-09 | Materialisation and incremental logic suit volume and late data | DBT-035, DBT-036 |
| CR-10 | CI cost warning reviewed; no full scan added to a frequent job without reason | Section 13 |
| CR-11 | Metric logic changes are reflected in the semantic model and the metric definition | Section 2 (semantic/ folder), DBT-065 |
Use Pull Request (dev/pull-request) for the description template.
13. Cost guardrails
| ID | Guardrail | Limit or rule (Example) | Enforced by |
|---|---|---|---|
| COST-01 | Development data window | 30 days on facts | Target-aware filter macro |
| COST-02 | Query timeout per model | 30 minutes in CI, 2 hours in production | Profile and warehouse settings |
| COST-03 | Compute size per target | Small for dev and CI, medium for prod | Warehouse or cluster policy |
| COST-04 | Partition and clustering | Facts over 10 GB are partitioned by event date | Review checklist CR-09 |
| COST-05 | Incremental by default for large facts | Facts over 100 million rows are incremental | Review checklist |
| COST-06 | Job cost tagging | Every job sets query tags for project, target and job | Profile configuration, Cost Allocation and Tagging Standard (ops/cost-allocation-tagging-standard) |
| COST-07 | Monthly review | Top 10 models by compute reviewed each month | FinOps Cost Review (ops/finops-cost-review) |
- DBT-100 A model that becomes one of the top 10 by compute for two months in a row needs an optimisation ticket or a recorded exception.
- DBT-101 Delete models with no downstream nodes, no exposures and no queries in [90 days] (Example) after notifying the group owner.
14. Exceptions and compliance
Record standing exceptions here. Each exception names the rule, the reason, the compensating control and an expiry date.
| ID | Rule | Scope | Reason | Compensating control | Owner | Expires |
|---|---|---|---|---|---|---|
| EXC-001 (Example) | DBT-051 | fct_checkout_sessions | Materialised view used for near-real-time refresh; contract not supported | Singular test checks column names and types after each refresh | [@checkout-analytics] | YYYY-MM-DD |
| [EXC-NNN] | [Rule] | [Models] | [Reason] | [Control] | [@owner] | YYYY-MM-DD |
Measure compliance each quarter from the project manifest: share of
models with descriptions, share of public models with contracts, models
without primary key tests, and open exceptions past their expiry.
Report the figures to the data governance forum described in Data
Governance Operating Model (data/data-governance-operating-model).
15. Glossary and references
| Term | Meaning in this standard |
|---|---|
| Model | A SQL or Python file that dbt builds into a relation |
| Source | A raw table declared so that lineage and freshness are tracked |
| Grain | What one row of a model represents |
| Contract | Enforced guarantee of a model's column names and data types |
| Model version | A numbered variant of a model that lets consumers migrate after a breaking change |
| Group | A named set of models owned by one team |
| Exposure | A declared downstream consumer such as a dashboard or application |
| State comparison | Selecting nodes by comparing the project with a saved manifest |
| Deferral | Resolving references to unbuilt parents against another environment's relations |
| Slim CI | A CI job that builds only changed models and their children, using state and deferral |
References:
- dbt Core releases and changelog: https://github.com/dbt-labs/dbt-core (1.11.16 and 1.12.6 released 2026-10-14, 1.12.6 marked Latest; dbt Core 2.0, a Rust implementation, was announced as an alpha on 2026-06-01 and 2.0.x releases have been tagged since September 2026; check docs.getdbt.com for its current support status before adopting it).
- dbt documentation: best practice guide on project structure, model contracts, model versions, model access and groups, unit tests, source freshness, state selection and deferral: https://docs.getdbt.com
- Related templates: Data Platform Architecture
(
architecture/data-platform-architecture), Data Pipeline Specification (data/data-pipeline-spec), Data Contract (data/data-contract), Data Quality Standard (data/data-quality-standard), Data Product Specification (data/data-product-specification), Semantic Model Specification (data/semantic-model-specification), Metric Definition (data/metric-definition), Coding Standards (dev/coding-standards).
Standard and licence note
Structure aligned with the project structure, testing, governance and CI concepts described in the dbt documentation (docs.getdbt.com) and with dbt Core (Apache License 2.0). Only concept, command and property names are used; no text, examples or diagrams from the dbt documentation are reproduced, and the Corvid Commerce example is fictional. This template is original text written for this collection. dbt, dbt Core and dbt Labs are trademarks of dbt Labs, Inc.; this template is not affiliated with or endorsed by dbt Labs. Check the current documentation for your dbt release and adapter before use, as property names and defaults change between releases. Template licence: MIT.
相关模板
Data Quality Standard Template (ISO/IEC 25012, ODCS)
Organisation DQ standard: ISO/IEC 25012 vocabulary, tiers, rule catalogue, severity, enforcement points, observability, scorecard, issues, waivers
数据工程与治理Data Retention and Deletion Standard Template (GDPR Art. 17, SP 800-88)
Retention rule method, deletion classes, standard periods, deletion per store (OLTP, Iceberg, Delta, search, logs, ML, backups), erasure propagation, evidence, sanitisation
数据工程与治理Analytics Tracking Plan Template (Privacy-First, JSON Schema, GPC)
Privacy-first tracking plan: tiers, object_action naming, events, property dictionary, JSON Schema 2020-12, SchemaVer, consent and GPC gating, PII rules, QA