30-second verdict
Freeze the spreadsheet first. Migrate only what has moved in the last 24 months and archive the rest as CSV. Map every column to a named CRM property, in writing, before you touch the import button. Dedupe in the spreadsheet, because merging records after import is manual and slow. Run a 20-row test import, then the real one, then verify: record counts must reconcile, deal amounts must match to the dollar, and 10 random records must pass a spot check. Keep the old sheet read-only for one quarter. A one-tab contact list is a careful afternoon of DIY. A multi-tab workbook with deal history is 8 to 20 hours of work, and that is the version people call us for.
The FINAL_v7.xlsx problem
Every team that calls us about a CRM has the same file. It is called FINAL_v7.xlsx, or Master Client List (USE THIS ONE).xlsx. It has nine tabs and three of them matter. One column is colour-coded, and exactly one person on the team knows what yellow means.
The fear that keeps teams on that file is losing history. Five years of who bought what, who said call back in spring, which quote went out twice. Here is what we have learned moving teams off these files: CRMs do not eat history. People lose history in three specific ways. They import before deduping, then merge records badly. They skip columns the import tool could not auto-match. And they keep editing the old spreadsheet after go-live, so the truth forks. Every step below exists to block one of those three.
Prerequisites
You need admin access in the CRM, export rights on the spreadsheet, and the person who knows what the columns mean in the room for the mapping session. You do not need a paid tier for the import itself: HubSpot's free tier imports contacts, companies, and deals. Pipedrive has no free tier, but every plan imports. GoHighLevel imports on its base plan. If you have not picked a CRM yet, read our HubSpot vs GoHighLevel vs Salesforce comparison first; the steps below use HubSpot names, but the order is the same everywhere. And one honest exit ramp: if your file is one tab and under 200 rows, you do not need this guide. Import it, fix the stragglers by hand, and be done in an hour.
The migration in eight steps
Step 1: Freeze the file
Save a copy named crm-migration-source.xlsx. That copy is what you clean and import. Then tell the team, in writing, that the original is frozen as of today. The most common way history gets lost is not a failed import. It is three weeks of edits made to the spreadsheet while the migration crawled along, edits that never reach the CRM.
If the business cannot pause, add one new tab called changes-during-migration and make people log updates there: row, what changed, when. You apply that tab by hand after the import. It feels clunky. It is far less clunky than discovering in August that a renewal got recorded in a file nobody reads anymore.
Step 2: Decide what migrates and what gets archived
The rule we use: migrate anything with activity in the last 24 months, archive the rest. Archive means export to CSV, store in a folder named CRM archive 2026, set view-only. Nothing is destroyed. It just does not come along.
| Data in the sheet | Migrate or archive | Why |
|---|---|---|
| Active customers and open deals | Migrate | This is the business. |
| Contacts touched in the last 24 months | Migrate | Recent enough to act on. |
| Closed deals, last two fiscal years | Migrate | You want win rates and repeat-buyer history. |
| Leads with no touch in 24+ months | Archive | They distort every report, and CRMs that price by contact count charge you for them monthly. |
| Hard bounces and unsubscribes | Archive, except the opt-out list | Load the do-not-email addresses into the CRM's opt-out list so nobody emails them again. In Canada, CASL makes this non-optional. |
| Old quotes, dead SKUs, the 2019 trade-show tab | Archive | Look-up material, not working data. |
Step 3: Map every column to a property, in writing
Add a tab called mapping with four columns: spreadsheet column, CRM property, property type, decision notes. Every column in the source gets a row, including the ones you decide to skip. Skipping is fine. Skipping by accident is how history disappears.
- Name must become First name and Last name before import. Use Data, then Text to Columns, delimited by space, then fix the two-word surnames by hand.
- Status columns full of values like warm, HOT, follow up, and customer?? should not be forced into Lifecycle stage. In HubSpot, go to Settings, Properties, Create property. Object type: Contact. Label: Legacy status. Field type: Dropdown select. Options: exactly the cleaned values you settle on in Step 5.
- Notes columns map to a Multi-line text property called Legacy notes. The standard import will not turn a notes column into timeline activities, and that is fine. A multi-line property keeps every word on the record and searchable. Sort it into real notes and tasks later, one record at a time, only when you touch that record anyway.
- Anything with money gets field type Number. Anything with a date gets Date picker.
Where this breaks: the date columns
Three things hide in a Last contacted column: Excel serial numbers like 45083, dates two different people typed two different ways (03/04 means March 4 to one of them and April 3 to the other), and "Mar 3rd" typed as plain text. HubSpot's import asks you to pick one date format for the whole file. Mixed formats fail row by row, and the error file gives you row numbers, not explanations. Fix it in Excel first. Add a helper column with =TEXT(A2,"yyyy-mm-dd") and sort it. Real dates sort cleanly; text pretending to be a date clumps together. Fix those by hand. Any date in the future, or from before the company existed, is a typo.
Step 4: Dedupe before import, not after
HubSpot dedupes contacts by email and companies by company domain name at import time. Two rows with the same email collapse into one record, which is what you want. But the same human with two different emails becomes two records, and you will not notice until both get the renewal email. Merging after import is one pair at a time, by hand, and a merge cannot be cleanly undone. This is why deduping happens in the spreadsheet, where you can still see everything side by side.
The mechanics: add a helper column with =COUNTIF($C$2:$C$5000,C2)>1 against the email column and filter for TRUE. That catches exact duplicates. For the same person under two emails, sort by last name and scan. It is manual, and it is the single highest-value hour of the whole project. Decide your survivor rule before you start: keep the row with the most recent activity, copy any non-empty cells from the losing row into it, delete the loser.
Watch for shared inboxes. Rows where the email is info@ or accounting@ will collapse into one contact if two people share that address. Either find real addresses now or accept one record per inbox, and write that decision in the mapping tab.
Where this breaks: two emails in one cell
Somewhere in your file there is a cell that reads pat.work@company.com / pat@gmail.com. The import either rejects that row as an invalid email or stores the whole string as the address, and that contact is unreachable forever. Find these before import: search the email column for a space, a comma, a semicolon, and a slash. A valid email contains none of those. Keep the best address in Email and move the second one to a Single-line text property called Secondary email.
Step 5: Clean the values your dropdowns will choke on
For every column that maps to a dropdown, get its unique values: copy the column to a scratch tab and run Data, Remove Duplicates. You will find Toronto, toronto with a trailing space, TO, and GTA, all meaning the same place. Pick one canonical value per real-world thing and find-and-replace until the unique list is short and clean. Run =TRIM() across every text column; trailing spaces are invisible to you and create dropdown options that look identical but are not. Provinces deserve special attention: Ontario, ON, and ont. will show as three separate regions in every report until you pick one spelling.
Step 6: Run a 20-row test import
Pick 20 rows on purpose, not at random: the oldest record, the fullest row, the emptiest row, a name with an accent in it, the customer with the strange date, your biggest account. Save them as test-import.csv.
In HubSpot: Contacts, Import, Start an import, File from computer. On the mapping screen, every column must show either a mapped property or a deliberate skip. HubSpot will not let you finish while columns are unmatched, but the way past that gate is a single checkbox that skips all unmatched columns at once. That checkbox is where history goes to die. Go line by line instead. When the import asks for your file's date format, pick the one you standardized in Step 3.
Then open all 20 records and read them against the sheet. Delete the test records, fix whatever you found in the source file, and repeat until a test run comes through clean. Two or three rounds is normal.
Step 7: The full import and the verification pass
Run the real import, then verify three ways before you tell anyone it is done.
- Counts. Rows in the cleaned sheet, minus the header, must equal created plus updated in the import summary. HubSpot shows new records, updated records, and errors for every import. If errors are above zero, download the error file: it is your failed rows with an extra column explaining each failure. Fix those rows and re-import only them.
- Money. If you imported deals, compare the sum of the Amount property for this import against =SUM() of the amount column in the sheet. They match to the dollar or you find out why not. This is the check that catches a dropped row faster than anything else.
- Spot checks. Add a =RAND() column to the sheet, sort, and take the top 10 rows. Check those 10 records field by field in the CRM, plus the oldest record and the biggest customer. Pay attention to Legacy status, every date field, and the Legacy notes blob.
Step 8: Make the old sheet read-only for one quarter
Do not delete it. In Google Sheets, open Share and change everyone to Viewer. On SharePoint or Drive, set the Excel file to view-only the same way. Rename it so its status is obvious: ARCHIVED 2026-06, read-only, use the CRM.
Why a quarter? Because that is roughly how long it takes the team to hit the edge cases. Quarter-end invoicing. The annual renewal. The report one person ran every March and nobody else knew about. When someone needs a thing that did not migrate, they find it in the read-only file or the archive folder, and you move that one record into the CRM properly. What you must not do is reopen the sheet for edits, even once. Two systems of record means you have zero. After a quarter with no lookups, the file is an archive, not a fallback, and the migration is genuinely finished.
How you know it worked
Run this acceptance test two weeks after go-live. Every line should pass.
- The import summary reconciles: created plus updated equals your cleaned row count, and every error row was fixed or consciously archived.
- Deal amounts in the CRM match the spreadsheet totals to the dollar.
- Ten random records, the oldest record, and the biggest customer all pass a field-by-field spot check.
- Anyone on the team can find their own accounts without opening the old file. Watch them do it. Do not ask whether they can.
- The old sheet's version history shows zero edits since the freeze date.
- The first report the team actually uses, pipeline by stage or contacts by owner, looks right to the people who know the accounts.
If any line fails, stop adding new things to the CRM and fix it first. A CRM the team only half-trusts gets quietly abandoned, and the team drifts back to a new spreadsheet within months.
When to do this yourself, and when to stop
Do it yourself if the file is one tab, under a thousand rows, and contacts only. Follow this guide on a quiet afternoon. You do not need a consultant for that, and if you call us with that file, we will tell you the same thing.
DIY stops making sense when the tabs reference each other through VLOOKUPs nobody dares touch, when you need deal history with stages and amounts intact, when two source spreadsheets disagree about the same customers, or when the sheet feeds invoicing or anything regulated. The largest version of this we have built moved a permit-heavy operation onto HubSpot: 300+ permits tracked across connected pipelines, with deal-stage changes triggering QuickBooks invoicing and payment reconciliation. That operation started in spreadsheets too. The automation only works because the data underneath was mapped and deduped first, which is why migration sits at the start of every RevOps build we do.
What it costs at our flat $150/hr CAD:
- A contacts-only cleanup and import typically runs 3 to 5 hours, so $450 to $750.
- Contacts, companies, and open deals with a messy notes column typically runs 8 to 15 hours, so $1,200 to $2,250.
- A multi-tab workbook that drives invoicing gets a written quote after we see the file, and it is usually north of 20 hours.
Per how we work: scope is quoted in writing before we start, hours never expire, and there are no retainers. Sometimes the honest quote is a single hour, where we build the mapping tab with you on a call and you run the import yourself. Pricing is here, and if you want us to look at your file first, send it over.
FAQ
Should we just import everything, in case we need it later?
No. Dead rows make every report lie, and on CRMs that price by contact count you pay for them every month. Archive anything untouched for 24 months as CSV in a view-only folder. Nothing is destroyed, and you can migrate an archived record properly the day someone actually needs it.
What if our spreadsheet has no email addresses?
Then the CRM cannot dedupe for you, because email is the matching key for contacts in HubSpot and most other tools. Dedupe fully in the spreadsheet by name and phone, and accept that this is the slow, manual part. Also ask whether you really have a contact list at all: a sheet with company names and no emails is often a company list, and importing it as companies, matched by domain, is the better move.
Can we keep updating the spreadsheet alongside the CRM for a while?
No. Two systems of record means neither one is the record, and within a month you will not know which version of a customer is true. The sheet goes read-only on go-live day. If the spreadsheet did something the CRM does not do yet, treat that as a gap to close in the CRM, not a reason to keep both alive.
How long does a migration take?
Working hours are in the ranges above: an afternoon for a simple list, 8 to 20 hours for a workbook with deals and history. Calendar time matters more than people expect. Book the freeze, the cleanup, and the import inside two weeks. A migration that drags for two months loses, because the frozen sheet drifts away from reality and you end up cleaning the same data twice.
Want this handled instead of read about?
We scope this exact work in hours, quote it in writing, and ship it in weeks. The 30-minute call is free and useful either way.
Book a 30-minute call$150/hr flat · published pricing · no retainer pitch