Skip to content

Monday CRM to Zoho CRM Migration: The CTO's Technical Guide (2026)

A technical guide to migrating from Monday CRM to Zoho CRM — covering API constraints, data model mapping, rate limits, and step-by-step execution for CTOs.

Nachi Nachi · · 26 min read
Monday CRM to Zoho CRM Migration: The CTO's Technical Guide (2026)
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Migrating from Monday CRM to Zoho CRM is a data-model translation problem. Monday CRM stores everything as boards, items, columns, and Connect Boards links — a flexible, spreadsheet-like structure where relationships are opt-in. Zoho CRM enforces a relational schema with modules, lookup fields, and validated relationships between Accounts, Contacts, and Deals at the database level.

Every structural gap between these two architectures is where data silently disappears if you don't engineer around it.

If you need a fast answer: CSV export/import works for small, flat datasets where you accept manual relationship rebuilding. API-based migration is the only safe path when you need to preserve Account → Contact → Deal chains, activity history, and custom field integrity. Middleware tools like Zapier and Make handle ongoing single-record syncing — not bulk historical migration.

This guide covers the exact API constraints on both sides, a concrete object-mapping strategy, every realistic migration method with its trade-offs, and the edge cases that cause silent data loss.

For the reverse migration path, see Zoho CRM to Monday.com Migration: The CTO's Technical Guide. For general CRM migration strategy, see Best Practices for CRM Data Migration in 2026.

Monday CRM's Board Model vs. Zoho CRM's Relational Architecture

Monday CRM uses a board-based model. Your account ships with entity boards — Contacts, Leads, Deals, Accounts, and Activities — interconnected through Connect Boards columns. A contact links to an account through a Connect Boards column that connects to the Accounts entity board. These links are optional and unenforced — a Contact can exist without an Account, and a Deal can exist without a Contact.

Two monday-specific behaviors change migration scope fast:

  • Monday can auto-create or auto-associate Accounts from a contact's email domain, which inflates or fragments your Accounts count.
  • The Activities board does not contain the full interaction history — emails and standard notes are not logged there as activities. If you only export entity boards and ignore the Emails & Activities timeline, you will under-migrate history.

Zoho CRM is a traditional relational CRM. Standard modules — Leads, Contacts, Accounts, Deals, Tasks, Events, Calls, Products — have enforced schemas with validated field types and lookup relationships. A Contact's Account_Name field is a lookup to the Accounts module. A Deal's Contact_Name is a lookup to Contacts. These relationships are enforced at the module level. A Lead exists independently until conversion, at which point it splits into an Account, a Contact, and a Deal.

The architectural mismatch creates three specific migration risks:

  1. Relationship loss — Connect Boards links don't export to CSV. You must extract and rebuild them via API.
  2. Schema flattening — Monday's flexible columns (Status, Dropdown, People) don't map 1:1 to Zoho's typed fields (Picklist, Lookup, Owner).
  3. Activity orphaning — Monday's Emails & Activities timeline is item-scoped. Emails sent through Emails & Activities won't export as structured activity records.
Info

Monday's API is GraphQL-based, and personal token permissions mirror the user's UI access. If your Monday token user cannot see a private board, the extractor won't see it either. Zoho CRM uses OAuth 2.0 plus REST and Bulk APIs.

Monday CRM Column Types: Field Coverage Matrix

Not all Monday column types have lossless equivalents in Zoho CRM. The table below classifies each common Monday CRM column type by its migration outcome.

Monday Column Type Migration Outcome Zoho Target Field Notes
Name (item title) ✅ Lossless Text / Name field Direct string mapping
Email ✅ Lossless Email Direct mapping
Phone ✅ Lossless Phone Direct mapping
Date ✅ Lossless Date Normalize to YYYY-MM-DD
Numbers ✅ Lossless Integer / Currency Verify decimal precision for currency
Text (long text) ✅ Lossless Multi-line text None
Status (label) ⚠️ Transform required Picklist Every label must be pre-mapped to a Zoho picklist value; unmatched labels import as blank
Dropdown ⚠️ Transform required Picklist Same label-matching requirement as Status
People ⚠️ Transform required Lookup (Users) Must resolve Monday user emails to Zoho CRM user IDs via Users API
Connect Boards ⚠️ Transform required Lookup Must resolve Monday item IDs to Zoho record IDs using an ID mapping table
Formula ⚠️ Transform required Custom function (Deluge) Calculated values can be exported as static snapshots; live recalculation must be rebuilt in Zoho
Rating ⚠️ Transform required Integer or Picklist No native equivalent; map to closest typed field
Timeline ⚠️ Partial loss Two separate Date fields Monday Timeline stores a date range; split into start_date and end_date fields in Zoho
Mirror (mirrored column) ⚠️ Partial loss Depends on source Mirror columns reflect data from another board; extract the source value, not the mirror
Dependency ⚠️ Partial loss No direct equivalent Record-to-record dependency logic has no native CRM equivalent in Zoho; consider Notes or custom fields
Location ⚠️ Partial loss Address fields Monday stores as a single location string; split into street/city/state/country if needed
Files / Attachments ⚠️ Partial loss Attachments (REST only) Bulk Write API does not support attachments; each file requires a separate REST API call
Item updates (rich text) ⚠️ Partial loss Notes Rich text, inline images, and @mentions must be sanitized before import; formatting is lost
Monday Workdocs ❌ No equivalent Workdocs are a document editor within Monday with no structural equivalent in Zoho CRM; migrate content to Zoho's knowledge base or an external wiki
Board-level automations ❌ No equivalent Monday automations are not exposed via API and do not migrate; rebuild as Zoho Workflow Rules or Blueprints
Timeline view / Gantt ❌ No equivalent View-only constructs; no data to migrate
Subitems ❌ No direct equivalent Custom module or Notes Subitems are separate items requiring a dedicated API query; Zoho has no native subitem concept
Warning

