Migrate from dbt Semantic Layer
Convert your dbt semantic models and MetricFlow metrics into governed Malloy data models
The dbt Semantic Layer already expresses your business in entities, dimensions, measures, and metrics — which maps unusually cleanly onto Malloy. Credible reads your MetricFlow definitions, flattens the measure/metric split into Malloy measures, and — where you connect it — validates each metric against the Semantic Layer's own query engine.
Still weighing the move? Credible vs. dbt sets the two side by side.
You don't have to move your transformations to start. The on-ramp is a governed model over the marts dbt already builds — your descriptions and tests read as prior art, and your project is the input, not a rewrite. Transformations can move into the model later, where a #@ persist annotation replaces a model file, its config, and the orchestration run.
What Credible Reads
Your semantic models (semantic_models: YAML — entities, dimensions, measures) and metrics (metrics: YAML — simple, ratio, derived, cumulative). The agent parses that project YAML directly — the fullest-fidelity input, since the YAML carries expr, filter, and agg_time_dimension details the compiled APIs abstract away. Connecting the official dbt MCP server (get_metrics, get_dimensions, get_entities, execute_sql) or the Semantic Layer APIs (GraphQL / JDBC / Python) is optional and adds live metric validation.
What Comes Across
The everyday modeling carries over. The bigger pieces land like this:
| In dbt | In Credible |
|---|---|
| Semantic models (entities, dimensions, measures) | Malloy sources, joins, dimensions, and measures |
| Metrics (simple / ratio / derived) | Measures — no separate measure-vs-metric layer to maintain |
Cumulative metrics & metric_time | Rebuilt as window views over explicit date fields |
| Metric descriptions | #(doc) / #(index), compressed into the engine's concept index |
| Governance (none field-level today) | Fine-grained access control in the model, versioned and enforced at the gateway on every query |
| Semantic Layer API consumers | Directly queryable on every surface — agents over MCP, data apps, notebooks, dashboards, APIs |
The Migration Flow
Credible reads the semantic models and metrics, translates entities to keys/joins and measures/metrics to Malloy measures, enriches with #(doc)/#(index) tags, and — where you connect it — validates by querying each metric through the Semantic Layer API at the same grain and diffing the results. MetricFlow owns SQL generation, so its result is the number to match.
What Credible Handles
- The measure/metric split flattens into one set of Malloy measures. A
simplemetric is often just a rename of its measure — Credible won't create two Malloy fields where one belongs. metric_timeand the time spine have no Malloy equivalent. Credible chooses the concrete date field per view and truncates with.month/.day; cumulative metrics are rebuilt as view-level window calculations.- Derived and ratio metrics reference other metrics, not columns. Credible resolves the dependency chain (metric → metric → measure → column) before writing the Malloy expression.
Before & After
dbt semantic model and metrics:
semantic_models:
- name: orders
model: ref('fct_orders')
defaults:
agg_time_dimension: order_date
entities:
- name: order_id
type: primary
- name: customer
type: foreign
expr: customer_id
dimensions:
- name: order_date
type: time
type_params: { time_granularity: day }
- name: status
type: categorical
expr: order_status
measures:
- name: order_total
description: Sum of order amounts
agg: sum
expr: amount
metrics:
- name: revenue
type: simple
type_params: { measure: order_total }
- name: completed_revenue
type: simple
type_params: { measure: order_total }
filter: "{{ Dimension('order__status') }} = 'completed'"
- name: completion_rate
type: ratio
type_params: { numerator: completed_revenue, denominator: revenue }source: orders is conn.table('analytics.fct_orders') extend {
primary_key: order_id
join_one: customers is conn.table('analytics.dim_customers') on customer_id
dimension:
#(doc) Order status
#(index)
status is order_status
measure:
#(doc) Total revenue (USD)
# currency
revenue is sum(amount)
#(doc) Number of orders
order_count is count(order_id)
#(doc) Revenue from completed orders
# currency
completed_revenue is sum(amount) { where: status = 'completed' }
#(doc) Share of revenue from completed orders
# percent
completion_rate is completed_revenue / revenue
view:
#(doc) Revenue by month
revenue_by_month is {
group_by: order_date.month
aggregate: revenue
}
}The two simple metrics collapse into their measures rather than duplicating them; completion_rate becomes a straight ratio; and the Jinja filter becomes a { where: … } on the measure.
More than a reformat. Your metrics stop being a metrics API and become directly queryable, composable, AI-discoverable through the AI Analytics Engine, and deliverable to every surface. See what you gain →