---
title: "Zoho CRM to Salesforce Migration: The Complete Technical Guide"
slug: zoho-crm-to-salesforce-migration-the-complete-technical-guide
date: 2026-07-17
author: Rishabh
categories: [Migration Guide, Zoho CRM, Salesforce]
excerpt: "The technical guide to migrating from Zoho CRM to Salesforce. Covers object mapping, API limits, load order, polymorphic field handling, and validation."
tldr: "A Zoho CRM to Salesforce migration requires strict load ordering, ID translation tables, and careful WhoId/WhatId mapping for polymorphic activities. Plan 2 to 14 weeks depending on complexity."
canonical: https://clonepartner.com/blog/zoho-crm-to-salesforce-migration-the-complete-technical-guide/
---

# Zoho CRM to Salesforce Migration: The Complete Technical Guide


# Zoho CRM to Salesforce Migration: The Complete Technical Guide

> [!NOTE]
> **Quick Answer: Zoho CRM to Salesforce Migration**
>
> A Zoho CRM to Salesforce migration is a data-model translation project, not a CSV lift-and-shift. Timelines range from 2 to 6 weeks for a standard sales org (under 500,000 records) to 6 to 14 weeks for enterprise orgs with custom modules, multi-currency, and heavy activity history. The single biggest risk is broken relationships: Zoho Deals linked to Contacts, Accounts, and Activities must be re-linked by Salesforce ID after insert, and any misstep orphans pipeline data. Zoho Blueprints, workflow rules, and custom functions cannot be migrated programmatically—they must be rebuilt in Salesforce Flows or Apex. Teams with fewer than 100,000 total records and no custom modules can handle this in-house. Teams with polymorphic activity histories, multi-currency Deals, or more than 500,000 records should use an API-based approach or a managed migration service.

## What Is a Zoho CRM to Salesforce Migration?

A Zoho CRM to Salesforce migration is the process of extracting all CRM data from Zoho CRM—Leads, Contacts, Accounts, Deals, Activities, Notes, Products, Email History, Custom Modules—and loading it into the corresponding Salesforce objects while preserving every relationship, field value, and activity history. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/modules-api.html))

Enterprise sales teams move from Zoho CRM to Salesforce for specific, platform-driven reasons:

- **Reporting depth.** Salesforce joined reports, cross-object formulas, and native BI (CRM Analytics) support revenue attribution models that Zoho Analytics cannot replicate natively.
- **AppExchange ecosystem.** Salesforce has 7,000+ managed packages. Teams outgrow Zoho's integration ceiling once they need CPQ, advanced territory management, or enterprise ERP connectors.
- **Multi-cloud consolidation.** Salesforce lets sales, service, and marketing teams operate from a single org with shared Account and Contact records. Zoho splits CRM, Desk, and Campaigns into separate apps with sync-based integration.
- **Governance and compliance.** Salesforce Shield (available on Enterprise Edition and above), Event Monitoring, and field-level encryption satisfy SOC 2, HIPAA, and FedRAMP requirements that Zoho CRM cannot match at the platform level.

