dbt-to-dataform-cli
Migrate your dbt project to Dataform (BigQuery) in one command.
dbt (data build tool) has become the default way to do SQL transformation in a modern ELT pipeline. If your stack runs on Google Cloud, Dataform is BigQuery's native equivalent for analytics engineering — the same ideas (version-controlled SQL, a dependency graph, built-in tests) without a separate tool to run and maintain. dbt-to-dataform-cli automates the mechanical, repetitive part of moving a project from one to the other, straight from the command line.
pip install dbt-to-dataform-cli
dbt-to-dataform convert path/to/dbt_project -o path/to/dataform_project
No web upload, no zip files: point it at a dbt project directory and it writes a Dataform project next to it, plus a migration report telling you exactly what was converted automatically and what still needs a manual pass.
Why use this
- A real CLI, not a web app.
pip installit, run it locally, wire it into a script, a Makefile, or a CI pipeline. Your SQL never leaves your machine. - Nothing is silently dropped. Anything the converter can't handle
(complex Jinja macros, advanced dbt tests, dbt packages) is left in the
output wrapped in a clear
MANUAL REVIEW NEEDEDmarker and listed in the migration report — you always know exactly what to check by hand. - Validated on a real project. Every converter is tested end-to-end against jaffle_shop, dbt Labs' own reference project — not just unit tests on toy snippets.
- Actively maintained, with CI. Lint and the full test suite run across Python 3.9–3.12 on Linux and Windows on every commit; see the badge above.
Example
A typical dbt staging model:
-- models/staging/stg_customers.sql
{{ config(materialized='view') }}
select
id as customer_id,
first_name,
last_name
from {{ source('ecom', 'raw_customers') }}
becomes:
// definitions/staging/stg_customers.sqlx
config {
type: "view"
}
select
id as customer_id,
first_name,
last_name
from ${ref("raw_customers")}
— materialization, ref()/source() calls, and (if declared in
schema.yml) unique/not_null assertions all translated automatically.
See What it converts below for the full picture.
What it converts (MVP scope)
| dbt | Dataform | Status |
|---|---|---|
models/**/*.sql with {{ ref(...) }} |
definitions/**/*.sqlx with ${ref(...)} |
✅ implemented |
Materialization config (table/view/incremental) from dbt_project.yml and inline {{ config(...) }} |
config { type: ... } block |
✅ implemented |
{{ source(...) }} |
${ref(...)} |
✅ implemented (flagged for review — verify the source declaration) |
sources.yml |
sources.js declarations |
✅ implemented |
unique / not_null tests in schema.yml |
uniqueKey / nonNull assertions |
✅ implemented |
| Migration report (what needs manual review) | MIGRATION_REPORT.md |
✅ implemented |
Out of scope for the MVP: dbt packages (dbt_utils, ...), complex Jinja
macros ({% for %}, {% if %}, custom macros), dbt Cloud–specific
features, auto-generated documentation. Anything in this category is left
in the generated file wrapped in a MANUAL REVIEW NEEDED marker and listed
in the migration report — never silently dropped.
Usage
dbt-to-dataform convert ./my_dbt_project -o ./my_dataform_project
Options:
-o, --output PATH— where to write the converted project (default:./dataform_output)--dry-run— run the conversion and print the summary without writing any files
The output layout:
my_dataform_project/
definitions/
staging/
stg_customers.sqlx
...
customers.sqlx
orders.sqlx
sources.js # only written if the dbt project has a sources.yml
MIGRATION_REPORT.md
Sources
Every source table declared under sources: in any schema.yml/sources.yml
becomes a declare({...}) block in definitions/sources.js, keyed by the
dbt source table's name: — matching what {{ source(...) }} calls get
rewritten to in the converted models. Two things are flagged for manual
review rather than converted automatically:
identifier:overrides — Dataform'sdeclare()has no separate alias concept, so the physical table name is used asname:, and you'll need to update the generated${ref(...)}call in the model(s) that used to reference it by its dbt name.freshness:checks — Dataform has no built-in equivalent; recreate with a custom assertion if you rely on them.
Tests
unique and not_null column tests in schema.yml are converted into an
assertions: { ... } block nested inside the model's config { ... }:
config {
type: "table",
assertions: {
uniqueKey: ["customer_id"],
nonNull: ["customer_id"]
}
}
not_nullon multiple columns all collect into onenonNullarray — each check is independent, so this is a direct, lossless mapping.uniqueis trickier: Dataform'sassertions.uniqueKeyis a single composite key per model, while dbt lets every column carry its own independentuniquetest. If more than one column has auniquetest, only the first is converted automatically; the rest are flagged for manual review (they need their own separate assertion definition).- Anything beyond
unique/not_null—accepted_values,relationships, custom generic tests, dbt package tests (dbt_utils,dbt_expectations, ...) — is explicitly out of MVP scope and flagged in the migration report rather than silently dropped.
Trying it on a real project
This repo is validated against jaffle_shop, dbt Labs' official demo project:
git clone https://github.com/dbt-labs/jaffle-shop test-input
dbt-to-dataform convert ./test-input -o ./dataform_output
Development
python -m venv .venv
.venv/Scripts/activate # Windows
pip install -e ".[dev]"
pytest
ruff check src tests
CI runs on every push/PR to main (.github/workflows/ci.yml):
lint (ruff), tests across Python 3.9–3.12 on Linux and Windows, then a
package build + twine check to catch anything that would break pip install.
Roadmap
- ✅ Model
.sql->.sqlxconversion, materialization config - ✅
sources.yml->sources.js - ✅
schema.ymltests (unique,not_null) -> assertions - 🚧 Richer migration report (HTML output)
Contributing
Issues and PRs are welcome. This project is intentionally small in scope,
so a good place to start is the Roadmap above, or any dbt
project pattern the converters don't handle yet — open an issue with a
minimal repro. Please add a test alongside any change (see tests/) and
run pytest and ruff check src tests before opening a PR.
License
MIT — see LICENSE.
About
Built by Zinelabidine Chiguer — freelance Data Engineer specializing in BigQuery and Dataform on Google Cloud. Open to dbt → Dataform migration projects; feel free to reach out via GitHub.
Metadata
Release files for dbt-to-dataform-cli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dbt_to_dataform_cli-0.1.0.tar.gz | 18.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dbt_to_dataform_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.5 kB
Release files / dbt_to_dataform_cli-0.1.0.tar.gz
| Download URL | dbt_to_dataform_cli-0.1.0.tar.gz |
|---|---|
| Size | 18.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b2b03c97ce29af343c1d7abd337cadaa2057ef625ad17af5c8a71f186a004474
|
|
BLAKE2b-256 checksum How to use checksums |
d4de18ef21d879cb872f5aecbfd9b10bf8605d6801b99b6ab7d302dddfb9cb89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Release files / dbt_to_dataform_cli-0.1.0-py3-none-any.whl
| Download URL | dbt_to_dataform_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a2923247862693bbb5b9d28e750817c08add006d3a94044bd05cb87218729796
|
|
BLAKE2b-256 checksum How to use checksums |
109f53c9543cf7b1fd9514f7816fa35cbab457b98fd8f042905bd0ba4f7977e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|