Skip to content

Nutshell to GoHighLevel Migration Guide (2026)

Technical guide to migrating from Nutshell to GoHighLevel — covering data model mapping, API constraints, migration methods, and edge cases that break DIY attempts.

Raaj Raaj · · 24 min read
Nutshell to GoHighLevel Migration Guide (2026)
TALK TO AN EXPERT

Planning a migration?

Get a free 30-min consultation. Our engineers review your setup and map out a custom migration plan — no obligation.

Schedule Free Consultation
  • 1,200+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing

Migrating from Nutshell to GoHighLevel (GHL) is a data-model translation problem. Nutshell is a B2B account-centric CRM — Companies contain People, and Leads link to both. GoHighLevel is contact-centric with pipeline-driven workflows — everything radiates outward from a flat Contact record, with Opportunities inside Pipelines and Companies treated as a loose grouping mechanism.

A CSV export from Nutshell will flatten those Company → Person → Lead relationships, silently orphan activity histories, and give you zero automation logic on the GHL side. If you attempt a naive export-import, you will lose your relational data, create duplicate records, and truncate historical notes. Serious migrations require API-level execution to rebuild Nutshell's B2B architecture inside GoHighLevel's flatter schema.

This guide covers the structural mismatch between the two platforms, the API constraints on both sides, every viable migration method with honest trade-offs, data mapping strategies, and the edge cases that break most DIY attempts. If you're migrating from a different CRM to GoHighLevel, see our Salesforce to GoHighLevel migration guide or Close to GoHighLevel migration guide for comparisons against GHL's data model.

Why Companies Migrate from Nutshell to GoHighLevel

The drivers typically fall into three buckets:

  • Cost consolidation and marketing automation. Nutshell is a strong sales CRM, but marketing features are limited. GoHighLevel bundles CRM, pipeline management, marketing automation, funnels, SMS/email, and appointment scheduling into a single subscription starting at $97/month. Teams running Nutshell plus a separate email platform, landing page builder, and scheduling tool often find GHL cheaper in aggregate. (nutshell.com)
  • Agency model fit. GoHighLevel was built for agencies. Its sub-account architecture, white-labeling, and SaaS Mode let agencies resell the platform under their own brand. Nutshell doesn't offer this.
  • Workflow consolidation. Nutshell excels at pipeline management for B2B teams but lacks the built-in funnel builders, SMS marketing, and appointment booking that GHL includes natively.

Nutshell vs. GoHighLevel: Architecture and Data Model Differences

This section determines whether your migration succeeds or fails.

Nutshell's data model is account-centric and B2B-oriented. Companies are the top-level entity. People (individuals) can belong to one or more Companies. Leads represent business deals or opportunities — there's no separate distinction between leads and opportunities in Nutshell. A Lead can be attached to Companies and/or People, and your sales process from discovery to closing is tracked on a single Lead record. On Nutshell Pro and above, parent-child Company relationships let you track leads across multi-location businesses. The API treats accounts, contacts, and leads as core entities and exposes related data through a links object. (support.nutshell.com)

GoHighLevel's data model is contact-centric. The standard CRM entities are Contacts, Companies, and Opportunities (which live inside Pipelines). Companies serve as a grouping tool, organizing multiple contacts under a single company while providing a unified view of their collective activities. Opportunities must be linked to a Contact and placed in a Pipeline with a specific Stage. A contact can only be associated with one company at a time. (help.gohighlevel.com)

Concept Nutshell GoHighLevel
Top-level entity Company (Account) Contact
Individual people People (nested under Companies) Contacts (top-level)
Deals/Opportunities Leads (attached to Companies + People) Opportunities (inside Pipelines, linked to one Contact)
Activity history Timeline on Companies, People, and Leads Notes, Tasks on Contacts
Parent-child orgs Native (Pro+) Companies + Custom Objects
Custom data Custom Fields on Companies/People/Leads Custom Fields + Custom Objects
Company-contact relationship Person can belong to multiple Companies Contact linked to one Company

The fundamental issue: Nutshell's Lead is a relationship between a Company, one or more People, and a deal. GHL's Opportunity is a record attached to a single Contact inside a Pipeline. Multi-contact deals and multi-level organizational hierarchies need explicit architectural decisions before you start moving data.

The Custom Object Question

Info

Important update: GoHighLevel's Custom Objects are now available on every subscription tier (Starter, Unlimited, Pro) — up to 10 Custom Objects per sub-account. Earlier guides may reference Custom Objects as a Pro-only feature. That constraint no longer applies. (help.gohighlevel.com)