For a deeper architectural comparison, see [Salesforce vs Zoho CRM: Technical Architecture & Operations Guide](https://clonepartner.com/blog/blog/salesforce-vs-zoho-crm-technical-architecture-operations-guide/).

Four platform differences make this migration non-trivial:

1. **Object naming divergence.** Zoho calls pipeline records "Deals" (historically "Potentials"). Salesforce calls them "Opportunities." Field API names differ entirely. Zoho supports extra deal contacts through custom lookup fields; Salesforce uses a dedicated `OpportunityContactRole` junction object. ([help.zoho.com](https://help.zoho.com/portal/en/kb/crm/sales-force-automation/deal-management/articles/deals-overview))
2. **Activity polymorphism.** Zoho Tasks and Events store a single related-record link. Salesforce Tasks and Events use two separate polymorphic fields: `WhoId` (Contact or Lead) and `WhatId` (Account, Opportunity, Case, or custom object). Every migrated activity needs both fields resolved correctly. ([developer.salesforce.com](https://developer.salesforce.com/docs/platform/data-models/guide/tasks-events.html))
3. **Relationship enforcement.** Salesforce enforces referential integrity through Master-Detail and Lookup relationships with validation rules. Zoho's relationships are looser. Data that passes Zoho validation may fail Salesforce validation rules on insert, producing errors like `FIELD_INTEGRITY_EXCEPTION` or `INVALID_CROSS_REFERENCE_KEY`.
4. **Data sharing model divergence.** Zoho uses a simpler role-based sharing model. Salesforce uses Organization-Wide Defaults (OWD) + sharing rules + role hierarchy. Your Zoho sharing configuration must be translated into Salesforce's multi-layered sharing model before users can access migrated records correctly.

## Zoho CRM to Salesforce Object Mapping

The object mapping table below covers every standard Zoho CRM module and its Salesforce equivalent. Lead with this table when planning your migration.

| Zoho CRM Module | Salesforce Object | Notes / Caveats |
|---|---|---|
| Leads | Lead | Direct 1:1 mapping. Lead Source picklist values must match Salesforce entries exactly or be pre-created. |
| Accounts | Account | Zoho Account Owner maps to Salesforce `OwnerId`. Owner must exist as an active Salesforce User. Use Record Types to distinguish Account subtypes (e.g., Customer vs. Partner). |
| Contacts | Contact | Must reference a valid Account via `AccountId`. Zoho allows orphan Contacts; Salesforce does not if Account is required by your org configuration. |
| Deals (Potentials) | Opportunity | Stage names must match Salesforce Opportunity Stage picklist. Close Date is required in Salesforce but optional in Zoho. Use Record Types for different deal types (e.g., New Business vs. Renewal). |
| Products | Product2 | Salesforce requires a Pricebook Entry to associate Products with Opportunities. Zoho has no equivalent concept. |
| Price Books | Pricebook2 + PricebookEntry | Products must exist in the standard price book before custom price books. |
| Quotes | Quote + QuoteLineItem | Salesforce Quotes are children of Opportunities. The Opportunity must exist before the Quote is inserted. ([help.zoho.com](https://help.zoho.com/portal/en/kb/crm/manage-inventory/quotes/articles/quotes)) |
| Tasks | Task | Zoho's single related-record link must be split into `WhoId` (Contact/Lead) and `WhatId` (Account/Opportunity). |
| Calls | Task (Type = 'Call') | Salesforce has no native Call object. Calls are logged as Tasks with a Type picklist value of "Call." |
| Events (Meetings) | Event | Zoho UI may say "Meetings," but the API name remains `Events`. Recurring events need special logic. |
| Emails | EmailMessage | Zoho stores email threads linked to records. Salesforce uses `EmailMessage` (child of Case) or `Task` with Type='Email'. For non-Case email history, Einstein Activity Capture can sync going forward but does not import historical emails. Manual migration to Tasks with Type='Email' or a custom `Email_Log__c` object is required for historical records. |
| Notes | ContentNote or Note | Salesforce Enhanced Notes (`ContentNote`) require a `ContentDocumentLink` for association. Legacy `Note` is simpler but deprecated. |
| Attachments | ContentVersion + ContentDocumentLink | Modern Salesforce file storage is a two-step upload. Each file requires a separate API call for binary content. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/get-notes.html)) |
| Campaigns | Campaign | Campaign Member associations must be rebuilt using `CampaignMember` junction records. |
| Sales Orders | Order | Salesforce Orders require an activated Contract or direct Account reference. |
| Vendors | Custom Object | Salesforce has no standard Vendor object. Build a custom object or flatten vendor data into Account with a Record Type of "Vendor." |
| Territories | Territory2 (Territory Management 2.0) | Zoho territory rules must be manually rebuilt. Salesforce Territory Management 2.0 (available in Enterprise Edition+) uses assignment rules, not the same logic as Zoho territory hierarchies. |
| Custom Modules | Custom Object (`__c`) | Each custom module becomes a custom object. All field API names change. Every relationship field needs re-mapping. |

The rows that trip teams up most often are Events (because Zoho keeps `Events` as the API name even when the UI says "Meetings"), Emails (because there is no clean one-to-one migration path for historical email threads), and Notes/Attachments (because Salesforce stores modern files in content objects rather than a single legacy attachment model). ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/modules-api.html))

### Field-Level Mapping

**Picklist handling.** Zoho allows free-text entry in picklist fields by default. Salesforce restricts picklist values to predefined entries unless the field is set to unrestricted. Migrate all picklist values to Salesforce first, then load records. If a value does not exist in the target picklist, the Salesforce API will reject the record with `INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST`.

**Record Types.** Salesforce Record Types control page layouts, picklist value availability, and business process assignment per object. If your Zoho CRM uses different Deal types or Account categories, map these to Salesforce Record Types during schema design rather than using custom picklist fields. Record Types must exist before data load because they affect which picklist values are valid on insert.

**Multi-currency.** If your Zoho CRM org uses multi-currency, enable it in Salesforce before loading any data. Salesforce stores currency values with a per-record `CurrencyIsoCode` field. Zoho stores the currency code at the org level unless per-record currency is enabled. Every Deal record needs the correct `CurrencyIsoCode` on insert. Salesforce advanced currency management can preserve dated conversion rates for Opportunities, but only if configured before you load amounts. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=sf.admin_currency.htm&language=en_US&type=5))

**Owner matching.** Every Zoho record has an Owner. Salesforce requires `OwnerId` to reference an active User or Queue. Build a user-mapping table (Zoho User ID → Salesforce User ID) before loading any records. If a Zoho user does not have a Salesforce license, assign their records to a fallback user or queue.

**Pipeline stage mapping.** Zoho Deal stages are freeform text or picklist values. Salesforce Opportunity stages must align with a configured Sales Process. Each stage maps to a probability percentage and a forecast category. Verify that your Zoho stage strings exist exactly in the target Salesforce Sales Process before loading.

**Field metadata extraction.** Use `GET /settings/fields?module=...` in the Zoho API to extract stable `api_name` values and data types. Labels can change; API names do not. Pre-create Salesforce fields with the correct type, length, picklist values, and required status before loading data. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/field-meta.html))

### What Has No Clean Salesforce Equivalent

- **Zoho Blueprints** — Rebuild as Salesforce Flows (Screen Flows or Record-Triggered Flows).
- **Workflow Rules and Custom Functions** — Rebuild in Flow, Apex, or both.
- **Subforms** (inline child records within a Zoho module) — Map to related lists on a child custom object.
- **Canvas Views** — Rebuild using Lightning App Builder.
- **Email templates** — Zoho uses its own merge-tag syntax (`${Contacts.Last Name}`). Salesforce uses Handlebars-style merge fields (`{{{Contact.LastName}}}`). Templates must be recreated.
- **Zoho Webforms** — Replace with Salesforce Web-to-Lead or Web-to-Case forms.
- **Zoho SalesSignals notifications** — No direct equivalent. Use Einstein Activity Capture or custom Platform Event notifications.
- **Field-level audit history** — Not exportable via API in most Zoho editions. Salesforce Field History Tracking starts fresh from go-live.
- **Approval histories** — Approval stamps (who approved what, when) are not exportable as structured data. Migrate summaries as Notes if needed.
- **Reports and dashboards** — Cannot be exported or imported. The underlying data migrates, but report definitions, chart configurations, and dashboard layouts are platform-specific.
- **Sharing rules and data access configuration** — Zoho's sharing model must be manually translated to Salesforce's OWD settings, sharing rules, and role hierarchy. This is a configuration task, not a data migration task, but skipping it means users cannot access their records post-migration.

