SEEDS

Seeds are data files that live in your project's seeds/ folder and load into your target as tables — reference data like country codes, category mappings, or plan features that belongs in version control. Drop a file in, run dvt seed, and your models can ref() it like any other table.

QUICK START

Put a file in seeds/. The filename stem becomes the table name:

# seeds/country_codes.csv
country_code,country_name,region
US,United States,Americas
DE,Germany,EMEA
JP,Japan,APAC
BR,Brazil,Americas

Run dvt seed:

$ dvt seed

14:02:11  Concurrency: 4 threads (target='pg_dev')

14:02:11  1 of 1 START seed file public.country_codes ..................... [RUN]
14:02:12  1 of 1 OK loaded seed file public.country_codes ................. [INSERT 4 in 0.84s]

14:02:12  Finished running 1 seeds in 0 hours 0 minutes and 0.91 seconds (0.91s).
14:02:12  Completed successfully

14:02:12  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1

The seed landed as public.country_codeson your default target — the target schema, the filename stem. From here it's a normal node in the DAG:

-- models/staging/stg_orders.sql
select o.order_id, o.amount, c.region
from {{ source('shop', 'orders') }} o
join {{ ref('country_codes') }} c on o.country = c.country_code

dvt seedparses your project first, exactly like every graph-bearing command — a project that doesn't parse is refused before a single seed loads.

WHEN TO USE SEEDS

Seeds are for small, slowly-changing data that should live in git next to the models that use it: lookup tables, mappings, test fixtures, plan matrices. When the data changes, you edit the file, commit, and re-run — the table always mirrors the file.

They are not for raw production data — that's what sources declare. dbt's classic advice to keep seeds tiny is partly about its loader being slow on big files; DVT removes the loader penalty (next section), but git history is still the honest limit on how big a seed should get.

ENHANCEMENT 1: BULK LOADING VIA SLING

Seeds behave exactly like dbt seeds — same semantics, same place in the DAG, same seed phase in dvt build. DVT changes two things, and the first is how the bytes move.

In plain dbt: dbt seed parses the CSV in Python and pushes rows in batches of INSERT statements — a million-row seed becomes thousands of round trips to the warehouse.

In DVT: the file goes to Sling, which uses each engine's COPY-class bulk ingestion path, and seeds load in parallel up to your profile's threads. 10-100x faster on large files — 2.1M rows / 138 MB landed in ~10s in our benchmark.

Parallelism is automatic and honest: the concurrency line at the top of every run tells you how many loaders are active. Single-writer file targets (DuckDB, SQLite) take one loader at a time — running four threads against a file database isn't a slow path, it's lock failures — and DVT says so instead of silently doing less:

14:05:03  Concurrency: 1 threads (target='duck_local')
14:05:03  Serialized: 'duck_local' is a duckdb file database — one writer
          at a time, so the profile's threads:4 would collide on its lock.

ENHANCEMENT 2: FOUR FILE FORMATS

In plain dbt: seeds are CSV-only. Reference data you already have as parquet or JSON must be converted before it can be a seed.

In DVT: a seed is any of the four DVT file formats — csv, parquet, json, jsonl (ndjson is an accepted spelling of jsonl). Drop the file in seeds/ and run; the same file yields the same columns, types, and row count as it would through every other DVT file lane.

seeds/
├── country_codes.csv        → public.country_codes
├── fx_rates_2025.parquet    → public.fx_rates_2025
├── plan_features.json       → public.plan_features
└── event_samples.jsonl      → public.event_samples

Anything outside the four is refused by name, before anything loads — never silently skipped, never a half-seeded project. An .xlsx in seeds/ gets:

DVT065: seed: seed file 'regions.xlsx' asks for 'xlsx', which DVT does
not support. 'xlsx' was removed in 0.2.50: DVT's file surface is four
formats that behave identically in every lane, not ten that behave
differently per lane. DVT supports exactly four file formats: csv,
parquet, json, jsonl (ndjson is an accepted spelling of jsonl).

SELECTING SEEDS

--select (-s) and --exclude take bare seed names or full dbt selection syntax. Bare names resolve instantly; anything richer — graph operators, wildcards, tag:, path:— is answered by the engine's own selector, so a graph selection includes exactly the seeds a native build would:

dvt seed --select country_codes            # one seed, by name
dvt seed -s country_codes -s fx_rates_2025 # several (repeatable)
dvt seed --exclude legacy_map              # everything except one
dvt seed --select +fct_orders              # the seeds fct_orders depends on
dvt seed --select tag:reference            # everything tagged reference

The +fct_orders form is the one to remember: dvt build --select +my_modeluses the same resolution, so the seed phase of a build loads a model's seed ancestors automatically. And if a seed fails during dvt build, its dependents are marked SKIPPED — dbt's own semantics — instead of dying downstream on a raw engine error.

STOPPING EARLY: --FAIL-FAST