Custom Objects let you define record types beyond the standard CRM entities — for example, Properties, Vehicles, Policies, or Projects. Each object can have its own custom fields, relationships, and automations.

This matters for Nutshell migrations because:

  • Company hierarchies — If your Nutshell instance uses parent-child Company relationships, you can model child entities as Custom Object records associated with the parent Company in GHL.
  • Products — Nutshell Leads carry product data with pricing. GHL Opportunities don't have the same native product structure. You may need a Custom Object to preserve product-level detail.
  • Any Nutshell data that doesn't map to Contacts, Companies, or Opportunities — Custom Objects are your escape hatch.

Key constraints:

  • 10 Custom Objects max per sub-account across all plans
  • 10 unique fields max per object
  • The internal name and primary display field are not editable after creation
  • Custom Objects can be linked to Contacts, Companies, Opportunities, and other Custom Objects
  • Bulk import of associations is not yet fully supported — associations must be created individually via UI or API
  • Bulk emails, marketing campaigns, and certain communication tools only work with Contacts, not Custom Objects
  • Association support via the public API is still expanding — test in a sandbox before relying on it for migration (help.gohighlevel.com)

Use Custom Objects only when the source entity has its own lifecycle and reporting value. If the data is just metadata, flatten it into custom fields.

Migration Methods: CSV vs. iPaaS vs. Custom API vs. Managed Service

There are four viable approaches. Each has a different cost-complexity-fidelity profile.

Method 1: Native CSV Export/Import

How it works: Export Companies, People, and Leads as CSV files from Nutshell's list views or via a full export (Company Settings → Export generates a ZIP containing separate CSVs for Accounts, Activities, Contacts, Leads, Notes, Products, Tasks, and truncated Emails). Import into GHL using the bulk CSV importer. (support.nutshell.com)

When to use it: Under ~2,000 contacts with flat data, no multi-level relationships you need to preserve, and no activity/note history that matters.

Pros:

  • Zero engineering effort
  • Free
  • Fast for small datasets

Cons:

  • Flattens all relational data — Company → Person → Lead links are lost
  • Nutshell's CSV export only includes columns visible in your list view; you must toggle columns before exporting
  • GHL CSV import limits: 30 MB per file (treat this as the safe cap — some GHL docs mention 50 MB, but others specify 30 MB), one note per contact record (max 5,000 characters), and you need at least a name, email, or phone per row (help.gohighlevel.com)
  • Nutshell's full export includes emails only in truncated form (support.nutshell.com)
  • Opportunities must be imported separately and require a valid Contact ID in GHL, so contacts must be imported first
  • No rollback mechanism on GHL imports

Complexity: Low | Risk: High for anything beyond a flat contact list

For a deeper analysis of CSV-based migration trade-offs, see our guide on using CSVs for SaaS data migrations.

Method 2: iPaaS Middleware (Zapier, Make)

How it works: Create Zaps or Make scenarios that read records from Nutshell via API triggers or scheduled searches and write them into GHL via API actions.

When to use it: Post-cutover sync, small delta migrations, or light coexistence during a phased transition. Not as your primary method for a large historical backfill.

Pros:

  • No-code setup
  • Can preserve some relationships if you build multi-step workflows (create Company → create Contact → link → create Opportunity)
  • Good for ongoing sync during transition periods

Cons:

  • Task costs scale linearly — migrating 10,000 contacts with associated leads and notes could consume tens of thousands of Zapier tasks
  • Timeout errors on large datasets
  • GHL's API rate limits (100 requests per 10 seconds) will throttle high-volume runs
  • Error handling is minimal — a failed step may leave partially created records
  • No built-in deduplication logic
  • Nutshell webhooks are a firehose of events; you must filter aggressively downstream (developers.nutshell.com)
  • GHL's webhook retry behavior only retries 429 (rate limit) responses, not 5xx server errors (marketplace.gohighlevel.com)

Complexity: Low–Medium | Risk: Medium — task costs surprise people, and partial failures are hard to detect

Method 3: Custom API Scripts / ETL Pipeline

How it works: Write scripts that extract data from Nutshell's API, transform it to match GHL's schema, and load it via GHL's V2 API. This is the only method that gives you full control over relationship preservation, field mapping, deduplication, and error handling.

When to use it: More than ~5,000 records, any multi-level relationships (Company → Person → Lead), custom fields that need transformation, activity/note history that must be preserved, or when you need incremental (delta) sync capability.

Pros:

  • Full control over data transformation and relationship rebuilding
  • Handles deduplication, field mapping, and data cleaning programmatically
  • Idempotent retry logic for API failures
  • Can run incremental migrations (delta sync)
  • Highest auditability — you own the crosswalk, the logs, and the error recovery