## Salesforce Edition Requirements for Migration Features

Not all Salesforce editions support the features required for a smooth migration. Verify your edition before starting.

| Feature | Required Salesforce Edition |
|---|---|
| Bulk API 2.0 | Enterprise, Unlimited, Performance, Developer |
| Set Audit Fields (backdate CreatedDate) | Any edition (requires Salesforce Support to enable the permission) |
| Territory Management 2.0 | Enterprise, Unlimited, Performance |
| Salesforce Shield (Event Monitoring, Platform Encryption) | Add-on for Enterprise+; included in Unlimited+ |
| Custom Objects (>5) | Professional (up to 50), Enterprise (up to 200), Unlimited (up to 2,000) |
| Record Types | Enterprise, Unlimited, Performance |
| Data Loader | All editions |

Teams on Salesforce Essentials or Professional may hit limits on API access, custom objects, and Record Types that require edition upgrades before migration can begin.

## API Limits That Govern Migration Throughput

API rate limits are the primary throughput constraint in any CRM migration. Understanding the exact limits on both platforms determines how long the data transfer takes.

### Zoho CRM API Limits

Zoho CRM uses a **credit-based** system with a rolling 24-hour window. Each API call consumes one or more credits depending on the operation. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/api-limits.html))

| Zoho CRM Edition | Base Credits | Max Credits (24h) | Bulk Read API Access |
|---|---|---|---|
| Free | 5,000 | 5,000 | No |
| Standard/Starter | 50,000 + (Users × 250) | 100,000 | No |
| Professional | 50,000 + (Users × 500) | 3,000,000 | Yes |
| Enterprise/Zoho One | 50,000 + (Users × 1,000) | 5,000,000 | Yes |
| Ultimate/CRM Plus | 50,000 + (Users × 2,000) | Unlimited | Yes |

Key extraction constraints:

- **Get Records API** returns a maximum of 200 records per request.
- **COQL (CRM Object Query Language) API** returns up to 2,000 records per call with pagination up to 100,000 records per unique query.
- **Bulk Read API** exports up to 200,000 records per job as a CSV file. This is the correct extraction path for large objects—it avoids per-record credit drain and can export an entire module in a single async job. **Note:** Bulk Read is only available on Professional edition and above. Teams on Standard/Starter must use paginated Get Records calls, which significantly increases extraction time and credit consumption. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/bulk-read/create-job.html))
- **Concurrency limits** cap simultaneous API calls at 20 per org for Enterprise, 25 for Ultimate. Sub-concurrency for heavier operations (COQL, Composite, large inserts) is capped at 10 across all editions.

### Salesforce Bulk API 2.0 Limits

Salesforce Bulk API 2.0 is the correct tool for loading migration data. Unlike Bulk API 1.0 (where you manually create and manage batches), Bulk API 2.0 auto-chunks uploaded CSV data into internal batches of up to 10,000 records each. The relevant limits: ([resources.docs.salesforce.com](https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/salesforce_app_limits_cheatsheet.pdf))

| Limit | Value |
|---|---|
| Bulk API 2.0 ingest jobs per 24-hour period | 15,000 |
| Records per internal batch (auto-chunked) | 10,000 |
| Daily record processing limit | 100,000,000 records |
| Concurrent ingest jobs | 25 |
| Job payload size | 150 MB per job (recommend ≤100 MB due to base64 expansion) |
| Batch processing timeout | 10 minutes per batch, up to 10 retries |

Salesforce Data Import Wizard is limited to 50,000 records per import session and only supports Accounts, Contacts, Leads, Solutions, Campaign Members, and Custom Objects. It does not support Tasks, Events, Opportunities, or Products. For migration volumes, use Bulk API 2.0 via Data Loader, a custom script, or a managed service. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=sf.data_import_wizard.htm&language=en_US&type=5))

### Throughput Expectations

With Bulk API 2.0 processing 10,000 records per internal batch and each batch completing in roughly 1 to 3 minutes under normal load, expect approximately **200,000 to 400,000 records per hour** for simple objects (Leads, Contacts). Objects with active validation rules, Apex triggers, or process automations run slower—plan for **100,000 to 150,000 records per hour** in orgs with active triggers.

Platform limits are high enough that a few hundred thousand core records can load in hours when automation is controlled. The real schedule drag comes from failed lookups, picklist mismatches, duplicate rules, and dependency chains around Products, Quotes, Notes, and Files.

## Migration Approaches: CSV, API, or Managed Service

The right approach depends on record volume, relationship complexity, and your team's technical bandwidth.

