How to migrate off HubSpot.
The export is the easy part — HubSpot documents every one of them. The hard parts are the auto-renewal clause, the associations that only survive as CSV columns, and the assets that were never yours to export. A runnable plan, in the order the calendar forces.

Almost everyone who leaves HubSpot discovers the same thing in the same order. First, that the data comes out cleanly — HubSpot documents every export path, and there are more of them than you expected. Second, that the contract does not come out cleanly at all, because mid-contract cancellation is not a thing and the renewal date was six weeks ago. The migration is not a technical project with a billing footnote. It is a billing project with a technical footnote, and if you do it in that order it takes an afternoon of scripting and one calendar reminder.
Can you export all your data from HubSpot?
Almost all of it, and HubSpot publishes the map. Contacts, companies, deals, tickets, calls, tasks, custom objects, properties, property history, knowledge base articles, page and blog HTML, files, HubDB tables, URL redirects, forms submissions, lists, and marketing email performance all have documented export paths, delivered as CSV, XLS, or XLSX via an emailed download link. HubSpot's own export index is the authoritative list — it is more complete than any third-party migration guide, including this one.
What you get is a snapshot of current property values plus associations, not a live system. That distinction is the whole migration. A contact row arrives with its Record ID and every property you asked for; the association to its company arrives as a column containing the associated record's ID and name. Rebuilding the graph on the other side is a join, not a rescue operation — but it is a join you have to write.
What are HubSpot's export limits?
They are generous enough that most teams never notice them, and specific enough to plan around. HubSpot allows 300 exports in a rolling 24-hour window, with three running concurrently and the rest queued. CSV files over 2 MB arrive zipped. Files split above 1,000,000 rows for CSV and XLSX, and above 65,535 rows for XLS. XLSX caps at 16,384 columns and XLS at 256, which is why HubSpot recommends CSV for wide exports — an account with hundreds of custom properties will hit the column ceiling before it hits the row one.
The number that actually bites is the smallest one: download links expire after 30 days. Export logs stay visible for three years, but the files behind them do not. If you export in January and cut over in March, you are exporting twice.
What does not survive the export?
Three categories, and only the third is a real loss. Structure that was never data: workflows export as a spreadsheet of names, enrollment counts, and modification dates, plus a PNG per workflow — the logic itself is a picture. Dashboards, reports, and sequences are the same story. Behaviour attached to HubSpot's runtime: HubSpot notes that forms lose their paid features and legacy CTAs stop functioning once the account is deactivated, so anything embedded on an external site needs replacing before you switch off, not after. History with a retention window: workflow action logs keep full detail for 180 days and high-level detail out to two years.
Associations are the part people get wrong, because they look like a loss and aren't. By default an export includes up to 1,000 associated record IDs per association column, which is fine for contacts and companies and not fine for a company with 3,000 contacts. In the export dialog, Customize lets you switch Associations included in export to All associated records — CSV only. Set it before you export, not after you find the truncation.
- Exports cleanlyContacts, companies, deals, tickets, calls, tasks, custom objects, properties and property history, knowledge base articles, page and post HTML, files, HubDB, redirects, form submissions, lists, users and permissions.
- Exports as a descriptionWorkflows — a spreadsheet of names and counts, plus a PNG of each flow. Dashboards and reports export their current output, not their definition. Budget rebuild time, not migration time.
- Exports through the API onlyInbox conversations and sales activity. HubSpot points you at the Conversations and Engagements APIs rather than a button; write the pull before you cancel, while the token still works.
- Stops working at deactivationEmbedded forms lose paid features, legacy CTAs stop functioning. These live on pages you control, so replace them ahead of the cutover date.
- Has a retention clockWorkflow action logs: full detail for 180 days, high-level detail to two years. Export logs remain visible for three years; the download links behind them do not.
Do the calendar first, on day one
HubSpot's Customer Terms of Service state that you may not cancel before the end of your current term and that no refunds are provided for prepaid or unused fees. Subscriptions auto-renew for the shorter of the same duration as your prior term or one year, so cancelling means turning off auto-renewal in Account & Billing → Subscriptions → Cancel auto-renewal — a Super Admin or billing admin action — before the renewal date. Downgrades are the same: mid-contract downgrades are not permitted, and HubSpot asks you to raise one with a representative at least five business days before renewal. Read the cancellation article and the Terms before you write a single line of migration code.
When should you start migrating off HubSpot?
Work backwards from the renewal date, not forwards from the decision. Turning off auto-renewal is reversible right up until the effective date, so the safe sequence is: switch it off first, then evaluate, then turn it back on if you change your mind. Doing it in the other order — evaluate, decide, then discover the renewal passed in week three — is how a six-week project becomes a fourteen-month one.
A workable shape for a team with a year-long term: kill auto-renewal on day one, run the export and the mapping in the following fortnight, then run both systems in parallel until you have watched a full billing cycle of real work land in the new one. You are still paying for HubSpot during that window regardless, which makes the parallel run free. That is the one advantage of a term you can't exit early — you may as well use it.
How do you get HubSpot data into Munin?
Two paths, and which one you take depends on whether you are moving a list or a history. For contacts, companies, and deals as they stand today, map the CSV onto crm_bulk_create_contacts, which takes up to 500 contacts per call and deduplicates against existing email and phone — and against the do-not-contact list, so a re-import cannot resurrect someone who opted out. For a full graph with pipelines, stages, activities, and relationships intact, build the crm_import payload instead: it lands records in dependency order and returns an idMap from your source IDs to Munin's.
Be clear-eyed about the shape of the work. Munin's import tools consume Munin's own export format, so coming from HubSpot means writing the mapping yourself — there is no vendor-specific HubSpot importer to click. What makes that a morning rather than a quarter is the size of the target: seven record types on the CRM side, not the several hundred properties a HubSpot portal accumulates. You decide once which columns matter, and the ones you leave behind were the ones nobody filtered on anyway. Every module has the same pair — crm_import, kb_import, cms_import, conv_import, outreach_import, analytics_import — and the matching _export tool, which is the same door used in the other direction on the day you want to leave us.
import csv
# HubSpot: CRM > Contacts > Export, format CSV,
# Customize > All properties and associations on records.
rows = list(csv.DictReader(open("hubspot-crm-exports-all-contacts.csv")))
contacts = [
{
"name": f"{r['First Name']} {r['Last Name']}".strip(),
"email": r["Email"] or None,
"phone": r["Phone Number"] or None,
"title": r["Job Title"] or None,
"tags": ["hubspot-import-2026-08"],
"customFields": {"hubspotRecordId": r["Record ID"]},
}
for r in rows
]
# crm_bulk_create_contacts caps at 500 per call.
batches = [contacts[i : i + 500] for i in range(0, len(contacts), 500)]Tag the import, keep the old ID
Two habits that turn a scary one-way migration into something you can undo. Tag every imported record with the batch (hubspot-import-2026-08) so crm_list_contacts({ tag: "..." }) gives you the exact set back. And keep the HubSpot Record ID in customFields — it is the join key for every follow-up pass, and the only way to reconcile against the source six months later when someone asks why a deal amount looks wrong.
What order do the records have to go in?
Foreign-key order, because the parents have to exist before the children can point at them. crm_import handles this internally — pipelines, then segments, then companies, then contacts, then deals, then activities, then relationships — and returns an idMap mapping every source ID to the ID it received on this server. Thread that map into each subsequent import call and dependent records resolve their parents automatically.
The upserts are keyed so that re-running is safe: pipelines by slug, segments by name, companies by domain and falling back to name, contacts by email. Deals, activities, and relationships get fresh IDs each run with parents resolved through the idMap, which is why you keep the map rather than regenerating it. Conversations are the one module to run deliberately: conv_import is idempotent within a migration via the idMap, but messages are append-only and are not deduplicated across separate runs. Import conversations once, keep the map, and take a database snapshot first — the same advice as any other bulk load.
{
"name": "crm_import",
"arguments": {
"records": {
"pipelines": [
{ "id": "hs-default", "name": "Sales", "slug": "sales",
"stages": [
{ "id": "appointmentscheduled", "name": "Appointment scheduled",
"position": 0, "winLoss": "open" },
{ "id": "closedwon", "name": "Closed won",
"position": 5, "winLoss": "won" }
] }
],
"companies": [
{ "id": "7301442891", "name": "Acme Inc.", "domain": "acme.com",
"tags": ["hubspot-import-2026-08"], "customFields": {} }
],
"contacts": [
{ "id": "501993", "name": "Vita Costa", "email": "vita@acme.com",
"companyId": "7301442891", "tags": ["hubspot-import-2026-08"],
"customFields": { "hubspotRecordId": "501993" } }
],
"deals": [
{ "id": "18442310", "name": "Acme — renewal", "pipelineId": "hs-default",
"stageId": "appointmentscheduled", "primaryContactId": "501993",
"companyId": "7301442891", "amountCents": 1200000, "currency": "EUR" }
],
"segments": [], "activities": [], "relationships": []
}
}
}If the marketing suite is why you're there, stay
HubSpot's marketing automation, its template and app marketplace, and its partner ecosystem are genuinely deep, and nothing on this page replaces a campaign engine with a decade of accumulated integrations behind it. If that machine is running your revenue, migrating away from it is a downgrade dressed as a saving. Buy the renewal.
Munin is the move for a different team: one that wants CRM, conversations, knowledge base, CMS, outreach, and analytics on one Postgres and one customer record, priced flat instead of per seat and per marketing contact, under a licence that lets them run the whole thing themselves. If the reason you are shopping is that the bill grows every time the database does, the migration above is the whole project.
What does the stack look like on the other side?
Smaller, and driven differently. The six modules share one schema, so a contact is a row rather than six synchronised copies, and an erasure request has one place to land. There is no per-seat meter and no marketing-contact tier: Munin Cloud Free is €0/month with 5,000 MCP calls, 250 contacts, and 100 MB, and self-hosting under MIT is docker compose up with no licence fee at all.
The operating model is the part worth planning for. Munin exposes 155 MCP tools at one endpoint, so the thing that replaces clicking through a dashboard is an MCP client — Claude Code, Claude Desktop, ChatGPT, Cursor, or a runner you write — asking for the outcome and showing you the write before it happens. Teams that arrive from HubSpot expecting a screen-for-screen replacement have the adjustment; teams that arrive wanting the CRM to fill itself in from conversations they were already having tend to be productive in the first week. Decide which of those you are before you schedule the cutover, because it changes who on your team needs to be in the room.
- 01Turn off auto-renewal first. It is reversible until the effective date; the renewal date is not.
- 02Export with Customize → All properties and associations, CSV, All associated records. Not the default view.
- 03Write the API pulls for inbox conversations and sales activity while the token still works.
- 04Replace embedded forms and legacy CTAs on your own pages before the deactivation date.
- 05Import in FK order, thread the idMap, tag the batch, keep the HubSpot Record ID.
- 06Run both systems for one full billing cycle. You are paying for the term anyway.
- 07Re-export near cutover. The download links from month one have expired.
Frequently asked questions
How do I export all my data from HubSpot? There is no single button. HubSpot maintains an export index covering records, properties, knowledge base articles, page and post HTML, files, HubDB, forms, lists, and reporting, each with its own path. Records export from the object's list view via Export; every file arrives as an emailed download link that expires after 30 days.
Can I cancel HubSpot mid-contract? No. HubSpot's Customer Terms of Service state that you may not cancel before the end of your current term, and no refunds are given for prepaid or unused fees. You turn off auto-renewal in Account & Billing so the subscription ends on its renewal date, then the account drops to HubSpot's free tools. Downgrades follow the same rule and should be raised at least five business days before renewal.
Do HubSpot exports include associations between contacts, companies, and deals? Yes, as columns. Each associated object gets its own column containing associated record IDs and names, capped by default at 1,000 IDs per column. Choosing All associated records under Customize in the export dialog removes the cap, and is available for CSV files only.
What can't I export from HubSpot? Workflow logic, dashboards, reports, and sequences — you can export a spreadsheet describing your workflows and a PNG image of each one, but not the automation itself. Inbox conversations and sales activity come out through the Conversations and Engagements APIs rather than a button. Budget rebuild time for these rather than migration time.
How long does migrating from HubSpot to Munin take?
The data work is typically a day or two: export, write the column mapping, run crm_bulk_create_contacts in batches of 500 or build a crm_import payload, then verify counts. The calendar is the long pole — auto-renewal has to be cancelled before your renewal date, and running both systems in parallel for one billing cycle is the step that catches what the CSV didn't carry.
Is there a free CRM I can migrate to from HubSpot? Several. Munin Cloud Free is €0/month, EU-hosted, and includes CRM, conversations, knowledge base, CMS, outreach, and analytics; self-hosting under MIT is free at any scale. Twenty, EspoCRM, and SuiteCRM are CRM-only options — the licence-by-licence comparison is here.
The short version
- HubSpot documents an export path for nearly everything, and the data comes out clean. Start with the export index, not a third-party guide.
- Mid-contract cancellation is not permitted and there are no refunds for unused fees. Turn off auto-renewal on day one — it is reversible until the effective date.
- Set Customize → All properties and associations → All associated records before exporting, or association columns truncate at 1,000 IDs.
- Download links expire after 30 days, so a migration spanning two months means exporting twice.
- Workflows, dashboards, and sequences export as descriptions, not definitions. Inbox and activity data come through the Conversations and Engagements APIs.
- Into Munin: map the CSV onto crm_bulk_create_contacts in batches of 500, or build a crm_import payload and thread the returned idMap. Tag the batch and keep the HubSpot Record ID as the join key.
- On the other side: six modules on one Postgres, 155 MCP tools at one endpoint, Cloud Free at €0/month, and MIT self-hosting with docker compose up.
If you want somewhere to land the CSV this week, Munin Cloud is free and EU-hosted, and the same tools that import your HubSpot data will export it again the day you want to go somewhere else.
Migrating off HubSpot is a calendar problem wearing a data problem's clothes.