Modernising a Salesforce Org Without Disrupting the Business
Eight years of accumulated build, three sets of partner conventions, and no window in which anything was allowed to stop.
Commercial & Business ServicesBefore, the org carried three partner conventions side by side on the same objects: triggers and a service layer from one era, migrated Process Builder logic from another, and validation rules doing business logic from a third. Permission sets had been cloned per person rather than defined per capability, automation ran in whatever order the platform chose, and changes reached production without a repeatable path. After, each busy object carries one convention, access is granted through permission set groups assembled from capability-level sets, order of execution is explicit and written down, deployments follow one automated path, and everything deliberately not fixed sits in a register with the reason recorded.
As found
- Three partner conventions coexisting on the same objects
- Permission sets cloned per person
- Flows and triggers at the same timing with no set order
- Changes made directly in production because the path was slow
- Fields nobody could attribute to a live process
None of this was broken enough to stop work, which is exactly why it had survived eight years.
Target state
- One convention per object, enforced at review
- Permission set groups by role, built from capability-level sets
- One explicit order of execution per busy object
- A sandbox to production path with automated deployment
- A written register of the debt deliberately left alone
Every item earned its place by what it cost per sprint, not by how bad it looked in an audit.
A worked scenario showing how we approach this problem. The architecture and decisions are our real practice; it is not an account of one named customer.
The constraint that shaped everything
The brief in this situation is almost never "our org has technical debt". It is that a change which should take a day now takes a fortnight, and nobody can explain the difference to a finance director.
Trace it back and the same shape appears. The org has been extended by three different partners across eight years. Each left behind a coherent set of conventions, and none of them agree with the others. One built with Apex triggers and a service layer. One built in Process Builder, then lifted it into Flow without revisiting the design, so the flows are shaped like the tool that no longer exists. One did most of the work in validation rules and formula fields, on the entirely reasonable grounds that those deploy easily and need no test class.
None of that is broken. That is the part everyone underestimates. The business runs on this org every day, quotes from it, invoices from it and reports to a board from it. There is no window in which work can stop, no budget line called remediation, and no incident to point at.
So the constraint was never technical. It was that nothing could stop. That single fact determines the sequencing: the work is ordered by what each piece of debt costs to carry, not by how bad it looks written down.
Measuring before changing anything
The first two weeks change nothing in the org. They produce a picture of where work actually happens.
Org Check, which is the current in-platform inspection now that Salesforce Optimizer has been retired, gives you the structural view. Security Health Check gives you the settings view. Neither is the most valuable input. That is the Setup Audit Trail export for the last twelve months, because it shows where change lands. The objects appearing most often in it are the objects where debt charges rent.
Alongside that, the team is measured rather than the metadata: how long a routine change takes end to end, how much of that is waiting or repeating, how many deployment attempts a typical release needs, and which parts of the org make people ask a colleague before touching anything.
What a bad measurement looks like. A spreadsheet of several hundred findings sorted by object, each with a severity filled in from instinct. It gets circulated, admired and never actioned, because it presents four hundred problems of apparently equal standing to a team with capacity for six. A bad measurement is also one taken only from metadata: it will tell you a field is blank on most records and cannot tell you that the field feeds a regulatory return each March.
The measurement that works is small and pointed. Where does change land, what does a change cost, and which of that cost repeats. Everything else is context.
Why deleting is the fix that causes the outage
The instinct on inheriting an org like this is to start removing things. It feels like progress, it is visible, and it is the single most reliable way to take a business offline.
The reason is that in an org built by three teams, nothing declares its dependencies. A field with no data may still be referenced by an integration that writes to it nightly. A validation rule that looks redundant may be the only thing preventing a downstream system from receiving a record it cannot parse. An unused-looking flow may be the one that runs at quarter end.
So every change follows the same shape: additive, then parallel, then subtractive, with the subtraction in its own release.
Additive means the replacement is built next to the incumbent, not over it. The new permission set group is created and assigned; the old access is untouched. The consolidated flow is built and activated on a subset; the old logic still runs.
Parallel means both run long enough to cover the real business cycle, including whatever monthly and quarterly process only fires once. During this phase you are not looking for the new thing to work. You are looking for evidence that nothing reads the old thing.
Subtractive means the removal ships on its own, in a release containing nothing else, so it can be reverted without unpicking three unrelated changes. This is the discipline that makes deletion safe, and it is the one that gets negotiated away under time pressure.
Debt that costs nothing, and debt that costs every sprint
This is the judgement that made the programme fundable, so it is worth stating plainly.
A badly built custom object supporting a process retired years ago, on a page layout nobody opens, costs nothing. It is ugly. Ugly is free. It can sit there indefinitely and the business will never pay for it.
The same standard of build sitting on Opportunity, in the path of every change the team makes, charges on every release: an extra hour of impact analysis, an extra failed deployment, an extra caveat on every estimate. That is debt with a price.
The release path itself sat at the top of the list for exactly this reason. Every other fix has to travel through it, so debt there multiplies everything else. Sandboxes that no longer resembled production, deployments failing repeatedly on familiar errors, and the resulting habit of making small changes directly in production, which is how the three-conventions problem kept growing in the first place. Fixing the path first made every subsequent item cheaper.
Below that came the busy objects identified in the audit trail. Below that, the cheap and permanent items: hard-coded record type and queue IDs, unassigned permission sets, genuinely dead schema. Low value each, but they never come back once fixed and they clear the noise that hides real findings.
And below that, explicitly, the group that was recorded and not fixed.
Automation: the order, not the count
The finding that gets reported in an org like this is a number. Some count of flows and triggers on the busiest object, presented as though the number were the problem.
It is not. Ten well-ordered automations on Account are healthier than three that fight. What you are looking for is dependency: places where one piece of logic is only correct because another happened to run first, and nothing anywhere records that.
That is the real risk, because order of execution across same-timing automation is not something to be assumed. When it is left implicit, the org works until the day something is added, reordered or migrated, and then it produces a defect that presents as bad data rather than as an error. Nobody gets an alert. Somebody notices a wrong number weeks later.
So the work per busy object was: enumerate everything that fires on save, note the timing of each, identify every field written by one and read by another, and then make the ordering explicit and documented. Consolidation followed from that, where it made the ordering simpler. Consolidation for its own sake, to reduce a count in a report, was not done.
The test applied throughout: if this object's automation ran in a different order tomorrow, would anything break. Where the answer was yes and the order was not set deliberately, that was a defect waiting for a release rather than a tidiness issue.
Permissions: one short pass
Access was given a single deliberate pass rather than becoming its own project inside this one.
The state on arrival was permission sets cloned per person, which is what happens when access is granted under time pressure over several years and nobody owns the model. The target was capability-level permission sets assembled into permission set groups by role, so a new starter is granted a role rather than a copy of whoever sat next to them.
Two things are worth saying clearly, because both are commonly misstated. Profiles are not being retired; that plan was cancelled. And permission sets and permission set groups remain the recommended direction for granting access, which is a different statement and the one that actually governs the design.
The detail of how the model is built, audited and kept honest is a subject of its own, and it is set out in Permission set debt: how orgs quietly lose track of who can see what. For this programme, the pass produced three numbers, a target model and a migration that ran additively like everything else.
Unused, and rarely used
The largest volume of findings was schema, and the largest risk in the whole programme sat here, because the difference between the two categories is invisible in a usage report.
Unused means nothing writes it and nothing reads it: no layout, no list view, no report, no automation, no validation rule, no Apex, no integration. That is safe to remove, on the additive-then-subtractive sequence, with the removal in its own release.
Rarely used means it is populated infrequently but something depends on it. Year end. One team in one region. A partner process running quarterly. A blank rate cannot separate these from the unused ones, and deleting a rarely used field is precisely how an org discovers a business process it never documented, usually in the week that process runs.
So the finding on a rarely used field is not the field. It is an undocumented process with no named owner, and the correct output is a written description and an owner, not a deletion. Several of those were the most valuable artefacts the programme produced, and none of them appeared on the original findings list.
Getting it funded when nothing is visibly broken
This is the actual obstacle, and it is not technical.
Nothing is down. No customer has complained. The org processes the same volume it did last year. Asking for a modernisation budget in that setting sounds like asking to be allowed to tidy, and tidying does not get funded ahead of anything with a revenue number attached.
The argument that works does not mention debt. It reframes the work as the cost of the next three things the business already wants, because that is what it is. Each of those roadmap items has been quoted at more than it should cost, and the difference is the same unresolved debt appearing in three estimates. Put the inflated estimate next to the clean one and the business case reads itself.
Two supports make it land. The first is the audit trail evidence, which shows the inflation is concentrated in the objects everyone is about to change. The second is the register of accepted debt, because a programme that names what it is deliberately not fixing is credibly scoped, and one that promises to fix everything is not.
The decisions that were contested
Fixing the release path before anything anybody could see. The first block of work produced no visible change in the org, which is uncomfortable when the funding argument was about delivery speed. We held to it because every later fix had to pass through that path, and doing it second would have meant paying for it twice.
Not consolidating automation to reduce the count. There was appetite for a headline number showing fewer flows. We argued against consolidation that did not make ordering clearer, because merging two well-behaved automations into one large one trades a legible dependency for a fragile piece of logic and reports it as an improvement.
Leaving a badly built legacy object alone. It is genuinely poor work, it embarrasses everyone who opens it, and nothing touches it. It went into the accepted register with the reason and a note that it should be fixed as part of whatever work next requires opening it, when the analysis cost is being paid anyway.
Keeping one partner convention we would not have chosen. On one object, the incumbent pattern was not our preference but was consistently applied and well understood by the internal team. Rewriting it to our house style would have been a change with real risk and no carrying cost attached. Consistency within an object beat consistency with our preferences.
What changes
The outcomes worth claiming here are operational, and they follow from the sequencing rather than from effort.
| Before | After | What made the difference |
|---|---|---|
| Three partner conventions on the same objects | One convention per object, enforced at review | Convention decided per object, not per team preference |
| Changes made directly in production | Every change travels one automated path | The release path was fixed before anything visible |
| Automation order left to the platform | Order of execution explicit and documented | Dependencies were enumerated, not counted |
| Access cloned per person | Permission set groups assembled from capability sets | Access modelled by role rather than by precedent |
| Cleanup bundled into feature releases | Removals ship alone and can be reverted | Additive, parallel, then subtractive, in that order |
| Findings list nobody actioned | A short live list and a written accepted register | Ranked by carrying cost rather than by severity |
The second-order effect is the one leaders notice. Estimates stop carrying a mystery premium, so a request that used to come back at a fortnight comes back at a few days, and the conversation about the roadmap becomes a conversation about priorities rather than about capacity.
What we would tell you before starting
Do not begin with the org. Begin with the last twelve months of change, because that tells you where your money is going, and it will not be where the audit says the worst problems are.
Then accept that this work is slower than it looks and much safer than it looks, in that order. The parallel-running phase feels like waiting. It is the phase that means nobody has to explain an outage.
And be deliberate about what you leave. Debt in a component nobody touches is correct to leave alone: the poor legacy object, the ugly formula on a retired process, the odd convention that is consistently applied and understood. Record it, note the reason, put a date on the note. An org modernisation that removes everything it dislikes has spent the budget on aesthetics, and the things that were actually costing you money will still be there next year.
