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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dbt_to_dataform_cli-0.1.0.tar.gz.
File metadata
- Download URL: dbt_to_dataform_cli-0.1.0.tar.gz
- Upload date:
- Size: 18.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2b03c97ce29af343c1d7abd337cadaa2057ef625ad17af5c8a71f186a004474
|
|
| MD5 |
d009223f3a583e756d6df69012965a72
|
|
| BLAKE2b-256 |
d4de18ef21d879cb872f5aecbfd9b10bf8605d6801b99b6ab7d302dddfb9cb89
|
File details
Details for the file dbt_to_dataform_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dbt_to_dataform_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2923247862693bbb5b9d28e750817c08add006d3a94044bd05cb87218729796
|
|
| MD5 |
d1399a1a3c4b7109710fba4f3c3512a0
|
|
| BLAKE2b-256 |
109f53c9543cf7b1fd9514f7816fa35cbab457b98fd8f042905bd0ba4f7977e0
|