Cons:

  • Requires 40–80+ hours of engineering time for a production-quality pipeline
  • Must handle Nutshell's JSON-RPC API (a REST API launched in February 2026 is also available)
  • Must handle GHL's rate limits (see next section)
  • Needs thorough testing with production-like data

If you have Nutshell Enterprise, read-only SQL access makes extraction significantly easier for very large datasets. (developers.nutshell.com)

Complexity: High | Risk: Low if well-engineered, high if rushed

Method 4: Managed Migration Service

How it works: A dedicated migration team handles extraction, transformation, loading, validation, and rollback planning. You define the mapping rules and acceptance criteria; they handle execution.

When to use it: When engineering bandwidth is limited, when the migration is complex (multi-level relationships, large datasets, custom objects), or when downtime and data loss are not acceptable.

Pros:

  • Fastest time to completion — days, not weeks
  • Relationship preservation handled by experienced engineers
  • Built-in validation and rollback procedures
  • You don't burn your team's sprint capacity

Cons:

  • Direct cost (though often less than the loaded cost of 40–80+ engineering hours at $100–200/hour)
  • Requires clear communication of business rules and mapping decisions

Complexity: Low (for you) | Risk: Lowest — accountability sits with the migration team

Comparison Table

Factor CSV Export/Import iPaaS (Zapier/Make) Custom API / ETL Managed Service
Engineering effort None Low High (40–80+ hrs) None
Preserves relationships Partial
Handles custom fields Partial Partial
Activity/note migration Limited
Scalability < 2K records < 5K records Unlimited Unlimited
Error handling Manual Minimal Full control Managed
Ongoing sync No Yes Yes Yes
Cost Free $50–500+/mo in tasks Engineering time Fixed project fee

Recommendations by Scenario

  • Small business, flat contact list, < 2K records: CSV is fine. Accept the loss of relationships and rebuild pipelines manually.
  • Small business, ongoing sync during transition: Zapier/Make for forward-flowing data, CSV for the initial historical load.
  • Mid-market, 5K–50K records, multi-level relationships: Custom API script or managed service. The relationship-preservation problem alone justifies the investment.
  • Agency migrating multiple client accounts: Managed service. The per-account complexity multiplied by N clients makes DIY impractical.

Nutshell and GoHighLevel API Constraints

A custom API migration requires navigating strict constraints on both sides.

Nutshell API

Nutshell offers two APIs for data extraction:

  1. JSON-RPC API (v1) — The primary, most full-featured API. Uses JSON-RPC v2.0, accessible via HTTPS at https://app.nutshell.com/api/v1/json. Authentication uses HTTP Basic with your email/domain as username and API key as password. Key methods: findContacts(), findLeads(), getContact(), getLead(), findAccounts(). (developers.nutshell.com)

  2. REST API — Launched in February 2026, resource-based with standard HTTP methods. Better filtering and searching capabilities. Use the links object to fetch related contacts, files, products, and other linked entities without flattening too early. The filter syntax supports incremental windows, e.g., filter [createdTime]=start TO end. (developers.nutshell.com)

Rate limits are intentionally vague:

  • find*() requests with non-stub responses are rate-limited the most
  • get*() requests are rate-limited to a lesser extent
  • add or edit requests are not rate-limited
  • Nutshell does not publish specific numeric limits — treat this as an undocumented ceiling and build your extractor with throttling, retry, and restart checkpoints

Pagination: Use limit and page parameters for client-side pagination on find* methods.

# Nutshell JSON-RPC: Fetch contacts page by page
import requests
 
def fetch_nutshell_contacts(api_url, username, api_key, page=1, limit=50):
    payload = {
        "jsonrpc": "2.0",
        "method": "findContacts",
        "params": {
            "orderBy": "id",
            "orderDirection": "ASC",
            "limit": limit,
            "page": page
        },
        "id": "req-1"
    }
    response = requests.post(
        api_url,
        auth=(username, api_key),
        json=payload,
        headers={"Content-Type": "application/json"}
    )
    return response.json()
Warning

Nutshell API edge case: Nutshell's API does not return a WWW-Authenticate header on 401 responses, which violates RFC 2617. Some HTTP clients (especially .NET) won't automatically retry with credentials. Force the Authorization header on the first request.

GoHighLevel API V2

GHL's V2 API is REST-based with OAuth 2.0 or Private Integration Tokens for authentication. V1 has reached end-of-support — all new work should target V2. OAuth access tokens are valid for one day; refresh tokens are valid for one year. (marketplace.gohighlevel.com)

Rate limits are strict and well-defined:

  • Burst limit: 100 API requests per 10 seconds, per Marketplace app, per resource (Location or Company)
  • Daily limit: 200,000 API requests per day, per Marketplace app, per resource
  • Rate limit headers are included in API responses for monitoring

For a migration moving 20,000 contacts with associated opportunities and notes, you're looking at roughly:

  • 20,000 contact creates
  • 15,000 opportunity creates
  • 40,000 note creates
  • = 75,000 API calls minimum, achievable in under a day within rate limits

Add error retries, lookups for deduplication, and association creation, and the real number is often 2–3x higher.

# GHL API V2: Create a contact with exponential backoff and jitter
import requests
import time
import random
 
def create_ghl_contact(access_token, location_id, contact_data, max_retries=5):
    url = "https://services.leadconnectorhq.com/contacts/"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Version": "2021-07-28",
        "Content-Type": "application/json"
    }
    payload = {"locationId": location_id, **contact_data}
    
    for attempt in range(max_retries):
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code == 429:  # Rate limited
            wait = min(2 ** attempt + random.uniform(0, 1), 60)
            time.sleep(wait)
            continue
        response.raise_for_status()
        return response.json()
    raise Exception("Max retries exceeded")
Danger

Upsert safety: GHL's POST /contacts/upsert is only as safe as your duplicate settings. If one existing contact matches email and another matches phone, GHL updates whichever field has higher priority in that location's duplicate configuration and ignores the other. Freeze your duplicate settings before running dry runs. (marketplace.gohighlevel.com)

Deprecated endpoint: GHL's GET /contacts/ endpoint is deprecated. Use POST /contacts/search for reconciliation and validation. (marketplace.gohighlevel.com)

For more detail on GHL's API rate limit behavior, see our Close to GoHighLevel migration guide.

Pre-Migration Planning

Data Audit Checklist

Before writing a single line of code or exporting a single CSV, audit what you have:

  • Companies — Total count, parent-child relationships, custom fields
  • People — Total count, multi-company associations, custom fields
  • Leads — Total count by pipeline and stage, products attached, confidence levels, sources
  • Activities — Types (calls, meetings, tasks), volume, which ones matter post-migration
  • Notes — Volume per record, character lengths (GHL limits to 5,000 chars per note via CSV)
  • Custom fields — Data types, picklist values, which fields are actively used vs. legacy
  • Tags — Used for segmentation? Automation triggers? Both?
  • Users/Owners — Field assignments that need to map to GHL team members
  • Files/Attachments — Volume, which records they're attached to
  • Emails — Nutshell's full export includes only truncated emails; decide if full email history matters (support.nutshell.com)

Nutshell's full export ZIP is a useful starting inventory because it explicitly includes Accounts, Activities, Contacts, Leads, Notes, Products, Tasks, and truncated Emails.

Define Migration Scope

Not everything needs to move. Common exclusions:

  • Leads older than 2–3 years with status "Lost" (archive in a data warehouse instead)
  • Duplicate or merged records
  • Test/sandbox data
  • Activities that have no business value post-migration

Migration Strategy

Strategy Best for Risk
Big bang Small datasets, weekend cutover Higher — one shot to get it right
Phased Large datasets, multiple pipelines Lower — validate each phase before the next
Incremental Ongoing sync during parallel-run Lowest — but requires more engineering

For most Nutshell-to-GHL migrations, a phased approach works best: migrate Companies and People first, validate, then migrate Leads/Opportunities, validate again, then migrate notes and activities.

Your risk plan should include a full source backup, a test migration in a GHL sandbox, written field mappings, a duplicate policy, a source-to-target ID crosswalk, and a rollback path.

Data Mapping: Nutshell → GoHighLevel

Mapping is where the actual engineering decisions happen.

Object-Level Mapping

Nutshell Object GoHighLevel Target Notes
Company Company GHL Companies are a grouping tool — less feature-rich than Nutshell's Company object. One contact can link to one Company.
Person Contact 1:1 mapping, but Contact is the top-level entity in GHL.
Lead Opportunity (in Pipeline) Map Nutshell pipeline stages → GHL pipeline stages. One Lead = one Opportunity.
Lead Products Opportunity monetary value OR Custom Object GHL Opportunities carry a value but not itemized products. Sum values or create a Custom Object for line items.
Lead Source Contact Source field OR custom field Depends on your attribution model.
Activity (Call/Meeting) Note on Contact GHL doesn't have a native Activity log matching Nutshell's timeline. Import as Notes with a prefix (e.g., [CALL - 2025-03-15]).
Task Task on Contact Direct mapping available.
Tag Tag on Contact 1:1 mapping.
Custom Field Custom Field (matching type) Create custom fields in GHL before import.
Parent-Child Company Company + Custom Object association Requires Custom Objects for child entities.

