Support
Log In

Migrate from dbt Semantic Layer

Convert your dbt semantic models and MetricFlow metrics into governed Malloy semantic 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.

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. Here's how the bigger pieces land — and where they get better:

In dbtIn 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_timeRebuilt as window views over explicit date fields
Metric descriptions#(doc) / #(index), indexed by the AI Analytics Engine
Governance (none field-level today)Fine-grained access control in the model, versioned and enforced everywhere
Semantic Layer API consumersDirectly 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 canonical number to match.

What Credible Handles

  • The measure/metric split flattens into one set of Malloy measures. A simple metric is often just a rename of its measure — Credible won't create two Malloy fields where one belongs.
  • metric_time and 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 become directly queryable and composable — not just a metrics API — AI-discoverable through the AI Analytics Engine, and deliverable to every surface. See what you gain →

Next Steps

On this page