Content types in the bottom three rows (Workdocs, automations, timeline/Gantt views, subitems) are either abandoned or require custom structural work in Zoho. Budget for these explicitly — they are not covered by a standard field mapping pass.

Migration Approaches: From CSV to Custom ETL

Native CSV Export/Import

How it works: Export each Monday CRM board to Excel/CSV, clean the data, then import into the corresponding Zoho CRM module via Setup → Data Administration → Import.

Monday export constraints: You may only export a board with up to 10,000 items at a time. Only table views can be exported. Monday CRM only exports columns visible in the current view — hidden columns won't appear. Connected board columns may export as blank on lower-tier plans. Subitem updates are not exported.

Zoho import constraints: Row limits vary by edition — Standard: 10,000; Professional: 20,000; Enterprise: 30,000; Ultimate: 50,000. File size limits: Enterprise and Ultimate: 25 MB; Professional and Standard: 10 MB; Free: 5 MB.

When to use it: Under 5,000 records, flat data, no relationships to preserve.

Complexity: Low | Scalability: Small datasets only

Warning

CSV exports from Monday CRM flatten Connect Boards links into text strings (or blanks). You'll need to manually rebuild Account → Contact → Deal relationships in Zoho using VLOOKUP-style matching on email or company name. For anything beyond a few hundred records, this becomes error-prone and time-consuming.

API-Based Migration (Custom Script)

How it works: Extract data from Monday's GraphQL API, transform it in your pipeline, then load into Zoho CRM's REST API or Bulk API.

Monday's API uses cursor-based pagination. You query boards, items, and column_values, paginating in pages of up to 500 items. Cursors are valid for 60 minutes after the initial request.

On the Zoho side, you can insert/update/upsert a maximum of 100 records per standard API call. For large datasets, use the Bulk Write API, which handles up to 25,000 records per CSV file with 200 column headers.

When to use it: Mid-to-large datasets, when you need to preserve relational links and have engineering resources.

Complexity: High | Scalability: Enterprise-ready with proper batching

Middleware/iPaaS (Zapier, Make)

How it works: Set up triggers in Monday CRM (item created/updated) that push data to Zoho CRM via pre-built connectors.

When to use it: Ongoing sync of new records between systems. These platforms are event-driven orchestration layers, not ordered historical loaders. They process records one at a time and hit rate limits quickly at scale — a poor primary mechanism for backfilling years of CRM history.

Complexity: Low–Medium | Scalability: Poor for bulk migration

Third-Party Migration Tools

Tools like Import2 offer point-and-click migration between Monday and Zoho. They handle basic field mapping and can automate pipeline and custom-field creation. Import2's documentation notes that custom objects and emails are not supported, and converted leads are not imported as converted leads. Evaluate whether they support your specific record types and custom fields before committing.

Complexity: Low–Medium | Scalability: Small-to-medium datasets

Comparison Table

Method Complexity Preserves Relationships Handles Custom Fields Scalability Best For
CSV Export/Import Low ❌ Manual rebuild Partial < 5K records Quick, flat migrations
API-Based (Custom) High ✅ Full control ✅ Full Enterprise Complex, relational data
iPaaS (Zapier/Make) Low–Med Partial Partial Poor for bulk Ongoing sync
Third-Party Tools Low–Med Partial Partial Small–Med Simple datasets
Managed Service Low (your side) ✅ Full ✅ Full Enterprise Any size, any complexity

Scenario recommendations:

  • Small business, flat data, < 2K records → CSV export/import
  • Mid-market, 2K–50K records, needs relationships → API-based or managed service
  • Enterprise, 50K+ records, custom objects, zero-downtime → Managed service or dedicated engineering team
  • Ongoing sync between both systems → iPaaS with webhook triggers

Monday CRM API Rate Limits