| Approach | How It Works | Best For | Max Volume | Key Risk |
|---|---|---|---|---|
| CSV Export + Data Import Wizard | Export each Zoho module as CSV. Import via Salesforce Data Import Wizard. | Small orgs, <50K records, standard modules only | 50,000 per session | No support for Opportunities, Tasks, Events, Products. Manual and error-prone for 5+ objects. |
| CSV Export + Data Loader | Export from Zoho, transform in scripts or spreadsheets, load via Salesforce Data Loader (Bulk API). | Mid-size orgs, 50K–1M records, dedicated admin | 5,000,000 per load | Requires manual ID remapping. No built-in error recovery beyond CSV logs. |
| API-to-API (Custom Script) | Extract via Zoho CRM Bulk Read API, transform in code, load via Salesforce Bulk API 2.0. | Enterprise orgs, >1M records, complex relationships | Unlimited (API-governed) | Requires engineering time. Must handle rate limits, retries, and ID translation tables. |
| iPaaS / No-Code Tool | Visual mapping and prebuilt connectors (e.g., Skyvia). | Standard schemas, moderate volume, ongoing sync | Moderate | Weak handling of files, polymorphic activities, quote dependencies, and `OpportunityContactRole`. ([skyvia.com](https://skyvia.com/data-integration/integrate-salesforce-zoho-crm)) |
| Managed Migration Service | A dedicated engineering team handles extraction, transformation, loading, and validation end to end. | Any size, zero internal bandwidth, zero tolerance for downtime | Unlimited | Cost (typically $5K–$50K+ depending on complexity). Vet the provider's CRM migration track record. |

**For small business** (under 50,000 records, standard modules only): CSV export and Data Import Wizard works. Budget 3 to 5 days of manual work.

**For mid-market** (50,000–500,000 records): Data Loader with Bulk API. Budget 2 to 4 weeks including mapping and validation.

**For enterprise** (over 500,000 records, custom modules, polymorphic activities): API-to-API with a custom script or a managed service. Budget 4 to 14 weeks depending on complexity.

Salesforce Data Loader supports all objects, handles external ID upserts and relationship matching, and produces separate success and error CSV logs for replay and QA. That makes it the practical workhorse for admins managing mid-size migrations. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=data_loader.htm&language=en_US&type=5))

No-code tools like Skyvia offer visual editors and synchronization between Salesforce and Zoho CRM, which speeds up standard mappings. The catch: visual mapping does not remove Salesforce object dependencies like `OpportunityContactRole`, `PricebookEntry`, or `ContentDocumentLink`. For one-time migrations with complex parent-child hierarchies, these tools often require manual intervention to resolve broken lookups.

## Load Order and Zoho-to-Salesforce ID Translation

The load order is the single most important technical decision in this migration. Loading objects in the wrong sequence creates orphaned records and broken lookups.

### Required Load Sequence

1. **Users** — Create or map all Salesforce Users before loading any records.
2. **Accounts** — No dependencies. Foundational parent object. If using Record Types, create them first.
3. **Contacts** — Requires `AccountId` from step 2.
4. **Leads** — No dependencies, but must not overlap with Contacts.
5. **Products and Pricebook Entries** — Products first, then Pricebook Entries linking Products to a Pricebook.
6. **Opportunities (Deals)** — Requires `AccountId`. Optional `ContactId` via `OpportunityContactRole`.
7. **OpportunityContactRoles** — Requires both `OpportunityId` and `ContactId` from prior steps.
8. **Quotes and Quote Line Items** — Requires `OpportunityId` from step 6.
9. **Tasks and Events** — Requires `WhoId` and `WhatId` from earlier steps.
10. **EmailMessages (as Tasks)** — If migrating email history, load after Contacts and Opportunities.
11. **Notes and Attachments** — Requires parent record IDs.
12. **Custom Objects** — Order depends on lookup dependencies between them.
13. **Campaign Members** — Requires both `CampaignId` and `LeadId` or `ContactId`.

> [!WARNING]
> **Never load Tasks or Events before Contacts, Accounts, and Opportunities.** Salesforce will reject any Task or Event where the `WhoId` or `WhatId` references a record that does not yet exist. The resulting error is `INVALID_CROSS_REFERENCE_KEY` with no indication of which specific parent record is missing—you must debug from your ID translation table.

### Handling the ID Translation

Every record in Zoho has a unique Zoho ID (e.g., `4150868000000322019`). Salesforce assigns its own 18-character ID on insert. To preserve relationships, you need an **ID translation table** that maps every Zoho record ID to its corresponding Salesforce ID.

The workflow:

1. Export Zoho records with their Zoho IDs and all lookup field IDs.
2. Load parent records (Accounts) into Salesforce. Capture the returned Salesforce IDs.
3. Before loading child records (Contacts), replace every Zoho Account ID in the Contact data with the corresponding Salesforce Account ID from your translation table.
4. Repeat for every dependent object.

```python
# ID translation for Contact load
id_map = {}  # zoho_id -> salesforce_id

# After Account insert, populate id_map from Data Loader success CSV
for result in salesforce_bulk_results:
    id_map[result.external_id] = result.salesforce_id

# Before Contact insert, remap AccountId
for contact in zoho_contacts:
    zoho_account_id = contact["Account_Name"]["id"]
    contact["AccountId"] = id_map.get(zoho_account_id)
    if not contact["AccountId"]:
        log_error(f"Orphan contact: {contact['id']}, missing Account {zoho_account_id}")
```

The API request shape for extraction and loading looks like this:

```text
# Zoho Bulk Read extraction
POST /crm/bulk/v8/read
query.module.api_name = Deals
query.fields = id, Deal_Name, Stage, Amount, Closing_Date, Owner, Account_Name, Contact_Name
```

```text
# Salesforce Bulk API 2.0 ingest
POST /services/data/v67.0/jobs/ingest
object = Opportunity
operation = upsert
externalIdFieldName = Legacy_Zoho_Id__c
contentType = CSV
```

These are enough to build a reproducible extract-and-load loop, but they still need crosswalk tables for IDs. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/bulk-read/create-job.html))

> [!TIP]
> **Use Salesforce External ID fields.** Create a custom text field (e.g., `Zoho_ID__c`) on each Salesforce object and mark it as an External ID. This lets you use `upsert` operations via Bulk API 2.0, which eliminates the need for a separate ID translation table for simple lookups. Salesforce resolves relationships using the External ID value automatically. This approach is especially valuable for Contacts referencing Accounts—set the relationship field to use `Account.Zoho_ID__c` in your CSV header instead of the Salesforce `AccountId`.

## Handling Polymorphic Fields: Tasks, Events, and Files

