Migration
8 min read

Migrating from Metabase to Bruin DAC

How to decide whether a Metabase dashboard belongs in self-hosted Bruin DAC or Bruin Cloud, what to migrate first, and why the move is more about metric ownership than chart widgets.

Arsalan Noorafkan

Developer Advocate

Metabase is one of those tools that quietly becomes part of the furniture.

Someone builds a useful dashboard. Then a collection. Then a few saved questions that feed other dashboards. A year later, finance has one version of revenue, growth has another, and everyone is being very polite about the fact that both numbers look plausible.

Moving from Metabase is not really about replacing a chart canvas. It is about deciding where the query logic, metric definitions, and dashboard ownership should live next.

For some teams, that answer is Bruin DAC: dashboard files in Git, SQL and semantic definitions beside them, then a self-hosted dashboard on infrastructure they control. For others, it is Bruin Cloud dashboards: a configured AI agent builds the dashboard from a conversation, the team iterates in chat, and the result goes through a draft and publish flow.

Those are different workflows. And DAC now has a Metabase importer, which means you do not need to begin by manually recreating every native SQL card. You still need to review the result. But the first draft can come from the dashboard you already have.

First, do not migrate the whole collection

Most Metabase instances contain a few dashboards that run the business, a few that are useful when somebody remembers they exist, and a long tail of experiments that should have stayed experiments.

The move is not to recreate all of it in a nicer place. The move is to keep the small set of standing views that deserve maintenance, then let people ask the long tail of questions directly.

The best first dashboard has a clear owner, stable reporting tables, and a predictable audience. Think weekly revenue, daily operations, support queues, or a product-health view. Avoid the dashboard whose chart titles say things like v4 FINAL fixed and where the original author has left the company. That dashboard is a discovery project, not a migration candidate.

Before you build anything, make a small inventory: every card, its source tables, filters, default date range, aggregation, sort order, business definition, and the person who can say whether the number is right. It feels a little tedious. It is also the part that prevents a migration from becoming a quiet rewrite of the business.

What actually moves over

The importer can move a Metabase dashboard into DAC YAML. It accepts a live Metabase URL plus an API key, a live URL plus a session token, or a saved GET /api/dashboard/:id response. That gets you a file, not a finished migration. The important parts still need an owner and a review:

  • The reporting tables or models behind each card.
  • The SQL or query-builder logic.
  • The business definition behind metrics such as revenue, active customer, or conversion.
  • Filters, defaults, time grain, sorting, and limits.
  • The owner, audience, and access expectations.

Native SQL cards usually import cleanly into metric, chart, and table widgets. DAC can also compile simple MBQL when the dashboard response includes enough source metadata. The easy mistake is treating that output as gospel. When five cards calculate revenue differently, a clean import just preserves the disagreement faster.

This is where a file-based semantic model helps. In DAC, you can define a source table, dimensions, metrics, and reusable segments in semantic/, then have dashboard widgets ask for net_revenue by month instead of carrying the whole expression around. Use the importer's --semantic option when the Metabase source already has explicit models and named metrics. Do not promote every dashboard aggregate into a company-wide metric just because the importer can see it.

Pick the importer route that matches your access

The live route is the obvious one when you have a read-only Metabase API key and a dashboard ID. Give dac import metabase the URL, dashboard ID, key, Bruin connection name, and output path. If your organisation creates session tokens instead, the command accepts one of those too.

The saved-response route is useful when the person who can access Metabase is not the person doing the migration, or when you want a repeatable fixture for a tricky dashboard. Capture the dashboard API response, keep it out of Git because it can contain sensitive metadata, then import it locally. Same generated YAML, different handoff.

I would import once without --strict, read every warning, and then use --strict in CI or repeatable imports. Unsupported cards otherwise become text placeholders. That is helpful during discovery and pretty dangerous if nobody looks at the output.

When self-hosted DAC is the better landing spot

DAC is for teams that want a dashboard to behave like the rest of their data work: a file in a repository, reviewed as a diff, validated before deployment, and served from infrastructure they manage. The importer is the front door, not the ownership model.

The project is deliberately boring:

analytics-dashboards/
  .bruin.yml
  dashboards/
    executive-revenue.yml
  semantic/
    orders.yml

DAC reads warehouse connections from .bruin.yml. Dashboard files can be YAML for a straightforward, declarative report or TSX when the layout needs code. dac validate checks the structure and semantic references. dac check goes further and executes every widget query against the real connection. dac build gives you a static artifact to compare with Metabase before you expose a live version.

That is a strong fit when the change is the thing you want to govern. A new filter, a change to the net-revenue expression, or a revised chart should be reviewable in the same pull request as the model or pipeline change that made it necessary.

It is also useful when the dashboard must stay inside your own network. Run dac serve near the warehouse for a live, query-on-demand dashboard, or run dac build to produce a static snapshot with the data baked in. The static option is handy for a board pack or an archived monthly report. Just remember that it is a snapshot, not a live dashboard.

When Bruin Cloud is the better landing spot

Bruin Cloud dashboards are for a different kind of report. You start with an agent that has the right project and connection set, open AI -> Dashboards, and describe what you want. The agent writes queries, chooses charts and tables, lays out the grid, and updates the draft as you keep talking to it.

That is not a way to skip verification. It is a way to stop making people learn a dashboard builder before they can get a useful first version.

A good migration prompt is specific about the contract you already captured:

Build an executive revenue dashboard from analytics.orders.
Use paid-order net_amount excluding tax as net revenue.
Show daily revenue, order count, average order value, and revenue by billing region.
Use UTC dates and a last-30-days default. Do not use gross_amount.

Then inspect the SQL behind the generated widgets. Ask the agent to adjust the draft when something is off. Publish only when the same comparisons you ran in Metabase pass. In Cloud, viewers see the published version while editors can keep working on a draft, which is a useful guardrail for dashboards people actually depend on.

This route works well for a business or operations team that needs a shared report but does not want every edit to begin with a Git branch. It also makes more sense for dashboards that will keep changing as people ask follow-up questions. There is no DAC-to-Cloud import step, so bring the approved migration worksheet into the first agent prompt and inspect the generated SQL just as carefully.

The practical choice is about ownership

Use DAC when the data team should own the dashboard definition through code review. Use Bruin Cloud when a broader team should be able to build and improve a shared dashboard with a configured agent. You can use both. A finance dashboard might live as DAC YAML with a stricter review process, while a sales leader's weekly exploration lives in Bruin Cloud and evolves through chat.

Neither option is a reason to keep every dashboard forever.

Some Metabase dashboards are really recurring reports. Move those. Some are proper operational monitors. Move those too. A lot are a question that somebody asked once, then never again. Let those die, or replace the request queue with an AI analyst that can answer the question when it comes back.

The cutover that does not ruin Monday morning

Run the old and new dashboard side by side for a short, explicit verification period. Compare the same filters, dates, daily totals, grouped totals, and table rows. When there is a difference, work backward from the source table and SQL. It is usually a timezone, an end-date boundary, an old cache, or a definition nobody had written down.

Once the owner signs off, give users the new link and leave a redirect note in Metabase. Do not delete the old dashboard on day one. There may be an embedded chart, a subscription, or a saved question hidden behind it. Archive it after the replacement has real users, then clean up the surrounding things deliberately.

The detailed version is in our step-by-step Metabase migration guide. Start with the dashboard people actually open on Monday morning. What does its revenue number really mean?

Sign up to our newsletter

Practical updates on open-source data pipelines, AI analysts, governance, and what we are shipping at Bruin.