Skip to content

Tableau to Power BI Migration: How to Inventory, Triage, and Rebuild Your Dashboards

The part of the migration that decides whether it succeeds: knowing what you have, deciding what to move, rebuilding it on a proper model, and proving the numbers match before cutover.

The usual first question in a Tableau to Power BI migration is “how many dashboards do we have?” Few teams can answer it with confidence. The Tableau site has workbooks from people who left, copies named “Final v3”, and a handful of reports that finance opens once a quarter and would notice within a day if they vanished. If you start rebuilding before you can tell these apart, you pay to migrate the clutter and you still miss the one report that matters.

This article is the sequence I would follow for the hands-on part of a migration, after the decision to move has been made. If you are still deciding, read the decision guide first. Everything here can be done without buying anything. Two files go with it: an inventory template and a Python script that pulls the workbook list from the Tableau REST API (it fills the first few columns only; the rest is manual work). The script needs Python 3 and the requests package.

Microsoft publishes its own Power BI migration series, written for any third-party BI tool. It defines five stages: gather requirements and prioritize, plan deployment, proof of concept, create and validate content, deploy, support and monitor. It is not Tableau specific, so it says nothing about LODs or workbook XML. What follows is the Tableau-side detail that series leaves out, ordered in seven steps.

Seven numbered steps: inventory, triage by use, document logic, build the model, rebuild in waves, validate, cut over and retire.

Step 1. Build the inventory

You need one row per workbook and published data source. The template has these columns: workbook, project, owner, last published, last viewed, views in the last 90 days, data sources, connection type, extract or live, custom SQL, calculated field count, LOD count, parameters, actions, row-level security, tier, decision, target Power BI workspace, validator, sign-off and notes. The first nine come from the platform. The next six (custom SQL through row-level security) come from reading the workbook. The rest are decisions you record. The three example rows in the CSV are marked EXAMPLE and are fake.

There are four places to get the data.

REST API for the list. The Query Workbooks for Site call returns, per workbook, the name, content URL, created and updated timestamps, and child elements for the project and the owner. It pages at up to 1,000 items per request (default 100). The owner comes back as an id only, so you need the users list to turn ids into names. The Query Workbooks for Site reference has the exact response. The script in the download does both calls and writes a CSV. Listing users needs an account that is allowed to see them, so run it as a site administrator.

Metadata API for dependencies. This is the GraphQL API that knows which workbooks sit on which databases and tables. On Tableau Server it is installed but disabled by default. A server admin turns it on from the initial node with tsm maintenance metadata-services enable, which requires Tableau Server 2019.3 or later and briefly restarts some services. The endpoint is /api/metadata/graphql, and it uses the same authentication token as the REST API (Get Started). The documented schema lists downstreamWorkbooks and upstreamDatasources on both databases and tables, so the “what depends on this database” question looks like this:

query WorkbooksOnDatabase {
  databases(filter: { name: "adventureworks" }) {
    name
    downstreamWorkbooks {
      name
    }
  }
}