Migrating Activities (Tasks, Events) is the hardest part of a Zoho CRM to Salesforce migration because of polymorphic relationship handling.

In Zoho CRM, a Task links to a single related record via the `What_Id` field, which can point to a Deal, Account, Lead, Contact, or custom module record. There is also a `Who_Id` field for the person involved.

In Salesforce, a Task has two polymorphic fields:

- **`WhoId`** references a Contact (ID prefix `003`) or a Lead (ID prefix `00Q`). It answers: "Who did you interact with?"
- **`WhatId`** references an Account (`001`), Opportunity (`006`), Case (`500`), Campaign (`701`), or custom object. It answers: "What business record is this about?"

**Critical constraint:** If `WhoId` points to a Lead, `WhatId` must be null. Salesforce does not allow `WhatId` when `WhoId` references a Lead. Attempting this produces `FIELD_INTEGRITY_EXCEPTION: WhatId is not allowed when WhoId refers to a Lead`. This means any Zoho Task linked to both a Lead and a Deal must have the Deal link dropped or the Lead converted to a Contact first.

If your Zoho Task is linked to both a Contact and a Deal, set `WhoId` to the migrated Salesforce Contact ID and `WhatId` to the migrated Salesforce Opportunity ID. If the Task is linked only to a Lead in Zoho, set `WhoId` to the Salesforce Lead ID and leave `WhatId` null.

```python
# WhoId / WhatId split for activity migration
if src.lead_id and not src.contact_id:
    row['WhoId'] = lead_id_map[src.lead_id]
    row['WhatId'] = None  # WhatId not allowed with Lead WhoId
elif src.contact_id:
    row['WhoId'] = contact_id_map[src.contact_id]
    if src.deal_id:
        row['WhatId'] = opportunity_id_map[src.deal_id]
    elif src.account_id:
        row['WhatId'] = account_id_map[src.account_id]
```