Monday's GraphQL API uses complexity-based rate limiting — 10,000,000 complexity points per minute per account. That sounds generous until you realize nested queries grow exponentially in complexity. A single query fetching 500 items with all column values from a board with 30+ columns can consume hundreds of thousands of complexity points. If you're extracting Contacts, Deals, Accounts, and Activities boards simultaneously, you will hit the limit.

Monday encourages pagination by adding limit arguments to your calls. Paginated calls typically have lower complexity than querying objects without limits. You can still get all the data, just with more calls.

Monday has also introduced daily call limits. Daily request limits are currently enforced for basic and standard accounts, with limits for pro and enterprise accounts being gradually rolled out.

Warning

Some third-party posts quote older Monday complexity ceilings. Verify rate limits against current official API documentation before planning throughput. The numbers have changed over time.

Monday GraphQL Error States

When things go wrong on the Monday side, the API returns errors inside the response body rather than as HTTP error codes — GraphQL always returns HTTP 200 for syntactically valid requests. Errors appear in the errors array of the JSON response.

Common Monday API errors during migration:

Error Cause What to Do
ComplexityException Complexity budget exceeded for the current minute Back off for 60 seconds and retry; reduce items per page or remove nested fields
UserUnauthorizedException Token user lacks access to the requested board (private board) Switch to a token user with board access, or add the user to the board
ResourceNotFoundException Board ID or item ID does not exist Skip the record and log it; the item may have been deleted since extraction started
InvalidArgumentException Malformed query or unsupported argument Fix query syntax; check for deprecated field names against current API docs
RateLimitExceededException Daily call limit hit (enforced on Basic/Standard plans) Wait until the limit resets (typically midnight UTC) or upgrade plan tier

The response body for a complexity error looks like:

{
  "errors": [{
    "message": "Complexity limit exceeded",
    "extensions": {
      "code": "ComplexityException",
      "complexity": 12500000,
      "maxComplexity": 10000000
    }
  }],
  "data": null
}

Build your extraction loop to check response ["errors"] on every call — not just the HTTP status code.

Zoho CRM API Credit System

API usage in Zoho CRM is calculated based on credits. Each API call costs at least 1 credit, but some operations cost more — a Convert Lead API call costs 5 credits. Credits are allocated based on your edition and user count. In the Standard edition with 10 user licenses, available credits are 52,500 (50,000 + 10 × 250). For Enterprise with 100 users, that's 115,000 credits per day.

Zoho also enforces concurrency limits — the maximum number of API calls simultaneously active per org per app. The sub-concurrency limit is 10 for all CRM editions.

Zoho CRM API Error States

Zoho's REST API uses standard HTTP status codes alongside a structured JSON error body. Unlike Monday, a Zoho HTTP error code is meaningful.

HTTP Status Zoho Error Code Cause What to Do
429 API_LIMIT_EXCEEDED Daily credit limit exhausted Wait for reset (midnight IST); switch to Bulk API for large operations
429 CONCURRENT_REQUESTS_LIMIT_EXCEEDED More than 10 simultaneous calls per app Reduce thread count; implement a semaphore in your loader
400 INVALID_DATA Field value fails schema validation (e.g., text in a Date field) Fix the transform for that field type; check picklist values
400 MANDATORY_NOT_FOUND Required field missing from payload Check module schema for mandatory fields; add them to your field mapping
400 DUPLICATE_DATA Duplicate check field matched an existing record and operation is insert Switch to upsert or pre-deduplicate before loading
401 AUTHENTICATION_FAILURE OAuth token expired or invalid scope Refresh the access token; verify the token has the required CRM scopes
403 PERMISSION_DENIED Token user lacks write access to the target module Add the correct profile permissions in Zoho CRM for the OAuth user
500 INTERNAL_ERROR Transient Zoho server error Retry with exponential backoff; log the request for replay

For Bulk Write jobs specifically, errors appear in a downloadable result file rather than in the API response body. After polling for job completion, retrieve the result file and parse the status column — rows with SKIPPED or FAILED status include an error reason string.

The Bulk API Escape Hatch

Zoho's Bulk API bypasses standard credit limits for large-scale operations. For reads, 200,000 records can be exported in a single job. For writes, the maximum is 25,000 records per batch.

But the Bulk Write API has hard constraints:

  • Not supported: Attachments, Notes, file uploads, record images, Formula fields, Auto Number, Created By, Modified By, Created/Modified Time, rollup summary, Aggregate fields, Tags, and Campaign associations.
  • Initialization cost: Bulk Write initialization costs 500 credits per job whether it succeeds or fails.
  • Format requirement: You must upload a CSV file as a ZIP archive to a specific endpoint, obtain a file ID, then trigger an asynchronous job. You need polling logic to check job status and retrieve success/failure logs.
  • Lookup headers: For lookup fields in the CSV, headers must reference the field correctly — for example, Account_Name.id.

Attachments, notes, and file fields require separate API calls through the standard REST API — one call per file per record. For large attachment volumes, this burns hours and API credits.

Zoho OAuth Setup for Migration

