+1 (726) 227-2971

dbt + Looker in 2026: Where Should Transformations Live?

Every Looker team that also runs dbt eventually has the same argument: should this transformation be a PDT in LookML or a model in dbt? And then, a year later: why do we have both, and why do they disagree? Here is the split we recommend in 2026, and how to get there from a project that grew the other way.

What each layer is good at

dbt (or Dataform, or SQLMesh; the argument is the same) is a transformation framework. It turns raw tables into clean, tested, documented tables in the warehouse. Its strengths are tests (not_null, unique, accepted_values, custom), lineage, incremental materialization with full control over the SQL, environments, and a Git workflow that data engineers already use. Its output is tables any tool can read.

LookML is a semantic layer. It defines how tables join, what the metrics are, who can see which rows, and how fields are labeled for humans and, now, for Gemini. Its strengths are reuse (define total_revenue once), governance, row-level security, caching, and the fact that every consumer (dashboards, embeds, API, Conversational Analytics) goes through it. Its derived tables (SQL-based or native) can also transform data, which is where the overlap comes from.

The overlap: metrics layers and derived tables

Two features blur the line. dbt's semantic layer (MetricFlow) lets you define metrics in dbt and query them from other tools. Looker's derived tables let you transform data in LookML. Teams end up with revenue defined in both places, or with a heavy transformation living in a PDT because it was quicker than opening a dbt pull request.

Our view: pick one home for each kind of logic and be strict about it. For Looker shops, metrics belong in LookML, because Looker is where they are consumed and governed, and because Gemini in Looker can only use what is in LookML. Transformations belong in dbt, because dbt tests and documents them and because other tools need the results.

A pragmatic split

LogicLives inWhy
Cleaning, deduplication, type casting, renaming raw columnsdbt staging modelsTested once, reused everywhere
Business entities (customers, orders, subscriptions) as wide tablesdbt martsOther tools, notebooks and reverse ETL need them
Slowly changing dimensions, snapshotsdbt snapshotsdbt has the machinery; LookML does not
Heavy aggregations reused outside LookerdbtTested, scheduled, shared
Joins between entities, relationships, fan-out handlingLookML exploresThat is what explores are for
Metric definitions (total_revenue, active_users)LookML measuresOne definition, every consumer
Row-level security, user attributesLookMLIt must apply to every query path
Field labels, descriptions, hidden fieldsLookMLThis is the interface for humans and AI
Rollups that only Looker dashboards needLookML aggregate_tableTransparent, query-matched, no dbt run required
Analyst-specific reshaping (cohorts for one explore)LookML native derived tablesReuses model definitions; cheap to change

The test for a derived table that is arguing to stay in Looker: is it reused outside Looker, is it more than a screen of SQL, or does finance reconcile against it? Any yes means dbt.

Wiring them together

A few practices make the two layers feel like one system:

Generate LookML views from dbt models. Tools such as dbt2looker (and several successors) read dbt's manifest.json and emit view files with dimensions, types and descriptions taken from dbt's YAML. Descriptions written once in dbt become Looker field descriptions, which Gemini then uses. Even if you do not automate it, adopt the convention that a dbt column description is the source of truth.

Trigger Looker from dbt, not from timers. Have your dbt run write a completion marker (a row in a dbt_runs table, or just MAX(updated_at) on a mart), and point every Looker datagroup's sql_trigger at it:

datagroup: dbt_marts {
  sql_trigger: SELECT MAX(completed_at) FROM analytics.dbt_run_results WHERE status = 'success' ;;
  max_cache_age: "24 hours"
}

Caches clear and aggregate tables rebuild exactly once per successful dbt run, and never in the middle of one.

Test on both sides. dbt tests protect the tables; LookML data tests protect the metrics (test: with assert: on an explore). Run both in CI; see CI for LookML.

Name things the same. If the dbt mart is fct_orders, the view is orders with sql_table_name: analytics.fct_orders; the explore is orders. A mapping document nobody maintains is worse than a naming convention everybody follows.

Migrating from PDT sprawl

Most projects we inherit have dozens of PDTs that accumulated because dbt was not yet in place or was owned by another team. Moving them is mechanical if you go in order:

  1. Inventory. List every derived table, its persistence strategy, build time (from the PDT Event Log in System Activity), and what depends on it.
  2. Classify with the table above. Expect roughly a third to stay as native derived tables or aggregate tables, and the rest to move.
  3. Move the leaves first. PDTs that nothing else depends on become dbt models; point the Looker view's sql_table_name at the new table and delete the derived_table block. Compare row counts and a few measures before and after.
  4. Replace dashboard rollups with aggregate_table. Many PDTs exist only to make a dashboard fast; aggregate awareness does that without a separate view.
  5. Convert remaining SQL derived tables to NDTs where they reuse model logic; see Native Derived Tables Done Right.
  6. Switch datagroups to the dbt trigger and delete every persist_for.

The result is a LookML project that reads like a semantic layer again, a dbt project that owns the transformations with tests, and a BigQuery (or Snowflake) bill that reflects one build per source change instead of many.

If your project is somewhere in the middle of this journey and you would like a second pair of hands, our LookML consultants have done this migration many times. Get in touch.