> ## Documentation Index
> Fetch the complete documentation index at: https://docs.credibledata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Looker

> Convert your LookML views, explores, and models into governed Malloy semantic models

Your LookML is years of encoded business logic — dimension definitions, measure formulas, join relationships, and the curation decisions behind them. Credible reads it as **prior art** and rebuilds the analytical domain as governed Malloy: the same metrics — validated to the row wherever a connection allows — plus the context an AI agent needs to answer the questions your explores couldn't.

LookML's UI patterns, Liquid templating, and performance-only constructs are identified and deliberately left behind.

## What Credible Reads

The agent inventories your LookML project — `manifest`, `model`, `view`, and `explore` files — resolving manifest constants as it goes. Give it the `.lkml` files and it has what it needs; it can also work from dashboards and other unstructured context when that's all you have. How far validation goes depends on what else it can reach:

* **LookML + live data** — with a warehouse connection (and, optionally, the Looker API), LookML supplies the business context and the data *validates* each proposal against live results.
* **LookML only** — with no connection, LookML is the sole source of context and each proposal is flagged unvalidated until data confirms it.

## What Comes Across

The everyday modeling carries over. Here's how the bigger pieces land — and where they get better:

| In Looker                                     | In Credible                                                                                                                                         |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Views & explores                              | Malloy **sources**, joins folded in; `import`/`export` and the `explores` manifest curate what's exposed                                            |
| Dimensions & measures (filtered, ratio, time) | The everyday building blocks, carried over                                                                                                          |
| Persistent & native derived tables (PDTs)     | **Pipelined queries and query-as-source** — the same transformation, minus the PDT build schedules, datagroups, and cascading rebuilds              |
| `access_grant`, model & explore permissions   | **Fine-grained access control, defined in the model and versioned in Git**, enforced on every surface                                               |
| `description:` and labels                     | `#(doc)` / `#(index)` tags, indexed by the [Context Engine](/how-to/analyzing/overview) so an agent can find the right field and use it correctly   |
| Dashboards & Looks                            | Rebuilt as [data apps](/how-to/analyzing/data-apps) or [notebooks](/how-to/analyzing/workspaces) — interactive, and not capped at Looker's tile set |
| Liquid SQL templating                         | **Real typed Malloy expressions** — no SQL string-templating to write or debug                                                                      |
| Drill fields, HTML, viz styling               | Dropped as Looker-specific; genuine logic (e.g. thresholds) is kept                                                                                 |

## The Migration Flow

