The data model decisions you cannot cheaply undo
Most of an org is reversible. A handful of early modelling choices are not, and they get made in the first fortnight by whoever is fastest to a whiteboard.
Platform & ArchitectureSalesforce is unusually forgiving. Fields can be added, page layouts rearranged, automation rewritten, and most of that happens on a Tuesday afternoon without anyone noticing.
The forgiveness is what makes the exceptions dangerous. Teams carry a reasonable "we can change it later" assumption into the handful of decisions where later means a data migration, a rebuild of everything standing on top, or in two cases nothing at all, because the setting does not come back off.
Those decisions get made early, often informally, and they hold, because within a year the schema is the smallest part of them. The choice also lives in every report filter, integration mapping, automation criterion and permission set built since, and changing the model means changing all of that at once, on a working system, without losing the history.
What "cannot undo" actually means
The phrase covers three situations, and they deserve different responses.
Permanent. The switch has no off position. Person Accounts and multiple currencies sit here, and the only mitigations are deciding correctly or a new org later.
Reversible as a migration. The schema can change, but only by moving data, rewriting dependencies and running a cutover. Relationship types, record types and object choices sit here. These are not disasters. They are quarters.
Reversible in the schema, not in the history. The structure can be added at any time; the data that would have been in it cannot. External IDs sit here, and this class gets underestimated most, because the fix looks cheap until somebody asks about last year.
| Decision | What reversal costs | Point after which the price rises sharply |
|---|---|---|
| Person Accounts | Permanent, no off switch | Before the first account exists |
| Multiple currencies | Permanent, no off switch | Before the first amount is stored |
| Master-detail or lookup | Migration, plus a sharing review on the far side | Once children are too numerous to re-parent by hand |
| Junction object added late | Migration, and the second relationship was never stored | When the first relationship is recorded as a single field |
| Record type sprawl | Migration, layout by layout, rule by rule | When automation and permissions start referencing them |
| Picklist that was an entity | Migration, plus reconciling every near-duplicate value | When values acquire attributes nobody has anywhere to store |
| Standard versus custom object | Rebuild of everything the platform gave you free | When reports, packages and integrations assume the custom one |
| External ID absent | Field is cheap, the mapping it would have held is not | The first silent mismatch nobody notices |
Read the middle column as the moment the conversation stops being configuration and becomes a programme.
1. Person Accounts
Enabling Person Accounts is the clearest member of the permanent class. It changes the shape of the Account object itself: a person account is an account and a contact held together, behaving in some contexts like one and in some like the other.
Why reversal is not really available. Every downstream consumer inherits new rules at once: reports that assumed an account has contacts beneath it, integrations that map a contact identifier, managed packages built against one model, automation that fires on Contact, duplicate rules and merge behaviour. Turning it off would mean redefining what an account is for all of them simultaneously, which is why the option does not exist.
The cheaper question. Not "do we sell to consumers". It is whether an individual is ever a customer in their own right, with no organisation above them, and is the subject of the relationship rather than a person inside one. Retail, wealth, healthcare, education and member organisations usually answer yes. Pure B2B usually answers no. The hard case is both models in one business: supported, but the design work is real, because automation, reporting and sharing each have to handle two shapes of account. If the answer is anything other than a confident no, model it before the first load.
2. Master-detail, lookup, and the junction added late
Teams treat the relationship type as a tidiness question. It is three decisions bundled into one field.
Master-detail gives roll-up summary fields, cascading delete and, the consequential one, sharing the child inherits from its parent. A detail record has no owner of its own. That is both the reason to choose it and the reason to fear it, because if access to the child ever needs to differ from access to the parent, the field type has already decided that it cannot.
What reversal costs. Conversion between the two types exists and is genuinely constrained. Moving from lookup to master-detail requires every child to have its parent populated, which on a live object means finding and fixing the orphans first, and moving the other way interacts with the roll-up summary fields built on the relationship. Neither is a field edit. Both alter how records are shared the moment they complete, so every sharing rule, report and integration touching the child needs checking on the far side, and the cost scales with the number of children rather than the number of fields.
The junction object added late is the same decision arriving a year after it should have. A many-to-many relationship modelled as a single lookup, a text field, or a picklist of the common counterparties works until the first record needs two of something. Introducing a proper junction then is not only a schema change: the existing relationships have to be reconstructed from a field that could only ever hold one, and whatever the second and third were, they were never stored. The junction goes in easily. The history does not come with it.
Ask it while the diagram is still on paper: for every relationship drawn, can one record ever have more than one of these? If the answer is even "rarely", the junction costs an afternoon now and a reconstruction later.
3. External IDs
Every object that receives data from another system needs a unique external ID field carrying that system's key, populated from the first load rather than after the first duplicate incident.
Without one, every integration becomes a matching problem. The code queries by name, by email, or by a composite that mostly works, and mostly-works matching does not fail loudly. It produces duplicates at a slow, steady rate nobody attributes to the original decision, and it produces the worse failure quietly: records matched to the wrong counterpart, which then looks like user error for years.
Why this sits in the third class. The field is cheap to add at any point. What cannot be added retrospectively is the mapping between the records that already exist here and the records that already exist there. Reconstructing it means reconciling against the source record by record, using the same unreliable matching that caused the problem, for every system integrated since.
The cheaper question. For every object an integration will touch, name the field holding the other system's key, mark it unique and external, and insist the integration upserts on it. Upsert against an external ID is idempotent, so a replayed message, a restarted job or a duplicate event does the right thing instead of creating a second record.
4. Where a distinction lives: object, record type or picklist
Most modelling arguments are one question asked badly: this thing varies, so where should the variation live? There are three containers, they are not interchangeable, and choosing wrong is a migration to fix.
Standard object or custom object
The pull toward a custom object is strong, because a fresh object carries no baggage. It also carries none of the platform work standard objects get free: standard reporting behaviour, features that only operate on standard objects, package compatibility, and years of accumulated engineering nobody has to ask for.
Reversal is a rebuild. Every report, layout, automation, integration and permission set is re-pointed and the data migrated with its relationships intact, while the org has spent the intervening years forfeiting features it could have had. Use the standard object unless you can name what breaks if you do. "It has fields we do not need" is not that reason. A custom object duplicating a standard one commits you to maintaining a parallel implementation of everything the platform would have given you, including the parts Salesforce has not built yet.
Record types, past the point anyone can reason about them
Record types are cheap to add and awkward to remove, because page layouts, picklist availability, automation entry criteria, reports, permission sets and assignment rules all reference them. Retiring one means unpicking it everywhere and moving its records elsewhere, which changes what those records display and which values they permit.
Sprawl arrives one reasonable request at a time: a region wants a different layout, a team wants two values hidden, a product line wants its own process. The sum is an object where nobody can say which record type a new record should get without asking somebody, and the cost becomes a tax on all future work, because every automation has to enumerate them and every report has to be checked against all of them.
A record type should represent a genuinely different business process on the same object. Ask at creation: what does this one change that a picklist value could not?
The picklist that turns out to be an entity
The opposite error is quieter. A value goes in as a picklist because at the time it is only a label: a partner, a product line, a referral source, a site. Then it acquires attributes. Somebody wants the partner's owner, the product's list price, the site's address, and those have nowhere to live, so they end up in a spreadsheet or encoded into the value itself.
Converting to a lookup is a migration. The values become records, every existing record is re-pointed, and every report filter, automation criterion, integration mapping and validation rule comparing a text value is rewritten to compare a reference. The near-duplicates a long-lived picklist accumulates are reconciled by hand, each one a judgement call.
Ask at design time: does anything about this value need to be described, owned, or reported on in its own right? If yes it is an entity and wants a lookup.
5. Currency, and the day a second one appears
Multiple currency support is the other setting with no off position. Salesforce states it plainly: once a multicurrency setup is enabled, it cannot be disabled.
It is consequential because it changes the meaning of every stored amount. A currency field stops being a number and becomes a number with a currency, converted for reporting against exchange rates somebody now owns and maintains. Reports gain a currency dimension, and integrations that sent an amount must send a currency alongside it, or accept a default and be silently wrong.
The reverse problem is worse and more common: running a single-currency org while the business already trades in several, and absorbing the difference in spreadsheets or a text field beside the amount. Conversions then happen at whatever rate the report builder chose, historical figures cannot be reproduced, and eventual enablement means revisiting every amount already stored to say which currency it was in. Frequently nobody knows.
The cheaper question. Will any amount here ever be recorded in more than one currency, including inside a subsidiary you have not acquired yet? If the honest answer is probably, decide before the first amount is stored, and settle at the same time who owns the rates.
6. The model decisions that fix your sharing, and your skew
How access is designed is a subject in its own right, covered separately in designing the Salesforce access model on purpose. What belongs here is narrower: the modelling decisions that limit what the access model is later permitted to express, and the ones that make sharing expensive to calculate.
Master-detail removes an option. A detail record cannot be shared independently of its parent, so the relationship type has answered an access question the access design was never consulted on.
Ownership skew. When one user or one queue owns a very large share of the records on an object, the sharing calculations behind that ownership become expensive, and worse as the object grows. It is created by a default owner on an integration user, a catch-all queue, a conversion process that parks everything in one place. Distributing ownership by design costs nothing. Redistributing it later means re-owning records on a live system and waiting out the recalculation.
Data skew. The same shape in the relationship rather than the ownership: one parent with an unusually large number of children, usually a placeholder account or a catch-all record that becomes the destination for everything unclassified. What to do once the symptoms appear is covered in why your Salesforce org feels slow. The point here is that neither started as a performance problem. Both were modelled in.
Ask the volume question early. For the three widest objects: what is the expected record count in three years, who will own those records, and which parent will most of them hang from? The launch dataset shows you neither skew.
7. Where the number lives
For any value that must be right in a report, amount, quantity, score, status, decide whether it is stored, calculated at read time, or calculated on save. Then be consistent.
| Approach | Good for | Cost |
|---|---|---|
| Formula field | Always current, nothing to keep in step | Evaluated on read, which shows up on large reports |
| Roll-up summary | Aggregating children of a master-detail parent | Only on master-detail, and a limited set of functions |
| Stored field, set on save | Reportable, filterable, fast to query | Must be maintained, and goes stale silently |
| Calculated downstream | Warehouse-grade analytics and history | Not available in the org for automation or validation |
The mistake is mixing them for the same concept. When one report reads the stored field and another recomputes it, the two disagree, and the trust problem that follows is harder to fix than the modelling choice that caused it. Trust in a number is lost quickly and rebuilt slowly, usually by somebody rebuilding both reports and proving which one was right.
How to spend the design time
A day of modelling before the first sprint is worth more than any subsequent week of remediation, and it is a day with a definite agenda rather than an open discussion.
- Draw the object diagram on one page, with relationship types and directions marked.
- For every relationship, note whether the child needs independent sharing, and whether one record could ever have more than one of the other.
- For every object an integration touches, name the external ID field.
- For the three widest objects, write the expected year-three record count and the likely owner distribution.
- Write one line of business meaning for every record type, and delete any that share a line.
- Answer the two permanent questions, Person Accounts and multiple currencies, in writing.
None of that is exotic. It is the set of questions that becomes very expensive to ask late, and the answers get written down because in two years the person who needs them will not be the person who made them.