Field-Level Mapping

Nutshell Field GHL Field Transform Required
Person → Name Contact → First Name / Last Name Split full name into components
Person → Email Contact → Email None
Person → Phone Contact → Phone Normalize to E.164 format
Person → Job Title Contact → Custom Field None
Person → Owner Contact → Assigned User Map Nutshell user IDs to GHL user IDs
Company → Name Company → Name None
Company → Phone Company → Phone Normalize to E.164
Company → Address Company → Address Map subfields (city, state, zip)
Company → Industry Company → Custom Field Create matching picklist values in GHL
Company → URL Company → Website None
Lead → Pipeline Opportunity → Pipeline Create matching pipelines in GHL first
Lead → Stage Opportunity → Stage Map stage names/IDs
Lead → Value Opportunity → Monetary Value None
Lead → Status Opportunity → Status Map: Open→open, Won→won, Lost→lost
Lead → Confidence Custom Field on Opportunity No native equivalent in GHL
Lead → Expected Close Date Custom Field on Opportunity No native close-date field on GHL Opportunities
Lead → Competitors Custom Field on Opportunity No native equivalent
Note → Body Note → Body Truncate to 5,000 chars for CSV; API has no limit
Tag Tag Create tags in GHL before import
Warning

Critical mapping decisions:

  1. Nutshell Leads with multiple People — GHL Opportunities link to a single Contact. Choose a primary contact per Opportunity, or create duplicate Opportunities (one per person).
  2. Lead Products — GHL Opportunities have a single monetary value, not an itemized product list. Either sum the product values or create a Custom Object for line items.
  3. Activities — Nutshell tracks calls, meetings, and emails on a shared timeline. GHL doesn't replicate this structure. Import as Notes with a standardized prefix to preserve context.
Info

Business Name automation gotcha: If you use GHL's Business Name automation to create companies from imported contacts, exact text matters. Misspellings or spacing differences will create wrong company associations and force cleanup. (help.gohighlevel.com)

Migration Architecture: Extract → Transform → Load

Extract Phase

Option A: Full CSV Export

From Nutshell, navigate to Company Settings → Export → Create Full Export. This generates a ZIP file containing separate CSVs for Companies, People, Leads, and other entities.

Limitations: Manual exports only include data visible in list views. Custom field data may require toggling columns. Activities and full email history cannot be bulk-exported via CSV — they must be extracted via API.

Option B: API Extraction

Use Nutshell's JSON-RPC or REST API to programmatically extract all records with full relationship data intact. Store raw JSON payloads in a local database (PostgreSQL or similar) to prevent re-querying Nutshell if the script fails.

# Extract all leads with related contacts and companies
def extract_all_leads(api_url, username, api_key):
    all_leads = []
    page = 1
    while True:
        result = fetch_nutshell_data(
            api_url, username, api_key,
            method="findLeads",
            params={"orderBy": "id", "orderDirection": "ASC",
                    "limit": 50, "page": page}
        )
        leads = result.get("result", [])
        if not leads:
            break
        for lead_stub in leads:
            full_lead = fetch_nutshell_data(
                api_url, username, api_key,
                method="getLead",
                params={"leadId": lead_stub["id"]}
            )
            all_leads.append(full_lead["result"])
        page += 1
        time.sleep(1)  # Respect rate limits on find requests
    return all_leads
# Incremental extraction via Nutshell REST API
curl -u user@example.com:API_KEY \
  'https://app.nutshell.com/rest/leads?filter[createdTime]=2026-03-01T00:00:00%20TO%202026-03-31T23:59:59'

Transform Phase

The transform layer is where your mapping decisions get implemented:

  1. Normalize phone numbers to E.164 format (GHL rejects non-standard formats)
  2. Split full names into first/last if Nutshell stores them as a single field
  3. Map picklist values — create a lookup table for Nutshell Industries → GHL custom field options
  4. Resolve multi-contact Leads — decide primary contact per Opportunity
  5. Build relationship maps — create a Company ID → Contact IDs → Lead IDs graph before loading
  6. Truncate notes to 5,000 characters for CSV import (API has no limit)
  7. Deduplicate — Nutshell allows People to exist under multiple Companies; decide if they become one Contact or multiple
  8. Map owners — build a Nutshell user ID → GHL user ID lookup table
  9. Build source-to-target ID crosswalk — essential for retries and validation

Load Phase

