Skip to content

dbt Import

Convert an existing dbt test suite into Qualytics quality checks. Point the CLI at a compiled manifest.json. Conversion happens in memory and the checks are created or updated in a datastore.

What gets imported?

Every generic test (not_null, unique, accepted_values, relationships, plus the dbt_utils and dbt_expectations packages) and every singular SQL test in the manifest becomes a quality check. Nothing is skipped: tests without a direct rule mapping arrive as satisfiesExpression drafts carrying their dbt source, ready for a reviewer to finish. See Quality Checks for how checks work in general.

Commands

Command Description
dbt plan Preview what a manifest would import as (offline, no authentication)
dbt import Convert a manifest and create or update the checks in a datastore

Workflow

graph LR
    D[dbt compile] --> M[manifest.json]
    M -->|dbt plan| P[Preview tiers and mappings]
    M -->|dbt import| Q[(Qualytics datastore)]
  1. Compile your dbt project. dbt compile writes target/manifest.json.
  2. Run qualytics dbt plan to preview, then qualytics dbt import to apply.

Only manifest.json is needed. It carries no database credentials (those live in profiles.yml, which dbt never compiles into the manifest), but it does include compiled SQL and full schema lineage, so treat it like source code.

Plan

Preview the import before touching anything. plan reads the manifest locally, so it works offline and in CI without credentials.

qualytics dbt plan --manifest target/manifest.json
qualytics dbt plan --manifest target/manifest.json --show-checks
Plan

• All 26 dbt tests convert to 28 Qualytics checks.
• 25 (89%) map to a rule automatically.
• 3 (11%) need an expression authored by hand.

▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌

Tier       Checks  Status
direct         19  Active  maps 1:1 to a Qualytics rule
normalize       6  Draft   mapped, but a parameter needs review
manual          3  Draft   custom SQL, authored by hand

Note: some dbt tests translate to more than one Qualytics check.
Option Type Default Required Description
--manifest, -m TEXT target/manifest.json No Path to the compiled dbt manifest.json
--container-map TEXT - No Override a container name: model=container (repeat for multiple)
--container-case TEXT - No Force container name case: upper or lower
--show-checks FLAG false No List every dbt test and the rule it maps to

Use --show-checks to see the full mapping for your project

The per-test listing shows each dbt test alongside the Qualytics rule it becomes and its tier, so you can spot anything that needs hand-finishing before you import.

Import

Convert the manifest and create or update the checks in one or more datastores. Unlike plan, import talks to your Qualytics instance, so it needs authentication and a synced datastore.

# Preview
qualytics dbt import --manifest target/manifest.json --datastore-id 42 --dry-run

# Apply
qualytics dbt import --manifest target/manifest.json --datastore-id 42

# Also write the converted YAML for git review
qualytics dbt import --manifest target/manifest.json --datastore-id 42 \
    --emit-yaml ./qualytics-config/checks/
Option Type Default Required Description
--datastore-id INTEGER - Yes Target datastore ID (repeat the flag for multiple)
--manifest, -m TEXT target/manifest.json No Path to the compiled dbt manifest.json
--dry-run FLAG false No Preview what would be created or updated
--container-map TEXT - No Override a container name: model=container (repeat for multiple)
--container-case TEXT - No Force container name case: upper or lower
--status TEXT - No Force every check to Active or Draft, overriding the tier default
--preserve-status FLAG false No Omit status so re-imports keep what is set in the platform
--validate-fields / --no-validate-fields FLAG --validate-fields No Check field names against the synced schema and correct their casing
--emit-yaml TEXT - No Also write the converted checks to this directory
--failures-log TEXT dbt-import-failures.log No Write checks that failed to import (which test, why) to this file

--status and --preserve-status are mutually exclusive.

Conversion tiers

Every dbt test converts. Tiers grade effort, not feasibility.

Tier Meaning Default status
direct Deterministic 1:1 mapping Active
normalize Mapped, but a parameter needs a human eye Draft
manual Custom SQL. The expression must be authored by hand Draft

Nothing is skipped. An unrecognized generic test or a singular SQL test still produces a check, as a satisfiesExpression in Draft with the dbt source recorded in its metadata, so a reviewer adapts it in Qualytics rather than going back to the dbt project to find it.