The extraction script requires a Zoho OAuth 2.0 access token with the correct scopes. Getting this wrong is the most common reason a migration script fails before it processes a single record.

Token types:

  • Self-client: Suitable for one-time migrations. Generate a token manually in the Zoho API Console under Self Client. Access tokens expire after one hour; use the refresh token to obtain a new one.
  • Server-based OAuth: Appropriate for long-running or scheduled migrations. Requires a registered redirect URI. Use this if your migration runs over multiple hours or days.

Required scopes for a full CRM migration:

ZohoCRM.modules.ALL
ZohoCRM.bulk.ALL
ZohoCRM.settings.modules.READ
ZohoCRM.users.READ

ZohoCRM.modules.ALL covers read/write on all standard and custom modules. ZohoCRM.bulk.ALL is required for Bulk Write and Bulk Read API access. ZohoCRM.settings.modules.READ is needed if you're querying module metadata (field names, picklist values) during the transform phase. ZohoCRM.users.READ is required to resolve People column values to Zoho CRM user IDs.

Token refresh: Access tokens expire after one hour. Your loader must implement token refresh logic — call the token endpoint with your refresh token before each batch if elapsed time approaches the limit:

import requests
import time
 
def refresh_zoho_token(client_id, client_secret, refresh_token):
    resp = requests.post(
        "https://accounts.zoho.com/oauth/v2/token",
        params={
            "refresh_token": refresh_token,
            "client_id": client_id,
            "client_secret": client_secret,
            "grant_type": "refresh_token"
        }
    )
    return resp.json()["access_token"]
Warning

Zoho's token endpoint regional URLs differ by data center: accounts.zoho.com (US), accounts.zoho.eu (EU), accounts.zoho.com.au (AU), accounts.zoho.in (IN). Using the wrong regional endpoint returns an AUTHENTICATION_FAILURE error even if your credentials are correct. Match the token endpoint to the data center where your Zoho org is hosted.

Pre-Migration Planning Checklist

Before extracting a single record, audit your Monday CRM environment.

Data audit:

  • Count records per Monday board: Accounts, Contacts, Leads, Deals, Activities
  • Identify custom boards and non-standard columns
  • Flag unused/orphaned records for exclusion
  • Document all Connect Boards relationships
  • Export a sample set and verify field completeness
  • Flag Monday behaviors that distort counts: auto-account-association from email domains, qualified leads already moved into Contacts