By default every seed gets its chance and failures are tallied at the end. --fail-fast (-x) stops at the first failure: whatever is already loading finishes, nothing new starts, and the tally counts what was skipped:

$ dvt seed --fail-fast

14:07:29  Concurrency: 4 threads (target='pg_dev')

14:07:29  1 of 6 START seed file public.country_codes ..................... [RUN]
14:07:29  2 of 6 START seed file public.fx_rates_2025 ..................... [RUN]
14:07:30  1 of 6 OK loaded seed file public.country_codes ................. [INSERT 249 in 0.71s]
14:07:30  3 of 6 START seed file public.bad_rows .......................... [RUN]
14:07:31  3 of 6 ERROR loading seed file public.bad_rows .................. [ERROR in 0.42s]
14:07:31  Aborting --fail-fast: a seed failed ............................. [SKIP 3]
14:07:31  2 of 6 OK loaded seed file public.fx_rates_2025 ................. [INSERT 8760 in 1.63s]

14:07:31  Finished running 6 seeds in 0 hours 0 minutes and 2.24 seconds (2.24s).
14:07:31  Completed with 1 error(s):
14:07:31    Failure in seed bad_rows

14:07:31  Done. PASS=2 WARN=0 ERROR=1 SKIP=3 TOTAL=6

RELOAD SEMANTICS: TRUNCATE VS --FULL-REFRESH

A plain dvt seed truncates each seed table and reloads it — the table object survives, so dependent views and grants survive with it. --full-refresh drops and recreates the table instead:

dvt seed                  # truncate + reload (dependent views survive)
dvt seed --full-refresh   # drop + recreate (use when columns changed)

Either way the table ends up matching the file — a seed is its file, so full-refresh is the only sensible seed semantic and the default mode replaces the data too. The difference is DDL: reach for --full-refresh when you added, removed, or retyped columns and the existing table shape no longer fits.

COLUMN NAMES: SNAKE_CASE BY DEFAULT

Seed column names are normalized to snake_case on load — "SKU Code" becomes sku_code, deterministically, on every engine. Spaced or mixed-case headers break Oracle's insert path and Databricks-class metastores outright; snake_case is the one casing every engine agrees on, and it's what makes the same seed load identically everywhere.

Override it with --column-casing (sourcekeeps the file's headers as-is, snake / upper / lower normalize):

dvt seed --column-casing source   # keep "SKU Code" exactly as written
dvt seed --column-casing upper    # SKU_CODE

Oracle gets special handling automatically: columns land UPPERCASE (Oracle's native case fold, so unquoted SQL resolves them) and strings land as sized VARCHAR2 rather than CLOB — so seed-fed models can GROUP BY and join on them without ORA-00932 surprises.

COLUMN TYPES: +COLUMN_TYPES

Types are inferred from the file, and inference is usually right. When an engine's strict casting rejects an inferred type — Databricks ANSI mode meeting 1-Mar-15-style dates, say — pin the column with dbt's native +column_types config in dbt_project.yml. DVT honors it and passes it through to the loader:

# dbt_project.yml
seeds:
  my_project:
    ordering_history:
      +column_types:
        ordering_date: string
        order_id: bigint

WHERE SEEDS LAND

Seeds load into the target's schema as <schema>.<filename stem>, and DVT creates that schema if it doesn't exist yet — a fresh project's first dvt buildnever dies on "schema does not exist". The one exception is Oracle, where schemas are users: DVT won't create a user for you, so the schema must already exist there.

--target loads seeds to any output in your profile — including bucket storages. On a bucket target (S3, GCS, Azure, SFTP) there are no tables, so each seed lands as a parquet object named after the seed, whatever format the file started as:

dvt seed --target dbx_dev    # seeds → another engine
dvt seed --target s3_lake    # seeds → a bucket

# seeds/country_codes.csv  →  <object_base>/country_codes.parquet

Every loaded seed is also recorded in DVT's materialization ledger, which is why dvt generate-sources never rediscovers your own seed tables as sources.

FLAG REFERENCE

FLAGWHAT IT DOES
-s, --selectSeeds to load — bare names or full selection syntax (graph operators, wildcards, tag:, path:). Repeatable. Default: every seed.
--excludeSeeds to leave out — same syntax as --select. Repeatable.
-t, --targetProfile output to load into, including bucket storages (seeds land as parquet objects). Default: the profile's default target.
--threadsParallel loads. Default: the target's threads setting. Single-writer file targets (duckdb, sqlite) serialize to 1 with a printed notice.
--full-refreshDrop and recreate each seed table. Default: truncate + reload, which keeps dependent views alive.
--column-casingColumn-name normalization: source | snake | upper | lower. Default: snake.
-x, --fail-fastStop at the first failed seed — in-flight loads finish, nothing new starts, skips are tallied.
--project-dirProject directory. Default: the current directory.
--profiles-dirProfiles directory. Default: ~/.dbt (or $DBT_PROFILES_DIR).
--debugDebug logging.