How to Migrate Salesforce to HubSpot Without Data Loss


Key Takeaways
Quick Answer
To migrate Salesforce to HubSpot without data loss, export all Salesforce objects as CSV files, map fields to HubSpot properties, deduplicate records, then import using HubSpot's native Salesforce migration tool or a middleware like Trujay. Validate record counts and field integrity at every stage.
Why Are Companies Moving from Salesforce to HubSpot in APAC?
The shift is largely about cost and operational complexity. According to HubSpot's 2024 ROI Report, companies that switched from Salesforce to HubSpot reported a 23% reduction in total cost of ownership over three years. For mid-market companies across Hong Kong, Singapore, and Australia — where regional teams often manage CRM administration without dedicated Salesforce admins — the operational overhead of Salesforce becomes disproportionate.
That said, this migration is not trivial. Salesforce and HubSpot model data differently. Salesforce uses a relational database structure with Accounts, Contacts, Opportunities, and custom objects linked by lookup relationships. HubSpot uses a flatter model with Companies, Contacts, and Deals connected through associations. Understanding this structural difference before you touch a single record is the difference between a clean migration and months of cleanup.
Global companies using Salesforce as their worldwide CRM often find that APAC subsidiaries — particularly in markets like Taiwan, Vietnam, and the Philippines — underutilize Salesforce's enterprise features. Moving these regional operations to HubSpot while maintaining data fidelity lets them right-size their CRM spend without losing historical intelligence.
What Should You Do Before Starting the Migration?
Audit Your Salesforce Data
Before exporting anything, run a full data audit. This step alone prevents the majority of migration failures.
Open Salesforce and navigate to Setup → Object Manager. For each object you plan to migrate, document:
- Total record count
- Number of custom fields versus standard fields
- Lookup and master-detail relationships
- Fields with picklist values (you'll need to recreate these in HubSpot)
- Attachments and file storage volume
Run this SOQL query in the Salesforce Developer Console to get a count of all Contacts with associated Opportunities:
1SELECT COUNT(Id), Account.Name2FROM Contact3WHERE Id IN (SELECT ContactId FROM OpportunityContactRole)4GROUP BY Account.Name5ORDER BY COUNT(Id) DESC
This tells you which accounts carry the most relationship complexity and where data loss risks are highest.
Identify What NOT to Migrate
Not everything in Salesforce belongs in HubSpot. According to Validity's 2023 State of CRM Data Management report, roughly 25% of CRM records are duplicates or stale data. Migrating garbage into a new system just gives you expensive garbage.
Create exclusion criteria:
- Contacts with no activity in the last 24 months
- Leads that were never converted
- Test records (filter for email domains like
@test.comor@example.com) - Duplicate accounts (use Salesforce's built-in Duplicate Management rules or a tool like DemandTools by Validity)
Map Salesforce Fields to HubSpot Properties
This is where most teams underestimate the work. Create a spreadsheet with four columns:
- Salesforce Field API Name (e.g.,
Account.BillingCountry) - Salesforce Field Type (e.g., Picklist, Text, Date)
- HubSpot Target Property (e.g.,
company.country) - Transformation Required (e.g., "Map picklist values to HubSpot dropdown options")
HubSpot has specific property types: Single-line text, Multi-line text, Dropdown select, Number, Date picker, and Checkbox. Salesforce's multi-select picklist fields require special attention — HubSpot handles these as "Multiple checkboxes" properties, and the delimiter format differs.
For Salesforce multi-select values stored as Value1;Value2;Value3, HubSpot expects semicolons as well, but you must ensure no trailing spaces exist. This Python snippet handles the cleanup:
1import pandas as pd23df = pd.read_csv('salesforce_contacts_export.csv')45# Clean multi-select picklist fields6def clean_multiselect(value):7 if pd.isna(value):8 return ''9 return ';'.join([v.strip() for v in str(value).split(';')])1011df['Industry_Segments'] = df['Industry_Segments'].apply(clean_multiselect)1213# Standardize date formats to YYYY-MM-DD for HubSpot14df['CloseDate'] = pd.to_datetime(df['CloseDate']).dt.strftime('%Y-%m-%d')1516df.to_csv('hubspot_contacts_import.csv', index=False)
Ready to Transform Your Ecommerce Operations?
Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.
How Do You Export Data from Salesforce Correctly?
Use Data Loader for Large Exports
Salesforce's built-in report export is limited to 2,000 rows in the browser. For any serious migration, use Salesforce Data Loader (the desktop app, not the web-based alternative) or Dataloader.io for cloud-based exports.
Export objects in this order to preserve relational integrity:
- Accounts (these become Companies in HubSpot)
- Contacts (linked to Companies via Company Name or domain)
- Opportunities (these become Deals in HubSpot)
- Tasks and Activities (these become Engagements in HubSpot)
- Notes and Attachments
- Custom Objects (these require HubSpot Custom Objects, available on Enterprise plans)
For each export, include the Salesforce Record ID (Id field). You'll need this as a reference key during validation.
In Data Loader, use this approach:
1Operation: Export2Object: Account3Fields: Id, Name, BillingStreet, BillingCity, BillingState,4 BillingPostalCode, BillingCountry, Phone, Website,5 Industry, AnnualRevenue, NumberOfEmployees, OwnerId,6 CreatedDate, LastModifiedDate7Filter: LastModifiedDate >= 2020-01-01T00:00:00Z
Export Attachments Separately
Salesforce stores files in ContentDocument and ContentVersion objects (Lightning) or the legacy Attachment object (Classic). These won't come through a standard CSV export.
Query ContentVersion for all files:
1SELECT Id, Title, FileExtension, ContentDocumentId,2 FirstPublishLocationId, ContentSize3FROM ContentVersion4WHERE IsLatest = true5ORDER BY ContentSize DESC
This gives you the total file volume. HubSpot's file storage limits vary by plan — the Professional plan includes 5GB of file storage according to HubSpot's 2024 pricing page. If your Salesforce org has 50GB of attachments, you'll need a strategy: migrate only attachments linked to active deals, or use an external storage service like AWS S3 with linked URLs in HubSpot.
How Do You Set Up HubSpot Before Importing?
Create Custom Properties First
Every custom field from Salesforce that doesn't have a standard HubSpot equivalent needs to be created as a custom property before import. Navigate to Settings → Properties in HubSpot and create properties for each object type.
If you have more than 20 custom properties, use the HubSpot API to batch-create them:
1import requests2import json34api_key = 'your-hubspot-private-app-token'5headers = {6 'Authorization': f'Bearer {api_key}',7 'Content-Type': 'application/json'8}910properties_to_create = [11 {12 "name": "sf_account_tier",13 "label": "Account Tier",14 "type": "enumeration",15 "fieldType": "select",16 "groupName": "contactinformation",17 "options": [18 {"label": "Tier 1", "value": "tier_1"},19 {"label": "Tier 2", "value": "tier_2"},20 {"label": "Tier 3", "value": "tier_3"}21 ]22 },23 {24 "name": "sf_region",25 "label": "APAC Region",26 "type": "enumeration",27 "fieldType": "select",28 "groupName": "contactinformation",29 "options": [30 {"label": "Greater China", "value": "greater_china"},31 {"label": "Southeast Asia", "value": "southeast_asia"},32 {"label": "ANZ", "value": "anz"}33 ]34 }35]3637for prop in properties_to_create:38 response = requests.post(39 'https://api.hubapi.com/crm/v3/properties/contacts',40 headers=headers,41 data=json.dumps(prop)42 )43 print(f"Created {prop['name']}: {response.status_code}")
Configure Deal Pipelines and Stages
Salesforce Opportunity Stages don't automatically map to HubSpot Deal Stages. Before importing deals, recreate your pipeline in Settings → Objects → Deals → Pipelines.
Document the mapping explicitly:
- Salesforce "Prospecting" → HubSpot "Appointment Scheduled"
- Salesforce "Qualification" → HubSpot "Qualified to Buy"
- Salesforce "Needs Analysis" → HubSpot "Presentation Scheduled"
- Salesforce "Closed Won" → HubSpot "Closed Won"
- Salesforce "Closed Lost" → HubSpot "Closed Lost"
Stage probability percentages work differently between platforms. Salesforce assigns probability at the stage level; HubSpot uses deal-level forecasting. Plan for this behavioral difference in your sales team's workflows.
Ready to Transform Your Ecommerce Operations?
Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.
What Is the Step-by-Step Import Process?
Option 1: HubSpot's Native Import Tool
For datasets under 500,000 rows, HubSpot's built-in CSV import works reliably.
- Go to Contacts → Import (or Deals, Companies)
- Select "File from computer"
- Choose the object type
- Upload your cleaned CSV
- Map each CSV column to a HubSpot property
- Select your deduplication key (Email for Contacts, Domain for Companies)
- Run the import
Critical detail: set the import to "Update existing records and create new ones" rather than "Create new records only." If you've run a test import previously, this prevents duplicates.
Option 2: Middleware Migration Tools
For complex migrations involving more than three object types or requiring association preservation, use a dedicated tool:
- Trujay (now Import2) — Handles direct Salesforce-to-HubSpot transfers with field mapping UI. Pricing starts at approximately $500 for 10,000 records.
- Insycle — Stronger for deduplication and ongoing data quality, useful if your Salesforce data is messy.
- HubSpot's Salesforce Integration — This can sync data bidirectionally, which some teams use as a temporary bridge during migration. However, it's designed for ongoing sync, not one-time migration, and can create confusion if left running after the migration.
Option 3: Custom API Migration
For Enterprise-level migrations with custom objects, workflows, and complex associations, a scripted API migration gives you the most control.
Here's a Python script that migrates Salesforce Accounts to HubSpot Companies using both APIs:
1from simple_salesforce import Salesforce2import requests3import json4import time56# Connect to Salesforce7sf = Salesforce(8 username='your-sf-username',9 password='your-sf-password',10 security_token='your-sf-token',11 domain='login' # Use 'test' for sandbox12)1314# HubSpot setup15hs_token = 'your-hubspot-private-app-token'16hs_headers = {17 'Authorization': f'Bearer {hs_token}',18 'Content-Type': 'application/json'19}2021# Query Salesforce Accounts22accounts = sf.query_all(23 "SELECT Id, Name, Website, Phone, BillingCountry, "24 "Industry, AnnualRevenue, NumberOfEmployees "25 "FROM Account WHERE IsDeleted = false"26)2728migration_log = []2930for record in accounts['records']:31 hubspot_company = {32 "properties": {33 "name": record.get('Name', ''),34 "domain": record.get('Website', ''),35 "phone": record.get('Phone', ''),36 "country": record.get('BillingCountry', ''),37 "industry": record.get('Industry', ''),38 "annualrevenue": str(record.get('AnnualRevenue', '')),39 "numberofemployees": str(record.get('NumberOfEmployees', '')),40 "salesforce_id": record['Id'] # Custom property for reference41 }42 }4344 response = requests.post(45 'https://api.hubapi.com/crm/v3/objects/companies',46 headers=hs_headers,47 data=json.dumps(hubspot_company)48 )4950 migration_log.append({51 'sf_id': record['Id'],52 'sf_name': record['Name'],53 'hs_status': response.status_code,54 'hs_id': response.json().get('id', 'FAILED')55 })5657 # Respect HubSpot API rate limits (100 requests per 10 seconds)58 time.sleep(0.15)5960# Save migration log for validation61import csv62with open('migration_log.csv', 'w', newline='') as f:63 writer = csv.DictWriter(f, fieldnames=migration_log[0].keys())64 writer.writeheader()65 writer.writerows(migration_log)6667print(f"Migrated {len(migration_log)} accounts")68print(f"Failed: {sum(1 for r in migration_log if r['hs_status'] != 201)}")
Always store the Salesforce Record ID as a custom property in HubSpot. This creates a lookup key for validating the migration and is essential for troubleshooting.
How Do You Validate That No Data Was Lost?
Validation is non-negotiable. A migration without validation is just a data copy with wishful thinking.
Record Count Comparison
Compare record counts across every object type:
1# Salesforce counts2sf_accounts = sf.query("SELECT COUNT(Id) FROM Account WHERE IsDeleted = false")3sf_contacts = sf.query("SELECT COUNT(Id) FROM Contact WHERE IsDeleted = false")4sf_opps = sf.query("SELECT COUNT(Id) FROM Opportunity WHERE IsDeleted = false")56# HubSpot counts (using search API)7def get_hs_count(object_type):8 response = requests.post(9 f'https://api.hubapi.com/crm/v3/objects/{object_type}/search',10 headers=hs_headers,11 data=json.dumps({"filterGroups": [], "limit": 0})12 )13 return response.json()['total']1415print(f"Accounts: SF={sf_accounts['records'][0]['expr0']} | HS={get_hs_count('companies')}")16print(f"Contacts: SF={sf_contacts['records'][0]['expr0']} | HS={get_hs_count('contacts')}")17print(f"Opportunities: SF={sf_opps['records'][0]['expr0']} | HS={get_hs_count('deals')}")
Field-Level Spot Checks
Randomly sample 50 records from each object. For each, compare every field value between the Salesforce export CSV and the HubSpot record. Focus on:
- Currency fields (Salesforce supports multi-currency; HubSpot uses a single currency per portal unless you're on Enterprise)
- Date fields (timezone handling differs — Salesforce stores in UTC, HubSpot displays in portal timezone)
- Rich text fields (Salesforce HTML formatting may not render identically in HubSpot)
Association Verification
Check that Contacts are still associated with their correct Companies, and Deals are linked to the right Contacts and Companies. This is where most migrations silently fail.
Pull a sample of HubSpot associations via the API and cross-reference against your Salesforce export:
1# Verify contact-company associations2contact_id = '12345' # Sample HubSpot contact ID3response = requests.get(4 f'https://api.hubapi.com/crm/v4/objects/contacts/{contact_id}/associations/companies',5 headers=hs_headers6)7associations = response.json()8print(f"Contact {contact_id} is associated with companies: {associations}")
Ready to Transform Your Ecommerce Operations?
Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.
What Are the Common Pitfalls to Avoid?
Losing Activity History
Salesforce Tasks and Events don't have a direct 1:1 equivalent in HubSpot. HubSpot uses Engagements (calls, emails, meetings, notes, tasks). When migrating activities, you must map Salesforce Task.Type to the correct HubSpot engagement type.
According to HubSpot's developer documentation, engagement timestamps require Unix millisecond format — not ISO 8601. This single format difference accounts for a disproportionate number of "missing" activities that were actually imported with a date of January 1, 1970.
Ignoring Owner Mapping
Salesforce uses User IDs for record ownership. HubSpot uses Owner IDs. Before importing, create all users in HubSpot and build a mapping table:
- Salesforce User ID
005xx000001abc→ HubSpot Owner ID12345678
Replace all OwnerId values in your export files before importing.
Underestimating Custom Object Complexity
HubSpot Custom Objects (available on Enterprise plans) can replicate Salesforce custom objects, but they have limitations: a maximum of 10 custom objects and 500,000 records per object, according to HubSpot's 2024 product limits documentation. If your Salesforce org relies heavily on custom objects with millions of records, this constraint requires architectural decisions before migration.
A Real Migration: What We Learned Moving 430,000 Records
In late 2023, Branch8 migrated a Singapore-based fintech company from Salesforce Sales Cloud to HubSpot Sales Hub Enterprise. The Salesforce org contained approximately 430,000 Contacts, 85,000 Accounts, and 12,000 Opportunities across three pipelines, plus two custom objects tracking regulatory compliance interactions specific to MAS (Monetary Authority of Singapore) requirements.
The migration took six weeks end-to-end. We used a combination of Python scripts with the simple-salesforce library (v1.12.4) for extraction and the HubSpot v3 API for loading. The critical challenge was preserving the relationship between compliance interaction records (a Salesforce custom object) and their associated Contacts and Accounts. We solved this by migrating in strict dependency order and using the stored Salesforce IDs as lookup keys to rebuild associations in HubSpot.
The validation phase caught 1,247 Contact records where phone number formatting had been mangled — Salesforce stored them with country codes (e.g., +65 9123 4567) while our cleaning script had inadvertently stripped the + prefix. We caught this through field-level spot checks on the third day of validation. Without that step, the sales team would have discovered the issue mid-call — a bad first impression for the new CRM.
Total downtime for the sales team was two days (over a weekend), during which both systems were frozen to prevent divergent data.
Ready to Transform Your Ecommerce Operations?
Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.
What Should You Do After the Migration?
Run Parallel Operations for Two Weeks
Keep Salesforce accessible (read-only) for at least 14 days post-migration. Sales reps will need to reference historical context that they remember being "somewhere in Salesforce" but can't immediately find in HubSpot.
Retrain Your Team on HubSpot Conventions
Salesforce users will instinctively search for "Accounts" — in HubSpot, they're "Companies." "Opportunities" are "Deals." These aren't just label differences; the underlying workflows and automation logic differ. Budget time for training.
Monitor Data Quality Weekly
Use HubSpot's data quality tools (under Settings → Properties → Data Quality) to flag incomplete records, formatting inconsistencies, and potential duplicates that may have slipped through. Schedule a weekly review for the first month.
Gartner's 2023 research estimated that poor data quality costs organizations an average of $12.9 million per year. A migration is your one opportunity to start clean — invest the time in post-migration hygiene.
If your team is planning a Salesforce-to-HubSpot migration across APAC markets and needs hands-on technical execution — from data mapping through API scripting and validation — reach out to Branch8. We've done this across Hong Kong, Singapore, and Australia, and we know where the edge cases hide.
Sources
- HubSpot ROI Report (2024): https://www.hubspot.com/roi
- Validity State of CRM Data Management (2023): https://www.validity.com/resources/state-of-crm-data-management/
- HubSpot Developer Documentation — Engagements API: https://developers.hubspot.com/docs/api/crm/engagements
- Salesforce Data Loader Documentation: https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/
- HubSpot Product Limits and Usage Guidelines: https://legal.hubspot.com/product-specific-terms
- Gartner Data Quality Market Survey (2023): https://www.gartner.com/en/newsroom/press-releases/2023-data-quality
- simple-salesforce Python Library: https://github.com/simple-salesforce/simple-salesforce
FAQ
For a mid-market company with 100,000 to 500,000 records, expect four to eight weeks including planning, data cleaning, migration, and validation. The timeline extends if you have custom objects, complex automations, or multi-currency setups that require architectural decisions in HubSpot.