Differences Between dbt Seeds, Macros, and Packages Explained

dbt seeds, macros, and packages are three distinct mechanisms that serve different architectural purposes—seeds load static CSV files into your warehouse as tables, macros are reusable Jinja-powered SQL snippets compiled at build time, and packages are external dbt projects that import additional models, macros, and tests into your codebase.

The DataTalksClub/data-engineering-zoomcamp repository demonstrates how these components create a clean separation of concerns in analytics engineering. Understanding the differences between dbt seeds, macros, and packages allows you to architect modular pipelines that isolate static reference data, reusable transformation logic, and third-party dependencies.

What Are dbt Seeds?

dbt seeds are static data assets that load CSV files directly into your data warehouse as tables. Unlike models that transform existing data through SQL, seeds version-control small lookup tables alongside your code and materialize them through the dbt seed command.

In the zoomcamp project, seeds live in the seeds/ directory and are configured in seeds/seeds_properties.yml. The repository includes reference data such as taxi_zone_lookup and payment_type_lookup, which become first-class tables in the DAG that downstream models can reference using {{ ref('seed_name') }}.

How Seeds Work

Seeds are defined by CSV files in your seed-paths (typically the seeds/ folder), with metadata specified in a YAML configuration file. When you run dbt seed, dbt creates or updates tables in the warehouse containing the CSV data. Because seeds participate in the dependency graph like any model, you can build downstream transformations that depend on this static data without hardcoding values.

-- models/marts/dim_zones.sql
with raw_zones as (
    select *
    from {{ ref('taxi_zone_lookup') }}
)

select
    location_id,
    borough,
    zone,
    service_zone
from raw_zones;

What Are dbt Macros?

dbt macros are reusable pieces of Jinja-templated SQL that generate or transform code at compile time. They function like functions in traditional programming, allowing you to encapsulate repetitive logic, handle cross-database dialect differences, and build dynamic SQL statements that would otherwise be copy-pasted across multiple models.

The zoomcamp repository stores macros in the macros/ directory, with specific implementations like safe_cast.sql and get_vendor_data.sql. These files contain Jinja logic that expands into standard SQL when dbt compiles your project.

Macro Structure and Usage

Macros accept arguments and can include control flow statements like {% if %} loops or conditional logic. In macros/safe_cast.sql, the project abstracts BigQuery-specific casting, while get_vendor_data.sql generates CASE statements mapping vendor IDs to human-readable names.

You invoke macros inside models using double curly braces:

-- models/staging/stg_yellow_tripdata.sql
select
    {{ safe_cast('pickup_datetime', 'TIMESTAMP') }} as pickup_ts,
    {{ safe_cast('dropoff_datetime', 'TIMESTAMP') }} as dropoff_ts,
    {{ get_vendor_data('vendor_id') }} as vendor_name,
    *
from {{ source('raw', 'yellow_tripdata') }}

What Are dbt Packages?

dbt packages are external dbt projects that you install as dependencies, bringing their own models, seeds, macros, and tests into your local project. They enable code reuse across organizations and allow you to leverage community-maintained utilities rather than reinventing common patterns.

The zoomcamp project declares its dependencies in packages.yml, typically including utilities like dbt_utils or codegen. After running dbt deps, these packages install into the dbt_packages/ directory (as defined in clean-targets within dbt_project.yml), making their objects available as if they were native to your project.

Leveraging Package Utilities

Once installed, package macros are called using the namespace prefix. For example, the dbt_utils package provides the surrogate_key macro for generating unique identifiers:

-- models/marts/fct_trips.sql
with base as (
    select *
    from {{ ref('stg_yellow_tripdata') }}
)

select
    {{ dbt_utils.surrogate_key(['trip_id']) }} as trip_key,
    *
from base;

Key Architectural Differences

Understanding how these three concepts operate within the dbt project structure clarifies when to use each:

  • Seeds are data assets. They become physical tables in your warehouse and are version-controlled as CSV files. Use them for small, static reference data that changes infrequently, such as lookup tables or mappings.

  • Macros are code assets. They exist only at compile time and generate SQL that gets injected into your models. Use them for repetitive logic, cross-database compatibility, or dynamic SQL generation.

  • Packages are project dependencies. They import external functionality, including models, macros, and tests from other dbt projects. Use them to standardize practices across teams or leverage open-source utilities.

Summary

  • Seeds load CSV files from the seeds/ directory into your warehouse as tables using dbt seed, as seen with taxi_zone_lookup in the zoomcamp project.
  • Macros are Jinja templates stored in macros/ that generate reusable SQL snippets at compile time, such as safe_cast and get_vendor_data.
  • Packages are external dependencies declared in packages.yml and installed via dbt deps, extending your project with third-party macros and models like those from dbt_utils.
  • Each component serves a distinct layer: seeds for static data, macros for reusable logic, and packages for external project composition.

Frequently Asked Questions

When should I use a seed instead of a source in dbt?

Use seeds for small, static lookup tables that you want version-controlled with your codebase, such as the taxi_zone_lookup data in the zoomcamp repository. Use sources to reference raw data that lives in your warehouse and is managed by external ingestion processes, not CSV files committed to git.

Can macros contain conditional logic and call other macros?

Yes, macros support full Jinja control flow including {% if %}, {% for %}, and {% set %} blocks, allowing complex conditional SQL generation. Macros can also call other macros, enabling layered abstractions where utility macros support higher-level business logic macros.

How do I update packages to newer versions in a dbt project?

Update the version constraints in your packages.yml file and run dbt deps to fetch the latest compatible versions. dbt installs packages into the dbt_packages/ directory (as configured in dbt_project.yml under clean-targets), and you should include this directory in your .gitignore to avoid committing external code.

Are seeds appropriate for large datasets?

No, seeds are designed for small reference data, typically under a few megabytes. Large datasets should be loaded through external ingestion tools or sources, as seeds are compiled into the project and can significantly increase repository size and dbt seed execution time.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →