Every Anaplan estate eventually hits the same wall, and it is never the calculation engine. It is the day finance announces a cost centre restructure effective the first of next quarter, or sales redraws regions, or the group rationalises a chart of accounts across three legacy ERPs. Suddenly a model that calculated beautifully for two years cannot answer a simple question: what did last year look like under the new structure?
Master data management is the least glamorous layer of a connected planning architecture and the one that decides whether your models survive their third year. This tutorial covers how to build a governed hierarchy and mapping layer in Anaplan: where structure lives, how to make restructures non-destructive, how to restate history, and how to stop list maintenance from becoming a model builder's full-time job.
It assumes familiarity with lists, SYS modules, LOOKUP, SUM, and basic action/process design.
The problem in one paragraph
An Anaplan list member is an identity. A hierarchy parent is a property of that identity today. Transactional data, however, is stamped at a point in time. When the hierarchy changes, every historical number silently re-aggregates under the new parent — or, if you create new members instead, history fragments across old and new codes. Neither default behaviour is what the business wants. What the business wants is to see history both ways: as it was reported, and as it would look under today's structure. You only get that if you design for it before the first restructure, not after.
Step 1: Decide where master data lives
Master data belongs in the data hub, full stop. The hub is the single place that:
- receives source system extracts (ERP cost centres, HRIS org units, CRM territories, product master);
- holds the lists that are imported into every spoke model;
- stores the mapping and effective-dating logic;
- runs the validation that stops bad structure propagating.
Spoke models should never create or edit structural list members through user action. If a planner can add a cost centre in the FP&A model, that cost centre does not exist in the ERP, will not reconcile, and will be a line in someone's variance explanation next month.
The one acceptable exception is a request-and-approve pattern, covered in step 7, where the spoke model captures a request and the hub remains the system that creates the member.
Step 2: Structure the lists properly
A worked example: a cost centre hierarchy sourced from ERP.
| List | Role |
|---|---|
G1 Legal Entity | Top level, from ERP company codes |
G2 Division | Management grouping |
G3 Function | Finance, Sales, R&D, Operations |
G4 Cost Centre | The leaf, the grain of the data |
MDM Cost Centre Master | Flat production list, every cost centre ever, including retired |
The last row is the piece most builds omit, and it is the one that makes everything else possible. G4 Cost Centre is the current hierarchy leaf. MDM Cost Centre Master is the flat, permanent registry: one member per code, ever, never deleted.
Rules:
- Codes, not names. List member names change; codes do not. Use the ERP code as the list member code, and carry the display name as a text property in a SYS module. Renaming a member in Anaplan while the code stays stable costs nothing; re-coding costs you every mapping in the estate.
- Never delete a structural member. Mark it inactive. Deletion removes history, breaks saved views, and invalidates imports that referenced it.
- No mixed grains. If some cost centres roll to Function and some roll straight to Division, you have a ragged hierarchy problem. Insert a pass-through "unassigned" member at the missing level rather than building two parallel structures.
Step 3: The SYS module layer
Every list gets exactly one SYS module holding its attributes. SYS04 Cost Centre Details, dimensioned by MDM Cost Centre Master:
Code = CODE(ITEM('MDM Cost Centre Master'))
Display Name = text, from source
Active? = boolean
Effective From = date
Effective To = date (31/12/2999 if open)
Owner (Employee) = list-formatted
Current G3 Function = list-formatted, from source
Current G2 Division = list-formatted, derived
Current G1 Entity = list-formatted, derived
Mapped G4 Member = list-formatted to G4 Cost Centre
Is In Current Tree? = ISNOTBLANK(Mapped G4 Member)
Nothing downstream should ever read a hierarchy parent by navigating the list structure directly when it could read it from the SYS module. The SYS module is your abstraction layer: when the structure changes, you change one module and the models that read it keep working.
Step 4: Effective-dated structure — the core pattern
This is the heart of the build. Create MDM01 Structure History, dimensioned by MDM Cost Centre Master × Time (monthly):
Parent (As Reported) = list-formatted to G3 Function, the parent in force that month
Parent (Current View) = SYS04.Current G3 Function, the parent today
Active in Period? = boolean
Restructure Event = list-formatted to a Restructure Events list, blank in normal months
Parent (As Reported) is populated once per period by a scheduled action that snapshots the current structure and then never changes it. Parent (Current View) always reflects today. Two line items, and you have both views of history permanently available.
Reporting modules then pick a basis:
Reporting Basis = list: As Reported / Current Structure
Effective Parent = IF Reporting Basis = As Reported
THEN MDM01.Parent (As Reported)
ELSE MDM01.Parent (Current View)
Cost (Reported) = SUM of transactional cost mapped by Effective Parent
A single page selector now lets a CFO flip between "what we published last year" and "what last year looks like under the structure we run today." That question is asked in every reorg, every acquisition, and every annual planning kickoff, and most Anaplan estates cannot answer it without a spreadsheet exercise.
The monthly snapshot action is non-negotiable discipline. Put it in the period-close process alongside the actuals load, and make it fail loudly if it has not run. A missing snapshot month is unrecoverable once the source structure has moved on.
Step 5: Mapping tables, not mapping formulas
Everywhere two structures meet — ERP account to planning account, legacy entity to new entity, SKU to planning product — build a mapping module, not a nested IF.
MAP01 Account Mapping, dimensioned by Source Account × Time:
Target Planning Account = list-formatted
Mapping Rule = list: Direct / Split / Derived / Excluded
Split % = number, for one-to-many
Mapped? = ISNOTBLANK(Target Planning Account)
Unmapped Value = IF Mapped? THEN 0 ELSE Source Amount
Three properties make a mapping table trustworthy:
- It is time-dimensioned. Mappings change. A source account that rolled to
Traveluntil March andTravel & Entertainmentafterwards must map both ways, in the right periods. - It is complete by construction. Build an
Unmapped Valuecontrol total and surface it on an exceptions page. The number that must be zero before close is not "how many codes are unmapped" — it is "how much money is sitting in unmapped codes." A thousand unmapped dormant accounts holding zero currency do not matter; one unmapped account holding 4% of opex does. - It is owned by a named human. Mapping is a finance data-governance role, not an IT one. Give that person a UX page and a publish action; do not make them raise a ticket.
For one-to-many splits, enforce the control:
Split Control = SUM of Split % by Source Account
Split Error? = Mapped? AND Mapping Rule = Split AND ROUND(Split Control, 4) <> 1
Step 6: Alternate hierarchies without duplicating data
Business users always want more than one view: legal entity for statutory, management structure for internal reporting, geography for tax, and whatever the new CFO prefers. The wrong answer is a second copy of the data dimensioned by a second hierarchy.
The right answer is an attribute-driven alternate hierarchy. Add the alternate structure as a list-formatted property in the SYS module and aggregate with SUM rather than relying on the native parent:
SYS04.Mgmt Reporting Unit = list-formatted to Mgmt Units
REP01 by Mgmt Unit:
Cost = SUM of base cost, mapped by SYS04.Mgmt Reporting Unit
Data is stored once at the leaf grain and aggregated several ways. Adding a fifth reporting view becomes a new property and a new thin reporting module, not a model rebuild. It also keeps the native hierarchy — the one that drives your largest modules — as small and clean as possible, which matters for calculation performance.
Where an alternate view is genuinely ragged or many-to-many (a cost centre contributing to two management units at 60/40), use a bridge module dimensioned by Leaf × Alternate Member with an allocation percentage and a control total. It is a larger module, so build it only where the business truly needs it.
Step 7: The structure change request workflow
Make the governed path easier than the ungoverned one, or people will route around it.
Build a Structure Change Requests numbered list and a request module:
Request Type = list: New / Rename / Reparent / Deactivate / Merge
Affected Member = list-formatted
Proposed Parent = list-formatted
Effective Date = date
Business Reason = text
Requested By = user
Status = list: Draft / Submitted / Approved / Rejected / Applied
Approver = user
Impact: Budget $ = the planned value currently sitting on the member
That last line item does more for governance than any policy document. Showing a requester that reparenting this cost centre moves $4.2m of budget between two divisional owners generates the right conversation before the change, not after.
Route approvals through Anaplan Workflow, and have the Applied status trigger the hub import that actually creates or moves the member. Nothing moves in the structure without a request row behind it — which also means your audit trail for structural change is a module you can report on, not a chain of emails.
Step 8: Validation that runs before the damage
Add MDM99 Structure Validation, dimensioned by MDM Cost Centre Master, with one boolean per rule and a single rollup:
Orphaned? = Active? AND ISBLANK(Current G3 Function)
Duplicate Name? = flagged from a name-count module
Inactive With Data? = NOT Active? AND ABS(Current Year Plan) > 0
Date Inconsistent? = Effective From > Effective To
Missing Owner? = Active? AND ISBLANK(Owner (Employee))
Error Count = sum of the above as 1/0
Error Text = concatenated reasons
Publish an exceptions page and gate the structure-publish process on Error Count = 0. Catching an orphaned member in the hub costs a model builder ten minutes. Catching it after it has synced to nine spoke models, because a director's roll-up is missing $900k, costs a week and a reputation.
Also run a reconciliation to source: count and sum the source extract, count and sum what landed in the hub, and flag any delta. Silent partial imports are the most common cause of "the model is wrong" tickets that turn out to be data loading issues.
Step 9: Propagating structure to spoke models
Structure flows out of the hub through ALM-safe imports, not manual edits:
- One process per spoke model, named explicitly (
P01 Load Structure from Hub), run on a schedule and after any structure change. - Import into production lists so ALM syncs do not overwrite them. Structural lists must be flagged as production data in every spoke model; a non-production list wiped by a model release is a genuinely bad day.
- Order matters: load parent levels before children, mapping modules before transactional data, and deactivations last.
- Keep a
Last Structure Synctimestamp line item visible on an admin page in every spoke. When someone says the numbers do not tie, the first question is whether the structures match, and this answers it in two seconds.
Step 10: Handling the restructure itself
When a real reorg lands, the sequence that works:
- Freeze and snapshot. Run the
Parent (As Reported)snapshot for the final period under the old structure. Export a full set of control totals: cost by division, headcount by function, revenue by region. - Load the new structure with a future effective date. Nothing changes in live reporting yet.
- Map old to new in
MAP02 Restructure Crosswalk, with split percentages where a cost centre breaks apart. Validate that every old member maps somewhere and splits total 100%. - Restate in a sandbox. Apply the crosswalk to prior-year actuals and the current plan, then reconcile total by total against the step 1 control totals. The grand total must tie exactly; if it does not, the crosswalk is incomplete.
- Publish the restated comparatives alongside the originals. Both views, same page, selector-driven.
- Cut over on the effective date, with the ownership and approval structures (DCA, selective access, workflow approvers) updated in the same release — not a week later.
Step 4 is the one people skip under time pressure, and it is the one that catches the 2% of spend that falls into a cost centre nobody claimed in the new structure.
What good looks like
A mature Anaplan master data layer has a few visible signs:
- A business user, not a model builder, maintains mappings day to day.
- Any period of history can be viewed under any structure, on demand, from a selector.
- Structure changes arrive through a request queue with an impact number attached.
- Exceptions pages are checked as part of close, and are usually empty.
- No spoke model creates structural members.
- The question "has the structure synced?" has an answer on screen.
None of this is difficult modelling. It is mostly SYS modules, mapping tables, booleans, and one disciplined monthly snapshot action. The reason it is rare is that it delivers nothing visible on day one and everything on the day of the reorg — which is exactly the kind of work that gets deferred until it is expensive.
If you are standing up a new Anaplan estate, build this layer in the first release. If you already have models in production and no structure history, start the monthly snapshot this month: you cannot recover past structure, but you can stop losing it from here.
Need help with this? QuanticPlanning's Anaplan consultants build and remediate data hub and master data architectures, run restructure and restatement programmes, and set up the governance layer that keeps them clean. Get in touch to scope an engagement.