Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt Seeds: Loading Static Reference Data Into Your Warehouse

Not every table your models need comes from a source system. Sometimes itโ€™s a small, hand-maintained lookup โ€” country code to region mappings, a list of holiday dates, a manually curated list of excluded test accounts โ€” that doesnโ€™t belong in any production pipeline because nothing external produces it. dbt seeds solve exactly this case: version-controlled CSV files, checked into your repository, loaded into the warehouse as real tables with a single command.


What a Seed Actually Is

A seed is a CSV file living in the seeds/ directory of your dbt project.

seeds/country_region_mapping.csv
country_code,country_name,region
US,United States,North America
CA,Canada,North America
GB,United Kingdom,Europe
DE,Germany,Europe
JP,Japan,Asia Pacific
AU,Australia,Asia Pacific
Terminal window
dbt seed

Running this loads the CSV into your warehouse as a real table โ€” country_region_mapping โ€” that you can then reference with ref() exactly like any dbt model.

select
o.order_id,
o.amount,
c.region
from {{ ref('stg_orders') }} as o
left join {{ ref('country_region_mapping') }} as c
on o.country_code = c.country_code

The fact that country_region_mapping came from a CSV rather than SQL logic is invisible to any model referencing it โ€” ref() works identically either way, which is the same materialization independence covered in ref() Function.


Why Version-Controlled CSVs Instead of a Manual Warehouse Table

The obvious alternative โ€” someone manually creates and maintains a lookup table directly in the warehouse โ€” has a real problem: thereโ€™s no history, no review process, and no way to know who changed a mapping or why. A seed file, being a CSV in Git, gets the same review and audit trail as any code change.

Terminal window
git log seeds/country_region_mapping.csv
git diff HEAD~1 seeds/country_region_mapping.csv

Adding a new country to the mapping is a pull request, reviewable by a teammate, with a clear record of exactly what changed and when โ€” meaningfully different from an untracked manual edit directly in a warehouse console.


Configuring Seed Column Types

By default, dbt infers column types from the CSVโ€™s contents, which usually works but occasionally gets it wrong โ€” a column of numeric-looking codes that should stay as strings (postal codes, leading-zero IDs) is a common trap.

dbt_project.yml
seeds:
my_project:
country_region_mapping:
+column_types:
country_code: varchar(2)

Explicitly declaring types for columns where inference is likely to guess wrong avoids a class of subtle bugs where a code like "007" silently becomes the integer 7.


Testing Seeds Like Any Other Model

Seeds support the same schema tests as models and sources โ€” a seed with a country_code column benefits from the same unique and not_null tests youโ€™d apply anywhere else.

seeds:
- name: country_region_mapping
columns:
- name: country_code
tests:
- unique
- not_null

This is worth doing precisely because seed files are hand-edited โ€” a typo introducing a duplicate row is easy to make and easy to miss on review without an automated test catching it.


Referencing Seeds From Downstream Documentation and Lineage

Because seeds are just another node type in dbtโ€™s dependency graph, they appear in the lineage graph exactly like models and sources โ€” clicking a seed in the documentation site (covered in dbt Docs Serve) shows every downstream model that consumes it. This is genuinely useful for reference data specifically, since it answers a question thatโ€™s otherwise easy to lose track of: โ€œif I change this mapping, what actually depends on it?โ€ A seed with descriptions and column-level documentation attached is just as discoverable to a new team member as any regular model.

When Seeds Are the Wrong Tool

Seeds are explicitly for small, static, infrequently-changing reference data. Theyโ€™re the wrong tool for:

SituationRight approach
Small, static lookup, rarely changesSeed
Data owned by another teamโ€™s systemSource
Frequently updated business dataSource, not seed
Time-tracking snapshots of changing dimensionsSnapshot, covered in dbt Snapshots

Full Refresh Behavior

By default, dbt seed only inserts new rows into an existing seed table if the schema hasnโ€™t changed โ€” to fully replace the contents (including deleting removed rows), use --full-refresh:

Terminal window
dbt seed --full-refresh

This drops and recreates the seed table from the current CSV contents, which is usually what you actually want after editing a seed file, since seeds represent the current state of the reference data, not an append-only log.


Common Mistakes

Using seeds for anything resembling operational data. If a seed file needs updating more than occasionally, or by more than one or two people, itโ€™s a sign the data should live in a proper source system instead.

Not specifying column types for ambiguous columns. Numeric-looking string codes are the most common type-inference trap โ€” always check what type a seed column landed as after the first load.

Forgetting --full-refresh after editing a seed. Without it, deleted rows from the CSV can remain in the warehouse table, since a plain dbt seed run doesnโ€™t necessarily reconcile deletions.

Summary

ElementPurpose
seeds/*.csvVersion-controlled static reference data
dbt seedLoads CSVs into the warehouse as real tables
+column_typesOverrides type inference for ambiguous columns
dbt seed --full-refreshFully replaces the seed table, reconciling deletions

Seeds are a small feature that solves a real and common problem โ€” small, static reference data that needs the same review discipline as your actual transformation logic, without needing a full source pipeline built around it.