# Differences Between dbt Seeds, Macros, and Packages Explained

> Understand the differences between dbt seeds, macros, and packages. Learn how seeds load CSVs, macros create reusable SQL, and packages extend your dbt project.

- Repository: [DataTalksClub/data-engineering-zoomcamp](https://github.com/DataTalksClub/data-engineering-zoomcamp)
- Tags: deep-dive
- Published: 2026-05-31

---

**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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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.

```sql
-- 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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/safe_cast.sql) and [`get_vendor_data.sql`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/macros/safe_cast.sql), the project abstracts BigQuery-specific casting, while [`get_vendor_data.sql`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/get_vendor_data.sql) generates CASE statements mapping vendor IDs to human-readable names.

You invoke macros inside models using double curly braces:

```sql
-- 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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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:

```sql
-- 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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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`](https://github.com/DataTalksClub/data-engineering-zoomcamp/blob/main/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.