Load in dependency order:

  1. Companies → GHL Companies (store Nutshell Company ID → GHL Company ID mapping)
  2. People → GHL Contacts (store Nutshell Person ID → GHL Contact ID mapping)
  3. Associate Contacts to Companies in GHL
  4. Create Pipelines and Stages in GHL (if not already created)
  5. Leads → GHL Opportunities (using stored Contact ID mapping)
  6. Notes and Activities → GHL Notes on Contacts/Opportunities
  7. Tags → GHL Tags on Contacts
  8. Custom Object records (if applicable)

Use POST /contacts/upsert for idempotent contact creation, but only after duplicate settings are frozen. Use POST /opportunities/ to create deals after the contact exists. Use POST /contacts/{contactId}/notes for contact-linked notes. (marketplace.gohighlevel.com)

Script Outline

For teams building their own migration, here's the high-level structure:

# nutshell_to_ghl_migration.py — Outline
import requests
import json
import time
import logging
 
# --- Configuration ---
NUTSHELL_API_URL = "https://app.nutshell.com/api/v1/json"
NUTSHELL_USER = "your_email@company.com"
NUTSHELL_API_KEY = "your_nutshell_api_key"
GHL_API_BASE = "https://services.leadconnectorhq.com"
GHL_ACCESS_TOKEN = "your_ghl_token"
GHL_LOCATION_ID = "your_location_id"
 
# --- Phase 1: Extract from Nutshell ---
def extract_companies(): ...
def extract_people(): ...
def extract_leads(): ...
def extract_notes(entity_type, entity_id): ...
 
# --- Phase 2: Transform ---
def normalize_phone(phone: str) -> str:
    """Convert to E.164 format"""
    ...
 
def map_nutshell_lead_to_ghl_opportunity(lead, contact_id_map, pipeline_map):
    """Resolve primary contact, map stage, set monetary value"""
    ...
 
def split_name(full_name: str) -> tuple:
    """Split 'Jane Doe' into ('Jane', 'Doe')"""
    ...
 
# --- Phase 3: Load into GHL ---
def create_ghl_company(company_data): ...
def create_ghl_contact(contact_data): ...
def create_ghl_opportunity(opp_data): ...
def add_ghl_note(contact_id, note_body): ...
def add_ghl_tag(contact_id, tag): ...
 
# --- Phase 4: Validate ---
def compare_counts(): ...
def sample_field_checks(): ...
def report_missing_relationships(): ...
 
# --- Orchestrator ---
def run_migration():
    # 1. Extract
    companies = extract_companies()
    people = extract_people()
    leads = extract_leads()
    
    # 2. Load Companies → store ID mapping
    company_map = {}  # nutshell_id -> ghl_id
    for co in companies:
        ghl_co = create_ghl_company(transform_company(co))
        company_map[co["id"]] = ghl_co["id"]
    
    # 3. Load Contacts → store ID mapping
    contact_map = {}  # nutshell_person_id -> ghl_contact_id
    for person in people:
        ghl_contact = create_ghl_contact(transform_person(person))
        contact_map[person["id"]] = ghl_contact["contact"]["id"]
    
    # 4. Load Opportunities
    for lead in leads:
        opp_data = map_nutshell_lead_to_ghl_opportunity(
            lead, contact_map, pipeline_map
        )
        create_ghl_opportunity(opp_data)
    
    # 5. Load Notes, Tags
    # ... iterate and attach to correct contact/opportunity
    
    # 6. Validate
    compare_counts()
 
if __name__ == "__main__":
    run_migration()