<Steps>
  <Step title="Read">
    Inventory every `.lkml` file, categorize it, and extract source and join candidates with prior-art notes. The `explore`/`view` split collapses into a single Malloy source: joins move from the explore into the source, and `relationship: many_to_one` becomes `join_one`.
  </Step>

  <Step title="Translate">
    Extract field-level proposals from each view — dimensions and measures with a `lookml` provenance — and convert derived tables and struct/`UNNEST` joins. Apply the [keep / skip / flag triage](/how-to/migrating/overview#what-the-agent-keeps-skips-and-flags): keep aggregation formulas, join cardinality, and `CASE` logic; skip `drill_fields`, `html:`/Liquid, and PDT optimization keys; flag 50-line SQL dimensions and synthetic primary keys.
  </Step>

  <Step title="Enrich">
    Rewrite each LookML `description:` into a `#(doc)` tag that tells an agent what the field means and how to use it, `#(index)` the categorical dimensions, and map LookML visibility (`hidden`, `fields` exclusions, `required_access_grants`) to Malloy access modifiers and [access control](/how-to/modeling/fine-grained-acls).
  </Step>

  <Step title="Validate">
    Confirm numeric parity and produce a [coverage report](/how-to/migrating/overview#proving-parity) — what was modeled, renamed, rearchitected, deferred, or skipped, and why.
  </Step>
</Steps>

## What Credible Handles

* **Liquid and HTML** — `{% … %}` templating and `html:` conditional formatting are stripped; their *intent* is noted, and re-created as a renderer annotation only if it belongs in the model.
* **Persistent derived tables** — classified as transformation, aggregation, or performance-only. Perf-only PDTs are skipped in favor of querying the base table directly; real transformations become query-based sources.
* **Refinements** (`+view`) — consolidated into one definition rather than layered, so there's a single source of truth per field.
* **Synthetic keys** — a `primary_key` built from `concat()` or `generate_uuid()` is flagged so you can confirm the real grain instead of baking in a workaround.

## Proving Parity

Two channels, used together:

1. **Looker API** — run the original explore through the API and compare. This requires the service account to satisfy the explore's `required_access_grants`, or restricted explores return 404 — so the agent preflights access first.
2. **SQL against the same warehouse** — run equivalent SQL directly against the warehouse the LookML reads and diff it against the Malloy result. This is the channel that validates the numbers in practice, with or without API access.

## Before & After

A LookML view and explore:

```lookml theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
view: orders {
  sql_table_name: sales.orders ;;

  dimension: order_id {
    primary_key: yes
    type: number
    sql: ${TABLE}.order_id ;;
  }

  dimension: status {
    label: "Order Status"
    description: "Current fulfillment status of the order"
    type: string
    sql: ${TABLE}.order_status ;;
  }

  dimension: order_size {
    type: string
    sql: CASE
           WHEN ${TABLE}.amount >= 100 THEN 'large'
           WHEN ${TABLE}.amount >= 20  THEN 'medium'
           ELSE 'small'
         END ;;
  }

  dimension_group: created {
    type: time
    timeframes: [date, week, month, year]
    sql: ${TABLE}.created_at ;;
  }

  measure: order_count {
    type: count
    drill_fields: [order_id, status, created_date]   # dropped — UI only
  }

  measure: total_revenue {
    label: "Total Revenue"
    description: "Total revenue in USD"
    type: sum
    sql: ${TABLE}.amount ;;
    value_format_name: usd
  }

  measure: cancelled_orders {
    type: count
    filters: [status: "cancelled"]
  }

  measure: cancellation_rate {
    type: number
    sql: 1.0 * ${cancelled_orders} / NULLIF(${order_count}, 0) * 100 ;;
    value_format_name: percent_1
    html: {% if value > 10 %}<span style="color:red">{{ rendered_value }}</span>{% endif %} ;;
  }
}

explore: orders {
  join: customers {
    type: left_outer
    sql_on: ${orders.customer_id} = ${customers.customer_id} ;;
    relationship: many_to_one
  }
}
```

The same domain in Malloy — one source, joins folded in, Liquid and drill fields dropped:

```malloy theme={"languages":{"custom":["/languages/motly.tmGrammar.json","/languages/malloy.tmGrammar.json"]}}
source: orders is conn.table('sales.orders') extend {
  primary_key: order_id
  join_one: customers is conn.table('sales.customers') on customer_id

  dimension:
    #(doc) Current fulfillment status of the order
    #(index)
    status is order_status

    #(doc) Order size bucket derived from amount
    order_size is
      pick 'large' when amount >= 100
      pick 'medium' when amount >= 20
      else 'small'

    #(doc) Date the order was placed
    created_date is created_at::date

  measure:
    #(doc) Number of orders
    order_count is count()

    #(doc) Total revenue in USD
    # currency
    total_revenue is sum(amount)

    #(doc) Orders that were cancelled
    cancelled_orders is count() { where: status = 'cancelled' }

    #(doc) Percentage of orders that were cancelled
    # percent
    cancellation_rate is cancelled_orders / order_count * 100

  view:
    #(doc) Monthly revenue trend with order counts
    monthly_revenue is {
      group_by: created_date.month
      aggregate: total_revenue, order_count
    }
}
```

The `type: time` dimension group becomes a single date dimension you truncate with `.month`/`.year` in a view — no enumerated timeframe list. `drill_fields`, the Liquid `html:` block, and `value_format_name` have no field-level model equivalent: drilling is implicit in Malloy, and formatting moves to `# currency`/`# percent` render tags.

**More than a reformat.** Off LookML, the model is AI-discoverable through the [Context Engine](/how-to/analyzing/overview), composes into questions your explores couldn't answer, and is open code you own rather than logic locked in Looker. [See what you gain →](/how-to/migrating/overview#more-than-a-reformat)

## Next Steps

<CardGroup cols={2}>
  <Card title="Migration Overview" icon="diagram-project" color="#5C7A93" href="/how-to/migrating/overview">
    The method behind every migration
  </Card>

  <Card title="Metadata & Discovery" icon="tags" color="#94793A" href="/how-to/modeling/metadata-tags">
    Tune your migrated model for AI retrieval
  </Card>
</CardGroup>
