From Spreadsheets to a Custom Internal System: The Migration Playbook We Ran for MOVE & MEET and Plodovi
How we audit a spreadsheet, model what it really encodes, clean years of messy history, and cut over without breaking the operations team that depends on it.
2026-08-17 · By Filip Lauc
Start by auditing the spreadsheet, not the requirements document
The first deliverable in a spreadsheet migration is not a spec, it is an audit of the existing files. Go cell by cell and classify every column as raw data, derived value, business rule, or workflow state. Most of the real system logic lives in formulas, conditional formatting, and filter views, not in anything anyone will tell you in a kickoff call.
When MOVE & MEET ran its London fitness community on spreadsheets, the member sheet looked like a simple list of names and emails. It was not. A column of dates was actually a manual attendance ledger, a highlighted row meant a member had paid but not yet been added to a session, and a nested IF formula decided whether someone counted as active based on how recently they had booked. None of that appeared in any written brief. It only appeared when we asked the person who maintained the file to walk us through a normal week, cell by cell, while we recorded which colours and columns triggered which actions.
The most valuable artifact from this stage is a rule inventory: a plain list of every implicit rule you found, written in one sentence each, with the person who owns it. This becomes the acceptance checklist later. If a rule is not on that list, it will not exist in the new system, and someone will notice the day after cutover.
- • Raw data columns: names, emails, prices, quantities, timestamps that are entered, not computed.
- • Derived columns: anything with a formula. Each one is a candidate for a computed field or a report, not a stored column.
- • Encoded rules: conditional formatting, colour codes, data validation dropdowns, hidden helper columns.
- • Workflow state: columns like "done?", "sent", "confirmed", which are really status machines with undocumented transitions.
- • Human conventions: strikethrough for cancelled, a blank row separating weeks, notes in the wrong column.
Decide which sheets are entities and which are events
Every spreadsheet tab is one of two things: a list of things that exist and get edited (entities), or a log of things that happened (events). Entities become tables with identity and mutable fields. Events become append-only records with a timestamp. Getting this split wrong is the single most common cause of a migration that feels worse than the spreadsheet it replaced.
Spreadsheets blur the two because a row is cheap to overwrite. In Plodovi's retailer operations, a delivery tab was maintained by editing rows in place, so when a delivery slot moved, the previous slot simply vanished. Modelled naively, that becomes a delivery table with a mutable date field and no history, which is fine until someone asks why a retailer's order arrived on a different day than agreed. Modelled correctly, the delivery is an entity and each schedule change is an event, so the audit trail exists by construction.
A useful test: ask whether anyone has ever scrolled back to an old row to answer a question. If yes, that tab is an event log and its history matters. If the sheet is regenerated from scratch every week and nobody looks at last week's copy, it is a working view over entities and should become a screen or a report, not a table.
Clean the historical data before it touches the new schema
Dirty history is where migrations stall. Plan an explicit data remediation phase: deduplicate records against a chosen match key, normalise free-text dates and units, and quarantine rows you cannot resolve rather than letting them into the production database. Import scripts should be idempotent and re-runnable, because you will run them many times.
The recurring problems are always the same. Duplicate people, because someone typed a name slightly differently or used a second email. Free-text dates, where "12/3", "March 12", and "next Tue" all appear in one column. Inconsistent units, which for Plodovi meant product quantities recorded sometimes as pieces, sometimes as kilograms, sometimes as crates, in the same column. Numbers stored as text with stray whitespace. Blank rows used as visual separators that a parser reads as empty records.
We treat migration as a pipeline with three outputs: clean rows that import, rows that import with a flag for later review, and a rejects file with a human-readable reason per row. The rejects file goes back to the operator, not to the engineers, because only they know whether "Marko K." and "Marko Kovacevic" are the same retailer. Do not silently guess. A migration that quietly merged two customers is far more damaging than one that asked twenty questions.
- • Pick a match key per entity (email, tax ID, phone) and document the tie-break rule when two rows conflict.
- • Normalise units to a canonical base and store the original string on the record for traceability.
- • Parse dates with an explicit locale, never with a permissive parser that guesses month/day order.
- • Keep a source_row reference on every imported record so you can trace any value back to the original file.
- • Re-run the whole import from the raw file each time instead of patching the database by hand.
Run parallel with the spreadsheet still authoritative
Do not cut over on launch day. Run a parallel period of two to four full business cycles where operators keep updating the spreadsheet as the source of truth while also using the new system, and a daily reconciliation compares the two. The system earns authority by matching the spreadsheet, not by being declared live.
A business cycle is whatever the operation actually repeats. For MOVE & MEET that was a week of sessions including a weekend. For Plodovi it was a full order-to-delivery loop across multiple retailers. Reconciliation should be automated: a script that pulls the current sheet, compares counts and totals against the system, and reports differences by row. Every mismatch is a finding. Roughly half will be bugs in the new system, and the rest will be rules you missed in the audit, which is exactly why the parallel run exists.
Parallel running costs operators double entry, so keep it short and visibly finite. Announce the number of cycles up front, show the shrinking mismatch count, and set the exit criterion in advance: a full cycle with zero unexplained differences. Ambiguity here is what turns a two-week parallel run into a six-month one where nobody ever trusts the system enough to switch.
The cutover checklist, and the spreadsheets we deliberately kept
Cutover is a scheduled, reversible event with a named owner, a freeze window, a final import, a verification pass, and a rollback path. Freeze edits to the spreadsheet, run the final import, verify record counts and a sample of high-value records, switch the sheet to read-only, and keep it archived rather than deleted.
Timing matters more than people expect. Cut over at the quietest point in the cycle, never mid-week for a business whose week peaks on Friday. Keep the frozen spreadsheet accessible read-only for at least a few months. It costs nothing and it removes the fear that makes people cling to the old process.
Not every sheet should be replaced. We deliberately left some in place in both engagements, because a custom screen would have been strictly worse. Ad-hoc one-off analysis, a finance model that changes shape every quarter, and scratch planning that never feeds another system are all better in a spreadsheet. The rule we apply: if the sheet is an input to an automated process or is edited by more than one person concurrently, it should become a system; if it is a private thinking tool with a short life, leave it and give the system a clean CSV export instead.
- • Name a cutover owner and a decision time for go or no-go.
- • Freeze spreadsheet edits and announce the freeze window to everyone who touches it.
- • Run the final import from the frozen file, with the same scripts used all through the parallel run.
- • Verify: record counts per entity, sum of key numeric fields, and a manual spot-check of ten high-value records.
- • Set the sheet to read-only, archive it, and link it from the new system's help page.
- • Keep a documented rollback: what it takes to go back to the sheet in the first 48 hours.
Winning over the operators who lose their spreadsheet's freedom
Operator resistance is not irrational. A spreadsheet lets you add a column in three seconds, sort any way you like, paste in a hundred rows, and write a note in a cell. A custom system usually takes those away. The features that win people over are the ones that give the freedom back: full export, bulk edit, free-text notes, and flexible search.
In practice, the shortlist is short and predictable. Export any list view to CSV or XLSX with the current filters applied, so nobody has to ask for a report. Bulk import and bulk edit, so a hundred rows are still a paste-and-confirm operation rather than a hundred form submissions. A notes field on every major entity, because operators will always have information the schema did not anticipate, and if you do not give them a place for it they will put it in the customer's surname. Fast filtering and saved views, which is what people actually miss when they say they miss Excel. Undo or at least a visible edit history, because spreadsheets have Ctrl+Z and forms usually do not.
Sequencing matters too. Ship the reporting and export features during the parallel run, not after cutover. The operators comparing the two systems every day are the people who decide whether the migration succeeds, and giving them the export button while they still hold the spreadsheet is what converts them from sceptics into the people who ask when they can stop the double entry.
Key Takeaways
- • Audit the spreadsheet cell by cell first: formulas, colour codes, and hidden helper columns are where the undocumented business rules live.
- • Classify every tab as an entity (things that exist) or an event (things that happened) before designing any schema.
- • Treat dirty history as its own phase, with idempotent import scripts and a human-readable rejects file that goes back to the operator, never a silent guess.
- • Run parallel for two to four full business cycles with the spreadsheet still authoritative, and exit only on a cycle with zero unexplained mismatches.
- • Keep spreadsheets that are private, short-lived analysis tools, and ship export, bulk edit, and saved views early to win operators over.
The full outcome of one of these migrations is written up in our MOVE & MEET case study, covering the move from spreadsheet-based operations to a website, mobile app, back office, and partner dashboard.
Frequently Asked Questions
How long does it take to replace spreadsheets with a custom internal system?
For a single operational domain with a handful of sheets, the audit and data modelling typically take one to two weeks, the build depends on scope, and the parallel run adds two to four business cycles on top. The part teams usually underestimate is data remediation, which scales with how many years of messy history you intend to carry over rather than with the number of features.
Should we just use Airtable or a no-code tool instead of a custom build?
Often yes, and it is worth testing first. No-code databases are a good fit when the workflow is mostly list management, the volume is modest, and no other system needs to integrate. Custom builds win when you need real business logic, role-based access for external parties like retailers or partners, mobile apps, or integrations with payments, logistics, and reporting systems that a no-code tool cannot express cleanly.
Do we have to migrate all the historical spreadsheet data?
No, and usually you should not. Migrate the entities you need to operate on day one, such as customers, products, and open orders, plus enough history to answer common questions. Older records can stay in the archived read-only spreadsheet or be loaded into a reporting warehouse later, which avoids blocking cutover on cleaning data nobody actively uses.
What is the biggest risk in a spreadsheet-to-system migration?
Missing an undocumented rule that the spreadsheet enforced through a formula or a colour convention. These surface as operational failures after cutover, when an invoice does not go out or a delivery is not scheduled. The parallel run exists specifically to catch them while the spreadsheet is still authoritative and the cost of the miss is a reconciliation line rather than a lost order.
How do we get the operations team to actually stop using the old spreadsheet?
Involve them in the audit so the system reflects how they really work, ship export and bulk-edit features before cutover so they do not lose flexibility, and set the sheet to read-only on a scheduled date rather than asking people to voluntarily stop. Keeping the archived file accessible removes the fear that drives shadow spreadsheets.
Sources
Written by
Filip Lauc
CEO, Jaspero
Filip Lauc is the CEO of Jaspero, a software development agency based in Osijek, Croatia. A full-stack JavaScript developer with over a decade of experience across Angular, Svelte, and Node.js, he leads Jaspero's work as a long-term embedded engineering partner for clients like GlycanAge, where his team has served as the dedicated engineering team for six years.
Let's Build Together
Your vision,
our expertise.
From AI integration to full-stack development, we turn ambitious ideas into products that perform.