Status is yours to set

The tier-to-status mapping above is the default, not a policy. --status overrides it wholesale:

# Review everything before anything fires
qualytics dbt import -m target/manifest.json --datastore-id 42 --status Draft

# Activate everything, including checks that need editing
qualytics dbt import -m target/manifest.json --datastore-id 42 --status Active

The default exists because manual checks carry an empty expression, and normalize checks carry an assumption a reviewer should confirm. For example, volumetric checks assume a single-day window because dbt has no equivalent concept. Those checks may not evaluate meaningfully until reviewed. --status Active prints how many fall into that group and then does what you asked.

Tests that become two checks

A few dbt tests assert two things at once, and split:

dbt test Becomes
expect_column_value_lengths_to_be_between minLength + maxLength
expect_column_value_lengths_to_equal minLength + maxLength (same value)

Each half gets its own check, so both are created and updated independently. A one-sided dbt test emits only the half it specifies: min_value alone produces just a minLength. Because of this, plan reports the check count and the dbt test count separately when they differ.

What carries across

where becomes filter. A dbt test scoped with config.where produces a check with the same SQL predicate in filter, which every Qualytics rule type supports. Dropping it would run the check against exactly the rows dbt was told to exclude.

Everything else lands in metadata. dbt facts with no direct Qualytics field are recorded on the check rather than discarded, so a reviewer completing a Draft never has to reopen the dbt project:

filter: status != 'deleted'          # from config.where
additional_metadata:
  _qualytics_check_uid: dbt__test_jaffle_not_null_proportion_stg_orders_amount_abc
  dbt_unique_id: test.jaffle.not_null_proportion_stg_orders_amount.abc
  dbt_test: dbt_utils.not_null_proportion
  dbt_package: jaffle
  dbt_severity: warn                 # only when it differs from dbt's default
  dbt_tags: nightly
  dbt_limit: 500
  dbt_kwargs: '{"at_least": 0.95}'   # every argument the mapping did not consume
  dbt_compiled_sql: ...              # singular tests

dbt_kwargs is the important one: when a rule maps but its parameters don't, the original thresholds stay visible on the check itself. Tags arrive as a comma-separated string and dbt_kwargs as a JSON-encoded string, so parse the latter before processing its values.

Idempotency

Each check's _qualytics_check_uid is derived from the dbt test's unique_id, so re-running import after the dbt suite changes updates checks in place rather than duplicating them. This also means several dbt tests on the same container, rule, and field (say, two expression_is_true tests on one column) stay separate checks instead of overwriting each other.

Status on re-import

By default import sets the status from the tier, which means a re-import resets a check someone activated in the platform back to Draft. Two ways to handle it:

  • Config-as-code: treat the YAML written by --emit-yaml as the source of truth. Activate by editing status in the emitted file, not in the web app. See Config as Code.
  • --preserve-status: omit status entirely so the importer keeps whatever each check currently has.

Supported dbt tests

Native dbt, dbt_utils, and dbt_expectations generic tests are mapped. Highlights:

dbt test Qualytics rule Tier
not_null notNull direct
unique unique direct
accepted_values expectedValues direct
relationships existsIn direct
dbt_utils.unique_combination_of_columns unique direct
dbt_utils.accepted_range between direct
dbt_utils.expression_is_true satisfiesExpression normalize
dbt_utils.relationships_where existsIn normalize
dbt_expectations.expect_column_values_to_match_regex matchesPattern direct
dbt_expectations.expect_table_row_count_to_be_between volumetric normalize
dbt_expectations.expect_row_values_to_have_recent_data freshness direct
dbt_expectations.expect_column_value_lengths_to_be_between minLength + maxLength direct
Singular (bespoke SQL) tests satisfiesExpression manual
Anything unrecognized satisfiesExpression manual

Run qualytics dbt plan --show-checks against your own manifest for the complete per-test mapping.

dbt_utils.relationships_where deserves a note: its to_condition becomes the reference filter on the referenced container, its from_condition becomes the check's filter (combined with config.where when both are set), and dbt-utils' no-op 1=1 defaults are dropped rather than carried over. It lands as normalize so a reviewer can confirm both conditions are valid in the target datastore before activation.

Where to look next