Excel to web app migration: what to keep, what to move, what to drop
Nobody migrates off a spreadsheet that is not working. They migrate off one that has been working so well, for so long, that the business now depends on a file somebody has open.
The spreadsheet is the most successful piece of business software ever written and the migration conversation usually forgets that. It gets described as legacy, or as something to be escaped, and the team doing the escaping designs a replacement for a problem the spreadsheet never had.
A more useful framing: the file did not fail. It succeeded past the point where a file is the right container. Working out precisely where that line was crossed tells you what to build, and more importantly what not to.
The documented ceilings are not the ceiling you will hit
People assume they are migrating because the spreadsheet is running out of room. Almost nobody is. It is worth looking at the published limits once, because they are larger than the working assumption by a wide margin.
Microsoft documents a worksheet as "1,048,576 rows by 16,384 columns", with 32,767 characters per cell and calculation precision of 15 digits. Those figures are in Excel specifications and limits. Google Sheets is tighter and still generous: "up to 10 million cells or 18,278 columns" per spreadsheet, whether created natively or imported from Excel, per Google's documented Drive and Sheets size limits.
A business with forty thousand rows is at roughly four percent of one Excel sheet. If the reason given for migrating is size, the reason given is wrong, and building for size will produce a system that solves nothing anybody was actually suffering from.
What breaks first, in the order it usually breaks
The real failures are structural rather than dimensional, and they arrive in a fairly reliable sequence.
- Concurrency. Two people need to change different rows at the same time and the file is a single object. This is the first and by far the most common trigger.
- Validation. Nothing stops a date being typed into a quantity column, and by the time anyone notices, the wrong value has been copied into three derived sheets.
- History. Somebody changed a figure last Thursday and there is no record of who or why. Version history on the file tells you the file changed, not what the number used to mean.
- Permissions. One person should see margins and another should not, and a spreadsheet has no honest way to express that short of maintaining two copies, which immediately diverge.
- Derived truth. The report that management reads is now a sheet built from three other sheets, and nobody is confident the chain still holds.
Notice that none of those are performance. Notice also that the first four are all the same underlying issue: a spreadsheet has no concept of a transaction performed by a person. That is the thing a database gives you, and it is the actual product of the migration.
We have written up two specific instances of this pattern, and the shape is identical in both: the general case in when a stock spreadsheet starts costing more than it saves, and a sector case in the three tools every training centre rebuilds in spreadsheets.
What to keep in the spreadsheet, deliberately
The most common migration error is moving everything, because moving everything feels like completion. It produces a rigid application that people work around by exporting to a spreadsheet, which is how you end up with both.
| Function | Where it should live | Why |
|---|---|---|
| Shared records many people edit | The application | Concurrency and an audit trail are the entire point |
| Validation and permissions | The application | A file cannot express either honestly |
| One-off analysis | The spreadsheet | Exploration needs a scratchpad, not a schema |
| Ad hoc modelling and what-ifs | The spreadsheet | Nobody should file a change request to test a scenario |
| Recurring reports | Generated from the application | Derived numbers should have one source |
Build an export that is genuinely good, then. The spreadsheet is not the enemy of the new system, it is the analysis layer on top of it, and a migration that removes people's ability to poke at the data will be resented in exactly the way that gets systems abandoned.
Version one should do less than the spreadsheet
This is the recommendation people argue with, so it is worth being direct. The first release should cover the records that need concurrency, validation and history, and nothing else.
The reason is that the spreadsheet accumulated features over years, and most of them were used once. Replicating the full surface means building a large system against requirements that were never really requirements, and the way you find that out is by shipping the small version and watching which omissions people actually complain about. Complaints are the requirements document you could not have written in advance. What that scoping conversation looks like in practice is in how to scope a software project before anyone writes code.
The reporting layer is where this bites hardest, because reporting is the part everyone can see and therefore the part everyone specifies. It is usually better handled separately and later, on the argument set out in automating manual reporting without buying a BI platform.
Keeping the history is the part that gets skipped
Migrations tend to import current state and leave the past in the old file. Then somebody asks a question about last year and the answer is in a spreadsheet nobody maintains, which is precisely the situation the migration was meant to end.
Import the history even where it is untidy, and mark it as imported so nobody mistakes reconstructed data for recorded data. Untidy history in the system beats clean history in a file that is drifting out of date, and the marking is what keeps it honest.
The honest limits
Two situations where the migration is the wrong call.
If one person owns the process and no one else edits it, most of the case evaporates. Concurrency was the main driver, and a single editor does not have a concurrency problem. Better validation inside the sheet is the cheaper fix, and it is genuinely a fix rather than a stopgap.
And if the process itself is still changing weekly, freezing it into a schema will hurt. A spreadsheet's flexibility is a real feature during the period when nobody knows what the process is yet. Migrate once the shape has stopped moving, not while it is still being invented.
Worth separating too: a reporting problem is not the same as a records problem, and they get conflated because both surface as complaints about the spreadsheet. If what the business actually wants is a reliable set of numbers rather than a place to enter transactions, the smaller intervention is choosing the numbers carefully. BDG Labs made that argument on the sales side in the six numbers that earn a place on a B2B dashboard, and the discipline transfers to any internal system: decide what deserves to be tracked before deciding what to build.
Describe it. We build it.
Seven or twelve days, pay on delivery, a year of maintenance included. Bring the problem, not a spec.
Book a meetingRead next
How to pick a tech stack for a startup, and why boring keeps winning
The stack decision is usually argued on benchmarks and settled on taste. Two numbers decide it better: who you can hire, and how long the thing is supported.
ReadA data backup strategy for a small business, in plain terms
Most small businesses have copies of their data and no backup strategy. The difference is two numbers and one rehearsal, and it decides what a bad day costs.
Read