Scope definition:

  • Which boards migrate? Which are archive-only?
  • Do Leads become Zoho Leads or Contacts? (Don't resurrect already-converted leads.)
  • Will you migrate historical activities, or start fresh?
  • Are there attachments/files to migrate?
  • Confirm your Zoho CRM edition supports the custom modules, fields, and linking modules you need
  • Identify content types with no Zoho equivalent (Workdocs, automations, Gantt views, subitems) and decide whether they are abandoned or need a separate migration path

Strategy selection:

  • Big bang: Everything moves at once. Simpler, but higher risk. Requires a maintenance window.
  • Phased: Migrate by module in waves (Accounts first, then Contacts, then Deals). Lower risk, longer timeline.
  • Parallel run: Both systems live simultaneously during transition. See Why Running Two CRMs in Parallel Beats a Hard Cutover for the trade-offs.

For a complete audit framework, see The Ultimate CRM Data Migration Checklist.

Data Mapping: Translating Boards to Modules

The safest mental model: Monday boards are source containers; Zoho modules are target truth.

Object Mapping

Monday CRM Board Zoho CRM Module Notes
Accounts Accounts Direct mapping. Company name, domain, industry fields. Deduplicate by normalized name/domain first.
Contacts Contacts Map Account via lookup to Accounts module.
Leads Leads or Contacts Unconverted leads → Zoho Leads. Qualified leads already moved to Contacts → Zoho Contacts + Deal. Do not recreate already-converted leads as Leads.
Deals Deals (Potentials) Map stage, value, expected close. Link Contact and Account lookups.
Activities Tasks / Events / Calls Split by activity type. Not the full email history.
Emails & Activities notes/emails Zoho Notes Separate extraction path required.
Custom boards (Client Projects) Custom Module Subject to edition limits. Consider Zoho Creator for complex structures.

Field-Level Mapping

Monday CRM Column Column Type Zoho CRM Field Zoho Field Type Transform Required
Item Name Name Deal_Name / Last_Name Text None
Status Status (label) Stage Picklist Map label values to Zoho picklist values exactly
Deal Value Numbers Amount Currency Verify decimal precision
Owner People Owner Lookup (Users) Map Monday user emails to Zoho user IDs
Email Email Email Email None
Phone Phone Phone Phone None
Expected Close Date Date Closing_Date Date Standardize to YYYY-MM-DD
Account (Connect Boards) Connect Boards Account_Name Lookup Resolve Monday item IDs to Zoho Account IDs
Close Probability Numbers Probability Integer None
Priority Status/Dropdown Priority Picklist Map label values
monday item ID Internal ID External ID or custom text field Text Store source identifier for auditing

Handling Custom Objects and Modules

Custom modules are now available across Standard, Professional, Enterprise, and Ultimate editions, but with different limits:

  • Standard: 10 team modules, max 25 custom fields per team module
  • Enterprise / Zoho One: Up to 200 modules (custom + team combined), 300 custom fields per module
  • Ultimate / CRM Plus: Up to 500 modules, 500 custom fields per module

If your Monday CRM setup relies on non-standard boards (Client Projects, custom workflow boards), map these to Zoho custom modules. If you need many-to-many relationships or hierarchical structures deeper than two levels, consider Zoho Creator instead of custom CRM modules — standard Zoho CRM modules support lookup fields but not true many-to-many junction objects. Linking modules (junction objects) are only manipulable in Enterprise and above, and each requires two mandatory lookup fields.

External ID fields — useful for storing the source Monday item ID — are only available in Enterprise and Ultimate editions. On lower tiers, use a custom text field as a legacy key.

Handling Relationships

This is where most migrations fail silently. Monday's Connect Boards columns store item IDs that only make sense inside Monday. Your migration script must:

  1. Extract all items from both the source board (e.g., Contacts) and the linked board (e.g., Accounts)
  2. Build an ID mapping table: monday_item_id → zoho_record_id
  3. Insert parent records (Accounts) first
  4. Insert child records (Contacts) with the Zoho Account ID as the lookup value
  5. Insert grandchild records (Deals) with both Contact and Account lookups

Process dependencies in strict order: Accounts → Contacts → Deals → Activities. Loading child records before parent records exist causes silent relationship loss — the record imports, but the lookup field is null.

Step-by-Step Migration Execution

Phase 1: Extract from Monday CRM

Use the GraphQL API to pull all items from each entity board with cursor-based pagination:

query {
  boards(ids: [CONTACTS_BOARD_ID]) {
    items_page(limit: 500) {
      cursor
      items {
        id
        name
        column_values {
          id
          text
          value
        }
      }
    }
  }
}

Then paginate:

query {
  next_items_page(cursor: "CURSOR_FROM_PREVIOUS_RESPONSE") {
    cursor
    items {
      id
      name
      column_values {
        id
        text
        value
      }
    }
  }
}

Each cursor is valid for 60 minutes after the initial request. Build throttling logic that checks remaining complexity before each request.

Phase 2: Transform

  • Parse column_values: Monday returns column values as JSON blobs. The text field gives human-readable values; the value field gives raw JSON. Use value for structured data (dates, statuses, Connect Boards IDs).
  • Resolve Connect Boards links: Extract linked item IDs from the value JSON of Connect Boards columns. Build your ID mapping table.
  • Map picklist values: Monday Status/Dropdown labels must match Zoho picklist values exactly. Create a mapping dictionary. If your Monday board uses "In Progress" and your Zoho Deal Stage picklist expects "Negotiation/Review", the value imports as blank.
  • Normalize dates: Standardize all dates to YYYY-MM-DD.
  • Handle Owner assignment: Map Monday People column (user emails) to Zoho CRM user IDs via the Users API.
  • Deduplicate: Monday CRM allows duplicate items with the same name and email on the same board. Monday's auto-account-association can also create duplicate company records from email domains. Reconcile before import.
  • Split blended boards: If qualified leads were moved into the Contacts board in Monday, separate them into the correct Zoho target (Contacts + Deals, not Leads).

Phase 3: Load into Zoho CRM

For bulk loading, use the Bulk Write API:

import requests
import json
 
# Step 1: Upload CSV file as ZIP
upload_url = "https://content.zohoapis.com/crm/v8/upload"
headers = {"Authorization": f"Zoho-oauthtoken {access_token}"}
files = {"file": ("accounts.zip", open("accounts.zip", "rb"), "application/zip")}
upload_resp = requests.post(upload_url, headers=headers, files=files)
file_id = upload_resp.json()["details"]["file_id"]
 
# Step 2: Create Bulk Write Job
bulk_write_url = "https://www.zohoapis.com/crm/bulk/v8/write"
payload = {
    "operation": "upsert",
    "resource": [{
        "type": "data",
        "module": {"api_name": "Accounts"},
        "file_id": file_id,
        "find_by": "Account_Name",
        "field_mappings": [
            {"api_name": "Account_Name", "index": 0},
            {"api_name": "Website", "index": 1},
            {"api_name": "Industry", "index": 2},
            {"api_name": "Phone", "index": 3}
        ]
    }]
}
job_resp = requests.post(
    bulk_write_url,
    headers={**headers, "Content-Type": "application/json"},
    data=json.dumps(payload)
)
job_id = job_resp.json()["details"]["id"]
 
# Step 3: Poll for completion and retrieve success/failure logs

Capture the new Zoho Account IDs from the success logs. You need these to build relationships when loading Contacts and Deals.

Phase 4: Rebuild Relationships

After loading Accounts, load Contacts with Account lookup values. Then load Deals with both Contact and Account lookups:

# Update Contacts with Account lookups (batches of 100)
update_url = "https://www.zohoapis.com/crm/v8/Contacts"
for batch in chunked(contact_updates, 100):
    payload = {"data": [
        {"id": c["zoho_id"], "Account_Name": {"id": c["account_zoho_id"]}}
        for c in batch
    ]}
    requests.put(
        update_url,
        headers={**headers, "Content-Type": "application/json"},
        json=payload
    )

Finally, load Notes and Attachments via the standard REST API, since the Bulk Write API does not support them.

Phase 5: Validate

  • Compare record counts per module: Monday board item count vs. Zoho module record count
  • Sample 5–10% of records for field-level accuracy
  • Verify all lookup fields resolve correctly
  • Confirm picklist values imported without blanks
  • Check that activities are linked to correct Contacts/Deals
Tip

Build error logging into every API call. Log the Monday item ID, target module, lookup key, phase, raw vendor error, and retryability. This lets you replay failed records without re-running the entire migration.

Edge Cases That Cause Silent Data Loss

Duplicate records from auto-association: Monday CRM can auto-create Account records from a contact's email domain. This inflates or fragments your Accounts, creating duplicates that need reconciliation before you load into Zoho. Zoho's Bulk Write API can deduplicate on import using the find_by parameter — but only on one field. If you have duplicates across Leads and Contacts boards, reconcile them before import.

Multi-level relationship dependencies: Monday CRM's Account → Contact → Deal chain requires three separate load phases in strict dependency order. If you load Deals before Accounts exist in Zoho, the Account lookup fails silently — the Deal imports, but the relationship is null.

Multi-select Connect Boards: Monday allows you to link one Contact to multiple Accounts. Zoho CRM natively assumes a Contact belongs to one Account. You must enable the "Contact Roles" feature in Zoho or build a custom junction module. Linking modules are only available in Enterprise and above.

Attachments and notes: The Bulk Write API does not support Attachments, Notes, file uploads, or record images. You must use the standard REST API to upload each attachment individually — one API call per file per record. Zoho's total attachment size on a record should not exceed 100 MB.

Status/picklist mismatches: Monday Status labels are freeform text. If the label doesn't match a Zoho picklist value exactly, the field imports as blank. Map every picklist value before running the migration.

Subitem data: Monday subitems (child rows under items) don't export cleanly. Subitem updates are not exported to Excel — only parent item updates are. Via API, subitems are separate items that need a dedicated query.

Rich text updates: Monday's item updates often contain rich text, inline images, and @mentions. Zoho Notes support plain text or basic HTML. Sanitize Monday HTML payloads before pushing to Zoho.

Orphaned data: If an Account was deleted in Monday but the Contact remains, the Connect Boards column points to a ghost ID. Your ETL script must catch these and assign them to a default "Orphaned Records" account in Zoho.

Board item limits: The limit is 10,000 items per board for all plan types except Enterprise (100,000 items). CRM Pro users can have up to 5 boards, each with 100K items. If your boards are near these limits, API extraction takes significant time due to pagination and complexity limits.

Activities mismatch: Monday's Activities board excludes emails and standard notes. Activity migration and interaction-history migration are not the same thing. Plan the email/notes extraction path separately.

Zoho CRM Limitations to Plan For

  • Bulk Write field gaps: Formula fields, Auto Number, Created/Modified timestamps, Tags, and Campaign associations cannot be set via Bulk Write. Timestamps will reflect import time, not original creation time, unless you use a custom DateTime field.
  • Import automation trigger limit: Zoho only displays the option to trigger automation and process management if there are fewer than 10,000 rows in the import file. If your import exceeds 10K rows, workflows won't fire automatically.
  • Concurrency limits: Zoho limits simultaneous API calls per org per app. The sub-concurrency limit is 10 for all editions.
  • COQL query constraints: Zoho's COQL (CRM Object Query Language) is useful for audit queries but limited to 2,000 rows per call (100,000 with pagination), select-only, and does not support attachments in criteria.
  • Formula columns → Deluge: If your team relied on Monday's formula columns to calculate real-time metrics across boards, you'll rebuild these using Zoho CRM custom functions written in Deluge. This is a system implementation task, not a data migration task. Keep these phases separate.

Validation and Testing

  1. Record count comparison — Total items per Monday board must match total records per Zoho module (minus intentionally excluded records).
  2. Field-level spot check — Pull 50 random records from each module. Verify every field matches the source.
  3. Relationship verification — For every sampled Contact, confirm the Account lookup is correct. For every Deal, confirm both Contact and Account lookups resolve.
  4. Picklist audit — Export Zoho module data and check for blank picklist fields where Monday had values.
  5. UAT — Have sales reps validate 10 accounts end-to-end: Account details, linked Contacts, Deal history, and Activity logs.
  6. Rollback plan — Before go-live, take a full Zoho CRM backup. If validation fails after go-live, follow the rollback procedure below.

Rollback Procedure

Simply saying "delete the records and re-run" understates the complexity. Here is a more precise rollback sequence:

  1. Before go-live: Take a full Zoho CRM backup via Setup → Data Administration → Export. Store all module CSVs. Note the exact timestamp.
  2. Identify the rollback scope: If only Phase 4 (Deals) failed, you may not need to roll back Phases 1–3 (Accounts, Contacts). Use the stored Monday item IDs in your External ID / legacy key field to identify exactly which records belong to this migration run.
  3. Bulk-delete imported records without triggering workflows: In Zoho CRM, bulk deletion via the UI triggers Blueprint and workflow evaluation. To avoid this, use the REST API DELETE endpoint with the IDs of imported records. The DELETE endpoint does not trigger workflow rules. Process deletions in batches of 100 to stay within the concurrency limit.
  4. Verify dependent records are cleared: If you loaded Contacts that reference Accounts you are deleting, delete child records (Contacts, Deals) before parent records (Accounts). Zoho will block deletion of an Account that still has associated Contacts.
  5. Fix the transform logic for the identified failure cause (e.g., picklist mismatch, relationship load order error).
  6. Re-run from the failed phase only. Because you stored source IDs, you can re-run a single module's load without reprocessing phases that already succeeded.
Warning

Zoho's recycle bin retains deleted records for 60 days. Records in the recycle bin still count against your module record limits until permanently deleted. After a rollback, empty the recycle bin before re-running the migration if you are near edition limits.

For broader QA patterns, see Best Practices for CRM Data Migration in 2026: The Engineer's Guide.

Post-Migration Tasks

Once data is verified, execute the final delta sync to catch any records created in Monday during the migration window.

  • Disable Monday creation rights. After the final sync, prevent new record creation in Monday CRM to stop data drift.
  • Rebuild automations. Monday CRM automations don't migrate. Monday's API does not expose board automation management. Recreate them as Zoho CRM Workflow Rules, Blueprints, or Deluge functions. Zoho Bulk Write does not trigger workflows, so automation parity is a separate implementation task.
  • Rebuild dashboards and views. Monday dashboards are board-specific and non-transferable. Build equivalent Zoho CRM reports and analytics.
  • Train users. Zoho CRM's module-based navigation is fundamentally different from Monday's board-based UI. Budget 1–2 weeks for team onboarding.
  • Monitor for inconsistencies. Run daily validation queries for the first 2 weeks. Look for orphaned records, blank lookups, and duplicate entries. Monitor deltas for at least one full business cycle after cutover.

When to Use a Managed Migration Service

Build in-house when your dataset is small, flat, and your team has spare engineering capacity. Hire a managed service when:

  • You have 50K+ records across multiple boards with Connect Boards relationships
  • Your team's engineering bandwidth is committed to product work
  • You need zero downtime — sales can't stop during migration
  • You have custom boards or non-standard column types that need transformation logic
  • Your Monday setup includes private workspaces, cross-board links, or files
  • You've already attempted a DIY migration and found orphaned relationships or missing data

The hidden cost of DIY isn't the API code — it's the edge cases. Handling picklist mismatches, resolving duplicate contacts, rebuilding multi-level relationships, and debugging silent import failures can consume weeks of engineering time on datasets that looked simple at the outset. The actual duration depends on dataset size, number of custom fields, complexity of Connect Boards relationships, and how much deduplication work is needed before the first record loads.

Python Migration Script Outline

High-level structure for an API-based migration:

import requests
import json
import time
 
MONDAY_API_URL = "https://api.monday.com/v2"
ZOHO_API_URL = "https://www.zohoapis.com/crm/v8"
 
class MondayExtractor:
    def __init__(self, api_token):
        self.headers = {"Authorization": api_token, "Content-Type": "application/json"}
 
    def extract_board(self, board_id):
        """Extract all items from a board using cursor pagination."""
        all_items = []
        query = '''{ boards(ids: [%s]) { items_page(limit: 500) {
            cursor items { id name column_values { id text value } } } } }''' % board_id
        resp = requests.post(MONDAY_API_URL, json={"query": query}, headers=self.headers)
        data = resp.json()["data"]["boards"][0]["items_page"]
        all_items.extend(data["items"])
        cursor = data["cursor"]
 
        while cursor:
            next_query = '''{ next_items_page(cursor: "%s") {
                cursor items { id name column_values { id text value } } } }''' % cursor
            resp = requests.post(MONDAY_API_URL, json={"query": next_query}, headers=self.headers)
            page = resp.json()["data"]["next_items_page"]
            all_items.extend(page["items"])
            cursor = page["cursor"]
            time.sleep(1)  # Throttle to manage complexity budget
        return all_items
 
class ZohoLoader:
    def __init__(self, access_token):
        self.headers = {"Authorization": f"Zoho-oauthtoken {access_token}"}
 
    def upsert_records(self, module, records, find_by="Email"):
        """Insert/update records in batches of 100."""
        results = []
        for i in range(0, len(records), 100):
            batch = records[i:i+100]
            payload = {"data": batch, "duplicate_check_fields": [find_by]}
            resp = requests.post(
                f"{ZOHO_API_URL}/{module}/upsert",
                headers={**self.headers, "Content-Type": "application/json"},
                json=payload
            )
            results.append(resp.json())
            time.sleep(0.5)
        return results
 
class Transformer:
    def __init__(self, field_map, picklist_map, id_map):
        self.field_map = field_map       # monday_col_id -> zoho_field_api_name
        self.picklist_map = picklist_map  # {zoho_field: {monday_label: zoho_value}}
        self.id_map = id_map             # {monday_item_id: zoho_record_id}
 
    def transform_item(self, item):
        record = {}
        for col in item["column_values"]:
            zoho_field = self.field_map.get(col["id"])
            if not zoho_field:
                continue
            value = col["text"]
            if zoho_field in self.picklist_map:
                value = self.picklist_map[zoho_field].get(value, value)
            record[zoho_field] = value
        return record
 
# Execution order:
# 1. Extract Accounts -> Transform -> Load into Zoho Accounts
# 2. Build ID map: monday_account_id -> zoho_account_id
# 3. Extract Contacts -> Transform (resolve Account lookups) -> Load
# 4. Build ID map: monday_contact_id -> zoho_contact_id
# 5. Extract Deals -> Transform (resolve Contact + Account lookups) -> Load
# 6. Extract Activities -> Transform -> Load as Tasks/Events
# 7. Load Notes and Attachments via standard REST API
Info

This is a simplified outline. A production migration script needs OAuth token refresh logic, exponential backoff for rate limits, comprehensive error logging, a dry-run mode that validates transforms without writing to Zoho, and retry queues for failed records.

Best Practices

  • Back up everything first. Export your entire Monday account (Admin → General → Export account data) and take a Zoho CRM backup before touching anything.
  • Run a test migration. Migrate 100 records per module into a Zoho sandbox. Validate relationships and field values before touching production.
  • Validate incrementally. Don't wait until the end to check data quality. Validate after each module loads.
  • Disable Zoho automations during import. Workflow rules and Blueprints can fire during import and corrupt your data — auto-assigning owners, changing stages, or sending emails.
  • Document your field mapping. A shared spreadsheet mapping every Monday column to its Zoho equivalent (with data type and transform logic) prevents miscommunication between teams.
  • Store source IDs. Keep the Monday item ID in a Zoho custom field or External ID field. This makes auditing, debugging, and delta syncs far easier.
Info

All API-specific numbers in this guide — credit allocations, complexity limits, row caps, concurrency limits — are verified against vendor documentation at time of publication. API limits change. Verify current values against the Monday API documentation and Zoho CRM API limits documentation before planning throughput.

If your Monday CRM is truly small and flat, don't over-engineer this. If it's the operational memory of sales and revenue ops, don't under-engineer it either. The right migration path preserves relationships, makes rollback possible, and doesn't hand your team a month of cleanup disguised as a launch.

Frequently Asked Questions

Can I migrate from Monday CRM to Zoho CRM using CSV export?
Only for small, flat datasets under ~5,000 records. Monday's board export is capped at 10,000 items and strips Connect Boards relationships. You'll need to manually rebuild Account-Contact-Deal links in Zoho using VLOOKUP matching, which is error-prone at scale.
What are the Monday.com API rate limits for data extraction?
Monday's GraphQL API allows 10,000,000 complexity points per minute per account. Nested queries grow exponentially in complexity, so extracting large boards with many columns can exhaust this limit quickly. Use cursor-based pagination with 500 items per page and built-in throttling. Daily call limits are also being rolled out by plan tier.
Does the Zoho CRM Bulk Write API support attachments and notes?
No. Zoho's Bulk Write API does not support Attachments, Notes, file uploads, or record images. You must use the standard REST API to upload each attachment individually — one API call per file per record. Formula fields, Auto Number, and Created/Modified timestamps are also not supported via Bulk Write.
What order should I load data into Zoho CRM during migration?
Load in dependency order: Accounts first, then Contacts (with Account lookups), then Deals (with Contact and Account lookups), then Activities (linked to Contacts/Deals). Loading child records before parent records exist causes silent relationship loss — the record imports but the lookup field is null.
Can Zapier or Make handle a full Monday CRM to Zoho CRM migration?
They are better for ongoing sync or a parallel-run bridge, not bulk historical migration. These platforms process records one at a time via trigger-action automations, so using them as the primary migration engine for years of CRM history is usually the wrong fit.

More from our Blog