This is an outline, not production code. A production script needs error handling, idempotency checks (don't re-create records on retry), progress checkpointing, dead-letter queuing for failed records, and comprehensive logging.

Edge Cases and Challenges

Multi-Contact Leads

Nutshell allows a Lead to be associated with multiple People and an Account. GHL requires a single Contact per Opportunity. You must define logic to select the primary Contact during transformation — typically the lead's primary Person or the most recently active stakeholder. Secondary contacts can be preserved as a note on the Opportunity or as tags.

Orphaned Records

Nutshell allows Leads without associated People. GHL Opportunities must be tied to a Contact. For these orphaned leads, create a placeholder Contact (e.g., "Unknown – [Company Name]") to hold the Opportunity and note its orphaned origin.

Duplicate Records

GHL deduplicates contacts by email or phone during CSV import. But Nutshell may have the same person listed under multiple companies. Decide upfront: one Contact with a single Company association, or accept some duplication. GHL's upsert behavior depends on location-level duplicate settings and can match email vs. phone with different priorities — if those point to different existing records, only one gets updated. (marketplace.gohighlevel.com)

Notes and Activity Migration

Nutshell activities (calls, meetings, tasks) live on a shared timeline across Companies, People, and Leads. GHL does not have an equivalent shared timeline. You have two options:

  1. Import as Notes — Loses the structured activity type but preserves the content and timestamp. Use a standardized prefix like [CALL - 2025-03-15] to maintain context.
  2. Import as Tasks — Only works for task-type activities; calls and meetings don't map cleanly.

GHL's CSV importer limits notes to one note per contact record, max 5,000 characters. If your contacts have multiple notes, you must use the API, which has no such limit.

Attachments

Nutshell allows file attachments on Companies, People, and Leads. GHL does not have a native file attachment field on Contacts. Options:

  • Store attachment URLs in a custom field
  • Upload to GHL's media library and link manually
  • Use a Custom Object with a file field
  • Archive externally and link via notes

Nutshell's full export ZIP does not include file binaries — only notes and structured data. You'll need the API to extract file references. (support.nutshell.com)

User Mapping

Historical activities tied to deactivated Nutshell users will fail to map unless you either assign them to an active GHL user or append the original author's name to the note body. Build a Nutshell user → GHL user lookup table before loading.

API Failures

GHL returns HTTP 429 when you hit the burst limit. Implement exponential backoff with jitter. Always log failed records with their Nutshell source IDs so you can retry selectively rather than re-running the entire migration.

Limitations and Constraints

Be honest with stakeholders about what GHL cannot replicate from Nutshell:

Nutshell Capability GHL Limitation
Native activity timeline (calls, meetings) No unified activity timeline; Notes and Tasks only
Lead-level product line items Opportunity has a single monetary value; no itemized products
Lead confidence percentage No native field; use a custom field
Expected close date on Leads No native field on Opportunities; use a custom field
Parent-child Company hierarchy Requires Custom Objects for child entities
Person linked to multiple Companies Contact linked to one Company at a time
SQL access (Enterprise) No equivalent; API or CSV export only
Unlimited custom fields per entity Custom Object fields limited to 10 unique fields per object
Notes on multiple entities simultaneously Notes link to one Contact and one Opportunity; no multi-entity linking (help.gohighlevel.com)
Native email/calendar sync per-user Available but implementation differs

Validation and Testing

Record Count Validation

After each phase, compare:

Entity Nutshell Count GHL Count Delta Status
Companies X X 0
Contacts/People X X 0
Opportunities/Leads X X 0
Notes X X ≤ 2% ⚠️

Allow a 2–3% variance on notes and activities due to deduplication or character-limit truncation.

Field-Level Spot Checks

Sample 20–30 records across each entity type and verify:

  • Custom field values transferred correctly
  • Phone numbers are in E.164 format
  • Opportunities are in the correct Pipeline and Stage with the correct monetary value
  • Contact → Company associations are intact
  • Tags are present
  • Notes contain the expected content (check for truncation)
  • Owner/assigned user mappings are correct

UAT Process

  1. Migrate to a GHL sandbox/test sub-account first
  2. Have 2–3 end users verify their own records and pipelines
  3. Run a sample workflow to confirm automations trigger correctly on migrated data
  4. Document discrepancies and remediate before production migration

Rollback Plan

GHL CSV imports cannot be undone with a single click. Your rollback options:

  • Tag all imported records with a migration batch tag (e.g., migration-batch-1) so you can bulk-delete if needed
  • Keep Nutshell active until GHL is fully validated — run both systems in parallel for 1–2 weeks
  • Export a GHL backup before migration starts (if the account has existing data)
  • Maintain the source-to-target ID crosswalk so you can delete and reload selectively rather than starting from scratch

Post-Migration Tasks

Rebuild Automations

Nutshell's sales automations and pipeline logic do not transfer to GHL. You'll need to:

  • Recreate pipeline stages in GHL's Pipeline builder
  • Rebuild automated email sequences as GHL Workflows
  • Set up GHL-native triggers (form submissions, tag additions, stage changes)
  • Configure SMS and email templates
  • Recreate DND and subscription logic — GHL's CSV import applies DND across all channels; if you need channel-specific logic, use workflows or tags (help.gohighlevel.com)

Domain Warmup

If you're using GHL for email marketing, configure your sending domains and begin warming them up to avoid deliverability drops.

User Training

GHL's interface and mental model differ from Nutshell's. Train on:

  • Finding contacts and opportunities (search, Smart Lists, filters)
  • Working with Pipelines and Stages
  • Using Workflows for automation
  • The Contact-centric data model vs. Nutshell's Account-centric model
  • One-company-per-contact rules and note association behavior

Monitor for Inconsistencies

For 2–4 weeks post-migration:

  • Spot-check records reported as "missing" by users
  • Verify workflow triggers are firing correctly
  • Watch for duplicate contacts created by ongoing integrations (Zapier, forms, etc.)
  • Check for unmapped custom fields and pipeline-stage drift

Best Practices

  1. Back up everything before migration. Export a full ZIP from Nutshell and store it outside both platforms.
  2. Run at least one test migration on a GHL sandbox sub-account with production-like data.
  3. Validate incrementally — don't wait until the end to check counts and field values.
  4. Create all custom fields, pipelines, stages, and tags in GHL before importing data. GHL's importers won't create custom fields on the fly. (help.gohighlevel.com)
  5. Normalize phone numbers to E.164 before import. GHL rejects malformed phone numbers silently during CSV import.
  6. Use the API for notes migration — CSV limits you to one note per contact (5,000 chars). The API has no such limit.
  7. Tag every migrated record with a batch identifier for rollback and audit purposes.
  8. Keep Nutshell live during validation. Don't cancel your Nutshell subscription until your team has confirmed the GHL data is complete and correct.
  9. Preserve original Nutshell IDs in a GHL custom field or an external manifest for traceability.
  10. Freeze GHL duplicate settings before running any imports or API loads. Changing them mid-migration will produce inconsistent results.

When to Use a Managed Migration Service

Build in-house when:

  • Your dataset is small and flat (< 2K contacts, no multi-level relationships)
  • You have engineering bandwidth with CRM API experience
  • The migration is straightforward enough for CSV

Hire a managed service when:

  • Multi-level relationships need to survive (Company → Person → Lead chains)
  • Custom Objects are involved on either side
  • Activity history preservation matters
  • Engineering time is more expensive than a fixed project fee — 40–80 hours at $100–200/hour is $4,000–$16,000 in loaded cost
  • Downtime is not an option — your sales team needs to keep working in Nutshell until GHL is validated
  • You're an agency migrating multiple client accounts with different data structures

At ClonePartner, we handle the data layer so your team can focus on rebuilding automations and closing deals:

  • We programmatically rebuild Account → Contact → Opportunity relationships inside GHL
  • We handle GHL's 100 req/10s burst limit with built-in retry logic — zero dropped records
  • We adapt to your GHL plan and data structure (Custom Objects vs. Custom Fields)
  • We run migrations with zero downtime — your sales team works in Nutshell until GHL is fully validated
  • Most migrations complete in days, not weeks

The decision isn't "can we do this ourselves?" — it's "should we spend engineering cycles on a one-time migration, or ship product instead?"

Frequently Asked Questions

Can I migrate from Nutshell to GoHighLevel using CSV export?
Yes, but CSV flattens all relational data — Company → Person → Lead links are lost. GHL's CSV importer also limits notes to one per contact (5,000 chars max) and caps file size at 30 MB. CSV works for small, flat contact lists under ~2,000 records. For anything with multi-level relationships or activity history, use the API or a managed service.
What are GoHighLevel's API rate limits for migration?
GHL's V2 API enforces a burst limit of 100 requests per 10 seconds and a daily limit of 200,000 requests, both per Marketplace app per resource (Location or Company). Implement exponential backoff with jitter to handle 429 responses. For a typical migration of 20,000 contacts with opportunities and notes, expect 75,000–150,000 total API calls.
How do Nutshell Leads map to GoHighLevel?
Nutshell Leads (which serve as both leads and opportunities) map to GoHighLevel Opportunities inside Pipelines. The key difference: Nutshell Leads can link to multiple People and Companies, while GHL Opportunities link to a single Contact. You must choose a primary contact per Opportunity and create matching Pipelines and Stages in GHL before importing.
Does GoHighLevel support Custom Objects on all plans?
Yes. Current GHL support docs confirm Custom Objects are available on every plan (Starter, Unlimited, Pro) — up to 10 Custom Objects per sub-account. Earlier references to Custom Objects being Pro-only are outdated. Each object supports up to 10 unique fields and relationships to Contacts, Companies, Opportunities, and other Custom Objects.
How long does a Nutshell to GoHighLevel migration take?
A small CSV-based migration (under 2,000 flat contacts) takes a few hours. A custom API script for 5,000–50,000 records with relationship preservation typically requires 40–80+ hours of engineering time over 2–4 weeks. A managed migration service like ClonePartner typically completes in days, including validation and testing.

More from our Blog

Salesforce to GoHighLevel Migration: The Technical Guide
GoHighLevel/Salesforce/Migration Guide/Salesforce Service Cloud

Salesforce to GoHighLevel Migration: The Technical Guide

A technical guide to migrating from Salesforce to GoHighLevel: object mapping, API rate limits, custom object constraints, and step-by-step ETL architecture.

Raaj Raaj · · 21 min read