A dashboard is the display layer, not the logic
Someone asks you to rebuild a dashboard. Maybe the platform is changing, maybe the data source is moving, maybe the thing has grown into a mess of thirty tiles and it is time to start over. You open it and it looks straightforward. Half a dozen scorecards, a few time series, a couple of pie charts broken out by channel, a table at the bottom. A day of work, maybe two.
What you are looking at is the display layer. Underneath every one of those tiles is a small pile of decisions somebody made, possibly years ago, that determine what the number actually means. Which rows are included. Which are excluded. What "channel" is defined as. What counts as a lead. Those decisions live inside the report builder, and in most organizations they exist nowhere else. Not in a spec, not in a wiki, not in anyone's head.
Rebuild from the picture and you will produce something that renders correctly and reports different numbers. That is the whole problem in one sentence.
Where the logic actually hides
There are three layers, and each one is harder to see than the last.
- Per-chart filters. Set on the individual tile in the builder. They do not appear on the canvas, they are not in any export, and no API hands them to you in a usable form. You have to open each chart and read them. This is the layer people skip because it is tedious, and it is the layer that decides the number.
- Report-level and page-level filters. Applied invisibly to everything below them. A page filter excluding internal traffic explains a discrepancy you would otherwise spend a day chasing in the source data.
- Derived fields defined somewhere else entirely. The filter says "Channel is Paid Search." Fine. But where is Channel defined? Sometimes it is native to the data source. Sometimes it is a calculated field in the connector. And sometimes it is a custom channel grouping configured inside the analytics platform, which means the dashboard is referencing a definition that lives in a completely different product, maintained by a different person, changeable without anyone touching the dashboard.
That third layer is the one that ends projects late. The filter is visible. The definition it points at is not.
The two traps that break a rebuild
We pulled a live dashboard apart recently for exactly this reason, and it produced a clean example of both. Details anonymized, mechanics unchanged.
Trap one: the same concept, split two different ways
The dashboard split everything into residential and commercial. Simple business distinction, used on most of the tiles. Except it was implemented twice. Some tiles filtered on a page path regex, matching URLs that contained the residential section of the site. Other tiles filtered on a separate custom dimension that carried a business-line value.
Those two mechanisms do not return the same set of rows. They never did. Anyone rebuilding would pick one, apply it consistently because consistency looks like good practice, and quietly change the numbers on half the tiles. Nobody would notice for a quarter, and when someone finally did, the investigation would start in the wrong place.
Trap two: a field the dashboard does not own
Every pie chart broke traffic out by channel. That breakdown was not defined in the dashboard at all. It came from a custom channel grouping configured in the analytics platform, with its own rules about what counts as paid social versus organic social, how a particular set of referral domains gets bucketed, and so on.
Rebuild on the default channel grouping and every pie chart shifts. The tiles still render. The colors still look right. The numbers are wrong in a way that is invisible unless you happen to have the old dashboard open beside the new one, comparing slice by slice.
None of this was written down anywhere. It was not hidden on purpose. It accumulated, one reasonable decision at a time, and the record of those decisions was the dashboard itself.
Why "it renders and the numbers look right" is the dangerous outcome
A broken rebuild is easy. Charts error out, a field does not resolve, something shows zero. You fix it before anyone sees it.
A silently wrong rebuild has no error state. Every tile populates. Every number is plausible, because it is drawn from real data with a filter that is merely different from the one it replaced. It ships, people start making decisions on it, and the only way to catch it is for someone to remember what the old number was and care enough to ask.
This is the same failure mode as a tracking implementation that fires the wrong value. Nothing alerts. The data keeps flowing. We wrote about the tracking version of it in 5 Signs Your Conversion Tracking Is Broken, and the reconciliation version in Why GA4 and Google Ads Never Match. Both come back to the same root cause: a definition living in a place nobody documented.
The extraction checklist
Do this before you build anything. It is unglamorous and it is the difference between a rebuild that reproduces the numbers and one that only reproduces the charts.
For every tile, capture:
- The metric. Exact name, and whether it is native, aggregated, or calculated.
- The dimension or breakdown. Same question: native or derived.
- Every filter clause, in full. Field, include or exclude, condition type (equals, contains, regex, in-list), and the exact value including case and any wildcards. "Filtered to residential" is not a spec. "Include, Page path, RegExp match, ^/residential" is.
- How multiple clauses combine. AND or OR, and the grouping. This is where two filters that look identical produce different results.
- The date range and comparison setting. Inherited from the page, or overridden on this tile. Tile-level overrides are common and almost never noticed.
Then, for every field any of those filters reference, record which of three things it is:
- Native to the data source. Portable. Note it and move on.
- A calculated field. Copy the formula verbatim. Not a description of the formula, the formula.
- A platform-level configuration. Custom channel groupings, custom dimensions, audience definitions, content groups. Record where it is configured, who owns it, and the full rule set. This is the category that bites, because it is the one the dashboard cannot see.
Store the result as structured, versioned config. A spreadsheet, a YAML file, a table in the repo, whatever your team will actually maintain. Not screenshots. A screenshot cannot be diffed, searched, or reviewed, and six months from now nobody can tell whether it reflects the current state.
A useful test while you extract: for each tile, could a competent stranger reproduce this number from your document alone, without opening the dashboard? If not, you have written a description, not a spec.
The filter document is the real dashboard
Once the extraction exists, a few things get easier at once. The rebuild becomes mechanical, because you are implementing a spec instead of reverse-engineering a picture. Discrepancies become answerable, because someone can point at the line that defines the number. Inconsistencies surface on their own, the way two different residential definitions do the moment they are written next to each other. And the next migration, to whatever platform comes after this one, starts from a document instead of from scratch.
The charts are the output. The filter logic is the asset. Build and rebuild from that, not from the picture.
Migrating or rebuilding reporting this year? Book a Data and Tracking Audit. We document the logic behind your dashboards so a rebuild reproduces the numbers, not just the charts.