Tableau’s own example queries databases and tables; I swapped in downstreamWorkbooks from the schema page. Run it in the GraphiQL explorer on your own server (https://<server>/metadata/graphiql/) before relying on it, because the schema differs between versions.

Usage numbers. On Tableau Cloud, Admin Insights is a project of pre-built data sources, including TS Events (audit data such as view access), TS Users and Site Content. By default it keeps up to 90 days of data, or up to 365 days with Advanced Management (Tableau Help, Admin Insights). Admin Insights is Cloud only. On Tableau Server use the built-in administrative views instead. Stale Content, in the next step, is the one I checked.

The workbook file itself. Calculated field counts, LOD counts, parameters and actions are not in the list APIs in any convenient form. A .twb is XML, so a script can count them, or you can open the workbook. That is slow, which is why you do it only for tiers one and two.

Step 2. Triage by use, not by opinion

Tableau’s Stale Content administrative view flags workbooks and data sources that have not been used within a window you choose. The window runs from 1 to 120 days, and you can tag up to 1,000 objects per batch (tagging arrived in 2020.3). With Advanced Management you can automate archiving (Stale Content).

The 120 day ceiling matters for migration. A report used at quarter close or once a year looks stale to this view, and it can look stale to any 90 day count too. Treat “no views” as a prompt for a conversation with the owner, not a verdict.

Table of four triage tiers, critical, active, long tail and dormant, each with its signal and default action.

TierSignalDefault action
CriticalFinance, regulatory or board use, named owner, calendar-driven peaksRebuild first, full validation, written sign-off
ActiveRegular use by a team, owner knownRebuild in waves, merge duplicates onto shared models
Long tailOccasional use, unclear ownerAsk the owner, rebuild only if it is cheap
DormantNo use in the agreed window and no calendar reasonArchive, do not migrate

I set the window per estate rather than copying someone else’s threshold. Before anything is archived, email the owner with a date and an easy way to object. Silence by that date is a decision you can defend. Microsoft’s guidance makes the same point from the other side: migrate the business question rather than reproduce every report as it looks today, and consider eliminating legacy items that have not been used for some time.

Step 3. Document the logic before you touch it

For each tier one and tier two workbook, write down:

  • Every calculated field, its formula, and the fields it references. Tableau calculations often reference other calculations, so build order matters.
  • Every parameter and what it drives.
  • Every filter, with context filters marked, since they change how FIXED expressions behave.
  • What one row of the underlying data represents, and anywhere Tableau is aggregating without being asked.
  • Every dashboard action.
  • User filters and any row-level security.

The grain question is the one that gets skipped. Tableau aggregates to the level of the view, so a number can be right for years because of a grain nobody wrote down. Power BI will not infer it for you. Rebuild the logic from what the number is meant to be. Do not translate formulas line by line.

What lives outside the workbook

The workbook list does not show these, and each needs a decision before cutover.

ItemWhat to do
Published data sources and extractsGive each published data source its own row in the inventory, with an owner, because several workbooks depend on it. An extract refresh schedule becomes a Power BI scheduled refresh, or a source-side load. If the source is on-premises, Microsoft’s deployment guidance says a new gateway data source registration might be needed.
Tableau Prep flowsI know of no converter. Treat each flow as a data source to rebuild in Power Query, in a Power BI dataflow, or upstream in the warehouse. Microsoft suggests a dataflow when the same preparation would otherwise be repeated across many files.
Subscriptions and alertsThese belong to individual users, not to the workbook, so list them before cutover (on Tableau Cloud, Admin Insights has a Subscriptions data source). Power BI email subscriptions need a Pro or Premium Per User license, or a workspace on paid capacity, and subscribing others needs Pro or PPU plus a Contributor, Member or Admin role. Power BI data alerts can only be set on gauge, KPI and card tiles on a dashboard, and only the person who sets one sees it. Expect some Tableau alerts to have no equivalent.
PermissionsTableau project and group permissions do not map one to one. Decide the Power BI workspace roles, app audiences and row-level security roles, and remember that RLS only restricts Viewers.
User communicationSay where the new content is, who to ask, and the date Tableau goes read-only. Microsoft’s guidance adds documentation, FAQs and a way to give feedback.

Step 4. Map the features, then build the model

Treat this table as a prompt for design decisions, not a translation service.

TableauUsual Power BI equivalentWhat to check
Calculated fieldDAX measure or calculated columnA measure is evaluated at query time in filter context. A column is stored per row. Pick on purpose.
FIXED / INCLUDE / EXCLUDE LODCALCULATE with ALLEXCEPT, REMOVEFILTERS, or a measure on a different grainDifferent evaluation model. See the worked example below.
ParameterWhat-if parameter, or a disconnected table read with SELECTEDVALUEParameters drive measures. Using one in a dimension calculation can give wrong values.
Data blendingRelationships in the model, or a Power Query mergeNeeds explicit relationships.
Dashboard actionsCross-filtering, drillthrough, bookmarks, buttonsNot identical. Redesign the interaction.
User filter / entitlement tableRow-level security rolesRe-test with real Viewer accounts.
NULLBLANKNot equivalent. See “What goes wrong”.

On parameters, Microsoft’s what-if documentation says a parameter creates a measure holding its current value, and notes that parameters are designed for measures within visuals, and that a parameter used in a dimension calculation might be calculated incorrectly (Microsoft Learn, what-if parameters). If a Tableau parameter filters a dimension, expect to redesign it.

On modeling, build the shared model before the reports. Microsoft’s star schema guidance on Microsoft Learn says dimension tables filter and group, fact tables summarize, and fact tables should load at a consistent grain. Tableau let several workbooks each carry their own joins and calculations. If you copy that habit into ten Power BI files you have rebuilt the sprawl you were trying to leave. Microsoft’s series also encourages separating the data model from reports so the model can be reused.

Worked example: one FIXED LOD in DAX

Tableau’s own help gives this expression: {FIXED [Region] : SUM([Sales])}. It computes total sales per region regardless of other dimensions in the view, and shows the same value on every State inside a region. Tableau documents that FIXED expressions ignore view filters other than context filters, data source filters and extract filters.

Assume a Sales fact table, a Geography dimension with Region and State, and a Product dimension. A starting-point measure:

Region Sales (Fixed) =
CALCULATE (
    SUM ( Sales[Sales Amount] ),
    ALLEXCEPT ( Geography, Geography[Region] ),
    REMOVEFILTERS ( Product )
)

How it reads. CALCULATE evaluates the sum in a modified filter context. ALLEXCEPT removes every filter on the Geography table except the one on Region, so a State filter coming from the visual is dropped but the Region stays. REMOVEFILTERS ( Product ) clears product slicers, to mimic the way a FIXED expression ignores ordinary view filters. The percent of region is then DIVIDE ( SUM ( Sales[Sales Amount] ), [Region Sales (Fixed)] ).

Where results can differ from Tableau:

  1. ALLEXCEPT keeps filters that exist. If Region is on the visual, the Region filter is there to keep. If only State is on the visual, there is no Region filter in context, and the measure returns the total across all regions for each state. Tableau computes the region of each row. Put the attribute on the visual, or design the model so the grain is explicit, and test both layouts.
  2. Context filters. Tableau honors context filters inside FIXED. There is no single DAX twin. Decide for each one whether it is really a model-level filter, a slicer you keep by leaving it out of REMOVEFILTERS, or a filter that should not exist.
  3. Filters on other tables. The measure only clears Product because that is what I told it to clear. A date slicer, a customer slicer, or RLS on another table still applies unless you handle it. Row-level security always applies, which is usually what you want, but it will differ from a Tableau extract that a developer built with a broader filter.
  4. Relationships. Filters reach Sales only through model relationships. A missing or inactive relationship changes the answer without an error.

Microsoft’s CALCULATE reference lists the filter modifiers, including ALLEXCEPT and REMOVEFILTERS, and is short enough to read in full. I deliberately kept this to documented functions. For INCLUDE and EXCLUDE, and for FIXED on a dimension that is not on the visual, the right pattern depends on your model, and I would build it in a small test model and compare against Tableau before trusting it.

Step 5. Rebuild in waves

Start with one low-stakes dashboard so the team learns the tooling and agrees conventions: naming, folder structure, which workspace holds what. Then move to tier one while energy is high, then the active tier. Group by business unit within a wave. Dashboards for one team tend to share sources and logic, and you can retire that team’s Tableau access cleanly when the wave is done.

Use a validator who did not build the report. A builder checks what they meant to build.

Step 6. Validate the numbers, then the behavior

Microsoft’s migration series names four things to validate: data accuracy, security, functionality and performance. It also notes that it is more common than you might think to find an error in the legacy report, so you must be able to explain a difference, not only remove it. Here is the checklist I use per tier one workbook. Tier two gets the same list with less sampling.

#CheckHowEvidence
1Totals matchPlain table visual in Power BI against the same view in Tableau, before any chartBoth screenshots with the data as-of time
2Detail rows matchSample rows across the highest, lowest and middle valuesRow list with pass or fail
3Filter combinationsAt least five combinations, not only the default viewCombination list
4Nulls and blanksRows with missing keys, empty strings and zeroNotes on how each is shown
5Date boundariesFirst and last day of a month, quarter, year and fiscal yearPass or fail per boundary
6LOD measuresEach converted LOD at several levels of the visualTable of results
7SecuritySign in as real Viewer accounts in each role, including one with no roleScreenshot per role
8InteractivitySlicers, drillthrough, bookmarks, buttonsPass or fail list
9RefreshScheduled refresh runs and lands before users need itRefresh history
10PerformanceOpens in the service, not only in DesktopObserved load time
11Business sign-offNamed owner agrees in writingEmail or ticket reference

Row-level security needs its own attention. Microsoft’s documentation says RLS restricts only users with Viewer permission, and does not apply to workspace Admin, Member or Contributor roles. A developer testing in their own workspace role sees everything. The Test as role feature uses your own identity when evaluating dynamic rules, so for external guests Microsoft recommends signing in as the actual guest user (RLS).

Step 7. Cut over, then retire

A one-page calendar. “T” is the date users are told Tableau becomes read-only. The offsets are starting points to stretch or shrink for your estate, not benchmarks.

WhenActionOwnerDone when
T minus 4 weeksFreeze changes to the Tableau workbook being replacedBI leadChange log closed
T minus 3 weeksFinal validation pass against the same data snapshotValidatorChecklist complete
T minus 2 weeksBusiness owner sign-off; send user notice with the date, link and who to askOwner, BI leadWritten sign-off on file
T minus 1 weekSecurity re-test with real accounts; confirm refresh scheduleValidator, AdminEvidence saved
TPublish the Power BI app or workspace link; redirect from the Tableau project; set Tableau content to read-onlyBI lead, AdminRedirect live
T plus 1 weekCheck usage and support tickets; fix what the checklist missedBI leadIssue list triaged
T plus 4 weeks or laterArchive the Tableau workbook with its documentation; turn off access for that teamAdminArchive confirmed

Microsoft’s deployment guidance says a parallel run is commonly 30 to 90 days, and lists when to decommission: the agreed period has passed, every user has access to the new content, activity on the old report has died down, and no issue is hurting productivity. Treat the 4 week offset as the short end for low-risk content and run tier one longer. For rollback, keep the Tableau workbook read-only and intact until that point rather than deleting it. Microsoft’s page also suggests keeping earlier versions of Power BI files (SharePoint or OneDrive versioning works) so you can revert the other way.

Keep the documentation from Step 3 with the archive. It is the record of what each dashboard was meant to do, and you will want it the first time someone asks why a number changed.

What goes wrong

Grain. Tableau aggregates to the level of the view. Power BI evaluates a measure in whatever filter context the visual creates. A number that matched on the default view drifts when someone adds a field. The fix is to write down the grain in Step 3 and test several layouts in Step 6.

NULL and BLANK. Microsoft’s DAX reference states that blanks are not equivalent to nulls, that DAX uses blanks for both database nulls and empty Excel cells, and that blanks and empty strings are not always equivalent. In its BLANK example, nulls and empty strings from SQL Server are treated as the same. Filters, slicers and color rules written for Tableau nulls need rechecking.

LOD context. As the example shows, the same intent gives different results depending on which columns are on the visual and which tables the filters come from. Test measures at more than one level.

Extract versus live. A Tableau extract is a snapshot refreshed on a schedule. A Power BI Import model is another snapshot on another schedule, and a live or DirectQuery source is neither. If the two refresh at different times, a comparison fails for reasons that have nothing to do with logic. Compare both against one pinned query on the source at a stated time.

Security re-test. Tableau user filters and Power BI RLS roles are not the same mechanism. Testing as an Admin or workspace Contributor proves nothing about a Viewer. Users who belong to no role typically see no data, which looks like a broken report to them.

Custom SQL and calculated fields that rely on the source engine. Workbooks that push logic into custom SQL or database functions carry that dependency to the new model. List them in the inventory (the template has a column) and decide whether the logic belongs in the source, in Power Query, or in DAX.

What I tested, and what I could not

The REST script was written against Tableau’s REST API reference for sign-in with a personal access token, Query Workbooks for Site and Get Users on Site. I ran it only against a small local mock server that returns XML in the documented shape. I have no Tableau Server or Tableau Cloud site, so I could not confirm behavior against a live deployment, any API version differences, or what your permissions let a token see. Non-administrators see only the workbooks they have Read permission for, so use an administrative account for a full inventory. Try it on a test site. The updatedAt value is when the workbook was last updated, which is close to, but not guaranteed to equal, “last published”. The Metadata API query is built from documented field names and is untested on a live server. The DAX measure was checked against Microsoft’s function reference and the Tableau help page, not run against a model and compared with Tableau output, so test it as described above.

Where Rapid Lens fits

Steps 1 and 3 are the manual slog, and the script and checklist above get you through them. If you would rather not do them by hand, Rapid BI Catalog connects to Tableau Server, Tableau Cloud and Power BI Service and builds an inventory with field-level lineage, so you can see which dashboards share a data source and what each calculation depends on. RapidDox for Tableau reads a workbook file (.twb, .twbx) and documents its data sources, calculated fields, parameters and filters, with output to Word, Excel, PDF, HTML or CSV. It also reads data source files and Prep flows. RapidDox for Power BI does the same for .pbix files, which helps for the target side. Both work on metadata only.

Written by Natraj, Founder, RapidDox & Rapid Lens. 25 years of experience in data and business intelligence.