> The complete documentation index is at [llms.txt](/llms.txt).

:::info[Private alpha]

dbt migration support is in private alpha. [Contact us](https://www.bauplanlabs.com/contact) to request preview access.

:::

If your transformations live in dbt Core today, you can bring them to Bauplan with only a few minor changes.

Bauplan does not run dbt projects natively, but the gap is small as they are built on the same primitives--SQL models chained into a DAG, with contracts and tests on their outputs.

Converting a project is mostly mechanical, and we provide agent skills that do it for you. You can then build, test, and publish the result with a single `bauplan run` command on an isolated data branch.

This page shows what a dbt project looks like after the conversion, what changes, and how to migrate yours.

## Side by side

The example below has two models: `titanic_passengers` selects from a raw table, and `survival_by_class` reads from `titanic_passengers`.

```sql title="models/titanic_passengers.sql"
select
    passengerid as passenger_id,
    pclass as passenger_class,
    survived
from raw.titanic
```

```sql title="models/survival_by_class.sql"
select
    passenger_class,
    count(*) as passenger_count,
    avg(cast(survived as float)) as survival_rate
from {{ ref('titanic_passengers') }}
group by passenger_class
```

```yaml title="models/schema.yml"
models:
  - name: titanic_passengers
    columns:
      - name: passenger_id
        data_tests:
          - unique
      - name: passenger_class
        data_tests:
          - not_null
          - accepted_values:
              values: [1, 2, 3]
```

```sh
dbt run
dbt test
```

```sql title="titanic_passengers.sql"
-- bauplan: output_schema=PassengerSchema
select
    passengerid as passenger_id,
    pclass as passenger_class,
    survived
from raw.titanic
```

```sql title="survival_by_class.sql"
-- bauplan: output_schema=SurvivalSchema
select
    passenger_class,
    count(*) as passenger_count,
    avg(cast(survived as float)) as survival_rate
from titanic_passengers
group by passenger_class
```

```python title="schemas.py"
from typing import Annotated

import bauplan
from bauplan import TableField

class PassengerSchema(bauplan.TableSchema):
    """One row per Titanic passenger."""

    passenger_id: bauplan.Int64
    # No `| None`: the column is required and may not contain nulls
    passenger_class: Annotated[
        bauplan.Int64,
        TableField(doc="Class of the Titanic passenger"),
    ]
    survived: bauplan.Int64 | None

class SurvivalSchema(bauplan.TableSchema):
    """Survival rate aggregated by passenger class."""

    passenger_class: bauplan.Int64
    passenger_count: bauplan.Int64
    survival_rate: bauplan.Float64 | None
```

```python title="expectations.py"
from typing import Annotated

import pyarrow

import bauplan
from bauplan.standard_expectations import (
    expect_column_accepted_values,
    expect_column_all_unique,
)

class PassengerKeys(bauplan.TableSchema):
    """Columns checked by the passenger expectations."""

    passenger_id: bauplan.Int64
    passenger_class: bauplan.Int64

@bauplan.expectation()
@bauplan.python("3.11")
def test_titanic_passengers(
    data: Annotated[
        pyarrow.Table,
        bauplan.Model("titanic_passengers", projection_schema=PassengerKeys),
    ],
) -> bool:
    """Passenger ids are unique and passenger classes are 1, 2 or 3."""
    return expect_column_all_unique(data, "passenger_id") and expect_column_accepted_values(
        data, "passenger_class", accepted_values=[1, 2, 3]
    )
```

```sh
bauplan run
```

## What changes

| | dbt | Bauplan |
| --- | --- | --- |
| Model code | SQL with Jinja `ref()` macros | Plain SQL, upstream models referenced by name |
| Contracts and tests | YAML, run by `dbt test` as separate queries | Typed Python schemas and expectations, checked by `bauplan run` |
| Failure handling | Tests run after data is materialized | A failing run stays on its [data branch](/concepts/git-for-data) and never reaches `main` |