That `WhoId`/`WhatId` split is the difference between preserved history and orphaned records. ([developer.salesforce.com](https://developer.salesforce.com/docs/platform/data-models/guide/tasks-events.html))

### Email History Migration

Zoho CRM stores email threads linked to records. Salesforce handles email differently depending on context:

- **Case-related emails** use the `EmailMessage` object (child of Case).
- **Non-Case emails** have no standard import path. Einstein Activity Capture syncs Gmail/Outlook emails going forward but does not import historical email data from Zoho.
- **Practical migration path:** Export Zoho email records via API, then load them as Salesforce Tasks with `Type = 'Email'`, populating `Subject`, `Description` (email body), `ActivityDate`, `WhoId`, and `WhatId`. Store the original email metadata (sender, recipients, timestamps) in custom fields on the Task object (e.g., `Email_From__c`, `Email_To__c`, `Email_Date__c`).

This preserves email history on the Activity Timeline but loses threading, attachments (which must be migrated separately as ContentVersion records), and HTML formatting in the body field.

### Notes and Files

**Notes** in Zoho use a simple parent relationship. In Salesforce, the modern approach is `ContentNote` with a `ContentDocumentLink` record that ties the note to a parent Account, Contact, or Opportunity. The legacy `Note` object is simpler but deprecated for new implementations.

**File attachments** must be migrated to Salesforce Files (`ContentVersion` + `ContentDocumentLink`), not the legacy Attachment object. Each file requires a separate API call to upload the binary content. Notes and Files are separate migration workstreams—do not underestimate their scope. ([zoho.com](https://www.zoho.com/crm/developer/docs/api/v8/get-notes.html))

### Multi-Contact Deals

Zoho supports extra deal contacts through custom lookup fields (up to 10 lookups per module). Salesforce uses `OpportunityContactRole` for this purpose. Do not flatten multi-contact information into text fields on the Opportunity record—create proper `OpportunityContactRole` junction records so the data is reportable and visible on both the Opportunity and Contact layouts. Each `OpportunityContactRole` record requires an `OpportunityId`, `ContactId`, and optionally a `Role` picklist value (e.g., "Decision Maker," "Evaluator"). ([help.zoho.com](https://help.zoho.com/portal/en/kb/crm/faqs/sales-force-automation/deals-management/articles/faqs-deals))

## Integration Reconnection Checklist

Zoho CRM integrations do not carry over to Salesforce. Every third-party integration must be reconnected or replaced on the Salesforce side. Common integrations that require action:

| Zoho Integration | Salesforce Equivalent | Notes |
|---|---|---|
| Zoho Campaigns | Salesforce Marketing Cloud, Pardot, or third-party (Mailchimp via AppExchange) | Campaign Member sync logic changes completely. |
| Zoho Books / QuickBooks sync | QuickBooks connector via AppExchange (e.g., Breadwinner, DBSync) | Field mappings and sync direction must be reconfigured. |
| Zoho Desk | Salesforce Service Cloud | If migrating support data, Cases and Knowledge Articles require a separate migration workstream. |
| Zoho PhoneBridge (Twilio, RingCentral) | Salesforce Open CTI + same telephony provider | CTI adapter configuration is platform-specific. |
| Zoho Social | Salesforce Social Studio or third-party (Hootsuite, Sprout Social) | Social listening and publishing workflows must be rebuilt. |
| Custom Zoho CRM API integrations | Rebuild against Salesforce REST/SOAP API | Endpoint URLs, authentication (OAuth flow), and data schemas all change. |
| Zoho Flow / Zoho Creator automations | Salesforce Flow, MuleSoft, or third-party iPaaS (Zapier, Workato) | Automation logic must be rebuilt; no export/import path exists. |

Inventory every active Zoho CRM integration during the Discovery phase. For each, identify the Salesforce equivalent, estimate configuration time, and assign an owner. Integration reconnection frequently accounts for 20–40% of total migration effort in enterprise projects.

## Timeline, Phases, and Risk Register

A typical Zoho CRM to Salesforce migration takes **2 to 6 weeks** for a standard sales org and **6 to 14 weeks** for an enterprise org with custom modules and complex automation.

| Phase | Duration (Standard) | Duration (Enterprise) | Key Activities |
|---|---|---|---|
| Discovery and audit | 2–3 days | 1–2 weeks | Inventory all Zoho modules, fields, automations, integrations. Define scope. Verify Salesforce edition supports required features. |
| Schema design and field mapping | 2–3 days | 1–2 weeks | Build mapping document, create custom fields, objects, and Record Types in Salesforce. Set up External ID fields. Configure sharing rules and role hierarchy. |
| Data extraction and cleanup | 1–2 days | 1–2 weeks | Export all Zoho data via Bulk Read, deduplicate, normalize picklists, validate formats. |
| Test migration (sandbox) | 2–3 days | 1–2 weeks | Load into Salesforce sandbox (use Full Sandbox for volume testing, Developer Sandbox for schema validation). Validate record counts and spot-check relationships. |
| Automation rebuild | 1–3 days | 2–4 weeks | Rebuild Blueprints, workflow rules, and custom functions as Salesforce Flows/Apex. |
| Integration reconnection | 1–2 days | 1–3 weeks | Reconnect third-party integrations to Salesforce. Test data sync bidirectionally. |
| Production migration | 1–2 days | 2–5 days | Load all data. Resolve errors. Verify. |
| Delta sync and go-live | 1–2 days | 3–5 days | Final delta extraction, UAT, cutover, decommission Zoho write access. |

### Sandbox Strategy

Salesforce offers four sandbox types. Choose based on your testing needs:

| Sandbox Type | Data Included | Refresh Interval | Use in Migration |
|---|---|---|---|
| Developer | Metadata only, no data | 1 day | Schema validation, field mapping testing |
| Developer Pro | Metadata only, no data | 1 day | Schema validation with more storage |
| Partial Copy | Metadata + sample data (up to 5 GB) | 5 days | Test migration with representative data subset |
| Full Copy | Complete metadata + all production data | 29 days | Full volume load testing, performance benchmarking |

For migrations with over 500,000 records, test in a Full Sandbox to validate throughput, trigger performance, and storage consumption before production load.

### Phased Cutover vs Big Bang

A big-bang cutover (moving everything over a single weekend) is risky for active sales teams. The safer approach: migrate historical closed-won deals and old activities first, then run a final delta sync over a weekend to catch records created or modified during the migration window.

Keep Zoho CRM in read-write mode during the bulk migration. After UAT passes, cut over all users to Salesforce and set Zoho CRM to read-only. Most teams keep Zoho accessible in read-only mode for 30 to 90 days as a reference.

For the operating logic behind running systems in parallel, see [Why Running Two CRMs in Parallel Beats a Hard Cutover](https://clonepartner.com/blog/blog/why-running-two-crms-in-parallel-beats-a-hard-cutover/).

### What Reps Notice During Cutover

Reps notice open pipeline, past activity history, and quote or file availability. If Tasks and Events lose their `WhoId` or `WhatId`, the records may exist but will not surface on the expected Account, Contact, or Opportunity Activity Timeline. If Notes or Files are skipped, the Account looks empty even when core records migrated correctly.

Salesforce operates differently than Zoho CRM. Budget time for user training on the Lightning interface, Global Search, and how Opportunities relate to Accounts in the new schema. Correct data is necessary but not sufficient—reps need to know where to find it.

### Risk Register

| Risk | Likelihood | Common Error Message | Mitigation |
|---|---|---|---|
| Broken parent-child relationships | High | `INVALID_CROSS_REFERENCE_KEY` | Use External IDs and enforce strict load order. Validate with SOQL counts after each object load. |
| Salesforce validation rule failures | Medium | `FIELD_CUSTOM_VALIDATION_EXCEPTION` | Disable non-critical validation rules and Apex triggers during migration. Re-enable post-load. |
| API rate limit exhaustion (Zoho side) | Medium | `INVALID_REQUEST_METHOD` (HTTP 429) | Use Bulk Read API instead of per-record GET calls. Monitor credit consumption via API dashboard. |
| Duplicate records | Medium | `DUPLICATES_DETECTED` | Run deduplication before extraction. Enable Salesforce duplicate rules in "Allow with alert" mode during load. |
| Activity history loss | High | `REQUIRED_FIELD_MISSING` (WhoId/WhatId) | Migrate all Tasks and Events with both `WhoId` and `WhatId` mapped. Verify with Activity Timeline in Salesforce. |
| Picklist value rejection | High | `INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST` | Pre-create all picklist values in Salesforce before data load. Export Zoho picklist values via field metadata API. |
| Quote/price book failures | Medium | `FIELD_INTEGRITY_EXCEPTION` | Load Products → Pricebooks → PricebookEntries → Opportunities → Quotes → QuoteLineItems in strict order. ([help.salesforce.com](https://help.salesforce.com/s/articleView?id=Associate-Products-to-Standard-and-Custom-Pricebooks-using-Data-Loader&language=en_US&type=1)) |
| Required field missing | High | `REQUIRED_FIELD_MISSING` | Compare required fields in Salesforce schema against Zoho optional fields. Populate defaults for blanks. |
| Storage overage (files) | Medium | N/A (post-load billing) | Calculate file storage before migration. Archive historical files to S3/Azure, store URL references in Salesforce. |

### Rollback Plan

Before production migration, take a full Salesforce metadata backup using the Metadata API and a data backup of all existing records. If the migration fails validation, delete migrated records by object in reverse load order (Notes → Tasks → Opportunities → Contacts → Accounts). Salesforce does not support transactional rollback across Bulk API jobs—each object must be deleted individually using Bulk API 2.0 with `operation = delete`.

## Edge Cases That Break Migrations

### File Attachments and Storage Costs

Zoho CRM includes 1 GB of free file storage per org plus 512 MB per user on Enterprise plans. Salesforce provides 10 GB of base file storage plus 2 GB per user on Enterprise Edition, but charges approximately **$5/month per additional GB** for overages (pricing varies by contract). If your Zoho instance has hundreds of gigabytes of PDF contracts attached to Deals, migrating all files directly into Salesforce will trigger significant storage overage fees.

Best practice: calculate total file storage in Zoho before migration. Migrate active files (last 12–24 months) to Salesforce and archive historical files to a cheaper storage solution (AWS S3 at ~$0.023/GB/month, Azure Blob at ~$0.018/GB/month), leaving a URL reference in a custom field on the Salesforce record.

### Preserving Original Timestamps

Salesforce does not allow backdating the `CreatedDate` field via standard APIs. To preserve original creation timestamps from Zoho, you have two options:

1. Store the original Zoho created date in a custom field (e.g., `Original_Created_Date__c`). This is the simpler approach and works on all editions.
2. Use the Salesforce **"Set Audit Fields"** permission (available on request from Salesforce Support for any edition) to set `CreatedDate` and `CreatedById` during insert. This requires contacting Salesforce Support to enable the "Create Audit Fields" user permission, then assigning it to the integration user performing the migration.

Without this, all migrated records show the migration date as their creation date, which confuses sales reps reviewing historical interactions and breaks any reports filtered by creation date.

### Quotes and Price Book Dependencies

Zoho quotes include quoted items and price book behavior inside the CRM workflow. In Salesforce, Products, Price Books, Price Book Entries, Opportunity Products, Quotes, and Quote Line Items each have their own object and load order. The complete dependency chain:

1. `Product2` — Create the product catalog.
2. `Pricebook2` — Create any custom price books (Standard Price Book exists by default).
3. `PricebookEntry` — Link each Product to the Standard Price Book first, then to any custom price books. Error if reversed: `FIELD_INTEGRITY_EXCEPTION: Standard Price not defined`.
4. `Opportunity` — Set the `Pricebook2Id` on each Opportunity.
5. `OpportunityLineItem` — Link Products to Opportunities. Requires valid `PricebookEntryId`.
6. `Quote` — Child of Opportunity. Requires `OpportunityId`.
7. `QuoteLineItem` — Child of Quote. Requires `QuoteId` and `PricebookEntryId`.

Changing the price book on an Opportunity that already has products attached deletes all existing `OpportunityLineItem` records. Plan this dependency chain early—quote and price book errors show up late because those loads sit behind product and opportunity dependencies. ([help.zoho.com](https://help.zoho.com/portal/en/kb/crm/manage-inventory/quotes/articles/create-quotes))

### Sharing and Data Access Post-Migration

If you skip configuring Salesforce's sharing model before go-live, users may see "Insufficient Privileges" errors when accessing migrated records. Before production load:

1. Set Organization-Wide Defaults (OWD) per object (Private, Public Read Only, or Public Read/Write).
2. Build the role hierarchy to match your Zoho CRM data visibility structure.
3. Create sharing rules for any cross-team data access requirements.
4. Verify that record ownership (`OwnerId`) aligns with the role hierarchy so that managers can see their team's records.

## How to Validate the Migration Before Go-Live

Validation is not optional. Run these checks before declaring go-live.

**Record-count reconciliation.** Compare the total record count per object in Zoho against Salesforce using `SELECT COUNT() FROM [Object]`. Tolerance should be zero for core objects (Accounts, Contacts, Opportunities). Any discrepancy means records were lost or skipped during load.

```sql
-- Record count validation queries
SELECT COUNT() FROM Account               -- Compare against Zoho Accounts export row count
SELECT COUNT() FROM Contact               -- Compare against Zoho Contacts export row count
SELECT COUNT() FROM Opportunity            -- Compare against Zoho Deals export row count
SELECT COUNT() FROM Task                   -- Compare against Zoho Tasks + Calls export row count
SELECT COUNT() FROM Event                  -- Compare against Zoho Events export row count
```

**Field-level spot checks.** Sample 1% to 5% of records per object (minimum 50 records for objects with over 1,000 records). Open each record in Salesforce and compare field values against the Zoho source. Check dates, currency amounts, picklist values, and multi-line text fields specifically.

**Relationship integrity.** Run SOQL queries to find orphaned records:

```sql
-- Find Contacts with no Account
SELECT Id, Name FROM Contact WHERE AccountId = null

-- Find Opportunities with no Account
SELECT Id, Name FROM Opportunity WHERE AccountId = null

-- Find Tasks with no WhoId and no WhatId (orphaned activities)
SELECT Id, Subject FROM Task WHERE WhoId = null AND WhatId = null

-- Find OpportunityContactRoles referencing non-existent Contacts
SELECT Id, OpportunityId, ContactId FROM OpportunityContactRole
WHERE ContactId NOT IN (SELECT Id FROM Contact)

-- Verify all Zoho IDs mapped successfully (no blanks in External ID)
SELECT Id, Name FROM Account WHERE Zoho_ID__c = null
```

**Financial report comparison.** Compare key pipeline reports—especially open opportunity amount by stage and currency—between Zoho and Salesforce. Currency mismatches or missing stage mappings surface here. Run a cross-tabulation: total Opportunity Amount grouped by `StageName` and `CurrencyIsoCode` in Salesforce versus the equivalent Zoho report.

**User acceptance testing.** Have 3 to 5 sales reps open their own Accounts, check their pipeline, and verify that activity history appears correctly on the Activity Timeline. This is the single best test: if reps trust the data, the migration succeeded. Provide each test rep a checklist: verify 5 Accounts, 5 open Opportunities, and 10 recent Activities.

**Error log replay.** Re-run failed rows from Data Loader error logs until remaining exceptions are understood, not ignored. Data Loader's separate success and error CSV logs make this straightforward. Common errors to expect and resolve:

| Error | Cause | Fix |
|---|---|---|
| `INVALID_CROSS_REFERENCE_KEY` | Parent record not yet loaded or Zoho ID not in translation table | Verify parent load completed; check ID translation table for missing entries |
| `REQUIRED_FIELD_MISSING` | Salesforce requires a field that was optional or empty in Zoho | Populate a default value or make the Salesforce field non-required |
| `INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST` | Zoho picklist value does not exist in Salesforce | Add the value to the Salesforce picklist or map to an existing value |
| `DUPLICATES_DETECTED` | Salesforce duplicate rule blocked the insert | Set duplicate rules to "Allow" during migration, re-enable after |
| `STRING_TOO_LONG` | Zoho text field had more characters than the Salesforce field's max length | Increase Salesforce field length or truncate data |

([resources.docs.salesforce.com](https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/salesforce_data_loader.pdf))

For a complete QA framework, see [The Ultimate CRM Data Migration Checklist: A 10-Point Plan for a Zero-Loss Transition](https://clonepartner.com/blog/blog/the-ultimate-crm-data-migration-checklist-a-10-point-plan-for-a-zero-loss-transition/).

## Build In-House or Use a Managed Service

**Build in-house when:**

- Your org has fewer than 100,000 records across all objects.
- You use only standard Zoho modules (no custom modules, no multi-currency).
- You have a developer comfortable with both the Zoho CRM API and Salesforce Bulk API 2.0.
- Your timeline is flexible enough to absorb a failed test migration and re-run.
- You have no more than 5 active third-party integrations to reconnect.

**Use a managed service when:**

- You have more than 500,000 records or complex custom modules.
- Activity history with polymorphic relationships is business-critical.
- Your sales team cannot tolerate downtime or pipeline disruption.
- You lack an internal engineer who has done this specific migration before.
- You have significant file storage (>50 GB) that needs selective migration and archival.
- Email history preservation is a requirement.

The hidden cost of DIY is re-migration. In-house CRM migrations frequently require at least one full re-run due to broken relationships, missed objects, or validation failures. Each re-run costs 1 to 3 weeks of engineering time. The rework is not export time—it is discovering broken relationships, wrong owners, quote failures, or missing context in front of quota-carrying reps.

### Cost Estimation Framework

While costs vary significantly by org complexity, these rough ranges help with budgeting:

| Cost Category | Small (<50K records) | Mid-Market (50K–500K) | Enterprise (>500K) |
|---|---|---|---|
| Internal labor (admin/developer time) | 40–80 hours | 120–320 hours | 400–1,000+ hours |
| Managed migration service | $3K–$8K | $8K–$25K | $25K–$75K+ |
| Salesforce storage overages (if applicable) | Minimal | $100–$500/month | $500–$5,000+/month |
| User training (per rep) | 2–4 hours | 4–8 hours | 8–16 hours |
| Salesforce license differential | Varies by edition | Varies by edition | Varies by edition |

These ranges assume a one-time migration, not ongoing bidirectional sync. Factor in a 20–30% contingency for re-runs and edge case resolution.

ClonePartner has completed over 1,500 data migrations, including CRM-to-CRM projects involving polymorphic activity resolution and custom module mapping. Our engineering team manages the API throughput constraints on both platforms—automatically throttling Zoho extraction and batching Salesforce loads to prevent rate-limit failures. For complex Zoho CRM to Salesforce projects, we handle the full extraction, transformation, and load while your sales team keeps working in Zoho.

> Migrating from Zoho CRM to Salesforce and need to preserve every relationship, activity, and custom field? Our engineers handle the full extraction, transformation, and load while your sales team keeps working. Book a 30-minute technical scoping call.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### How long does a Zoho CRM to Salesforce migration take?

A standard sales org with under 500,000 records takes 2 to 6 weeks. Enterprise orgs with custom modules, multi-currency, and heavy activity history take 6 to 14 weeks. The timeline depends on data cleanup effort, number of custom modules, and automation rebuild scope.

### What data cannot be migrated from Zoho CRM to Salesforce?

Workflow Rules, Blueprints, Custom Functions, email templates, Canvas Views, approval histories, reports, dashboards, and Zoho SalesSignals cannot be migrated. These are platform-specific logic or UI constructs that must be rebuilt manually in Salesforce using Flows, Apex, and Lightning App Builder.

### Can I migrate custom modules from Zoho CRM to Salesforce?

Yes. Zoho CRM custom modules map to Salesforce custom objects. You must create each custom object, define all fields with matching data types, and set up Lookup or Master-Detail relationships before loading data. All API names change because Salesforce appends __c to custom object and field names.

### How much does a Zoho CRM to Salesforce migration cost?

Small orgs (under 50,000 records, standard modules) typically cost $2,000 to $5,000 with a managed service or 40 to 80 hours of internal engineering time. Enterprise migrations with custom modules and activity history range from $10,000 to $50,000 with a service provider, or 200 to 500+ hours of engineering effort.

### Is there downtime during a Zoho CRM to Salesforce migration?

Not necessarily. The best practice is to run the migration while the sales team continues working in Zoho CRM. After the bulk historical migration completes, run a delta sync to capture records created or modified during the migration window. Then cut over to Salesforce and set Zoho to read-only. This eliminates downtime.
