Skip to content

SolarWinds Service Desk to Salesforce Service Cloud Migration Guide

Technical guide to migrating from SolarWinds Service Desk to Salesforce Service Cloud — covering API extraction, data model mapping, load order, and common pitfalls.

Raaj Raaj · · 22 min read
SolarWinds Service Desk to Salesforce Service Cloud Migration Guide
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

SolarWinds Service Desk to Salesforce Service Cloud Migration Guide

Migrating from SolarWinds Service Desk (SWSD) to Salesforce Service Cloud means converting an ITIL-centric service management platform — built on the Samanage data model — into Salesforce's object-relational architecture centered on Cases, Accounts, Contacts, and the Lightning file system. This is not a lift-and-shift. SWSD stores incidents, problems, changes, releases, assets, CMDB configuration items, users, groups, and knowledge articles in a flat REST-accessible model with shallow nesting. Salesforce stores support data across a deep object graph: Account → Contact → Case → EmailMessage → ContentDocumentLink → ContentVersion. Every record relationship must be explicitly rebuilt using external ID matching.

Info

Quick answer: This migration is medium complexity for incident-only moves (2–3 weeks) and high complexity when CMDB, change management, and knowledge base are in scope (4–8+ weeks). The hardest part is not the data transfer — it's the data model conversion. SWSD's incident-comment-attachment model must be decomposed into Salesforce's Case + CaseComment/EmailMessage + ContentVersion structure, with all parent-child relationships rebuilt using external ID matching. Automation rules, SLA policies, and workflow triggers cannot be migrated — they must be rebuilt in Salesforce Flow, Entitlements, and Milestones. Use Bulk API 2.0 for record loading (supporting up to 100 million records per 24-hour period), and always rely on External IDs for relationship mapping to avoid needing Salesforce 18-character ID lookups at load time.

The proven pattern: extract from SWSD via the Samanage REST API, transform and stage in an intermediate data store, load into Salesforce using Bulk API 2.0, rebuild relationships via external ID matching, then run a delta sync for records created during the migration window.

Why Teams Migrate from SolarWinds Service Desk to Salesforce Service Cloud

The move is typically driven by platform consolidation and capability gaps, not dissatisfaction with basic ticketing. While teams looking for a simpler external support model might migrate from SolarWinds Service Desk to Enchant, enterprises move to Salesforce to unify their CRM and support data. When sales, account management, and external customer support are already on Salesforce, keeping internal IT or specialized support in a siloed SWSD instance creates visibility gaps and redundant data syncing.

Common drivers:

  • CRM unification. Organizations already running Salesforce Sales Cloud want support data in the same org. Having Cases, Accounts, Contacts, and Opportunities in one platform eliminates cross-system syncing and gives agents full customer context without switching tools. A customer issue logged by Sales can transition to an IT incident without webhook integrations.
  • Advanced case routing. SWSD supports basic assignment rules. Salesforce Omni-Channel provides skills-based routing, capacity-based assignment, and presence-aware distribution across channels (email, chat, phone, messaging).
  • Entitlement and SLA management. SWSD has SLA management, but Salesforce Entitlements and Milestones offer multi-tier SLA structures with per-customer and per-product entitlement rules — critical for B2B support organizations managing different contractual SLAs per account.
  • Automation depth. Salesforce Flow supports complex branching, subflows, scheduled paths, and platform events — substantially beyond SWSD's trigger-action rule model. Einstein for Service adds case classification, recommended replies, and knowledge suggestions natively without additional configuration.
  • Custom object extensibility. While SWSD allows custom fields, Salesforce allows entirely custom relational objects with their own page layouts, validation rules, and API access — making it easier to build bespoke asset tracking or compliance modules that integrate natively with Cases.
  • Reporting consolidation. Teams want support metrics in the same reporting layer as sales and marketing data. Salesforce Reports, Dashboards, and CRM Analytics provide this without third-party BI connectors.
  • Scale constraints. SWSD works well for small-to-mid IT teams. Organizations growing past several hundred agents or managing external customer support alongside internal ITSM often hit workflow and customization ceilings.

Data Model Mapping: ITSM to Service Cloud

Understanding the structural gap between these two platforms is the single most important step before writing any migration code. Salesforce Service Cloud is historically a Case management system built for customer support. The Salesforce Customer Service Incident Management (CSIM) feature — generally available as of Spring '23 (API version 57.0) and included with Service Cloud licenses at no extra cost — adds native Incident, Problem, and ChangeRequest standard objects for organizations requiring strict ITIL object separation. Most migrations still land on Cases because the Case object has deeper ecosystem support across Entitlements, Omni-Channel, and AppExchange integrations.

SWSD Object Salesforce Equivalent Notes
Incident Case (or CSIM Incident) Direct map. SWSD state machine → Case Status picklist. CSIM Incident object (API name: Incident) provides native ITIL object separation.
Problem Case (RecordType: Problem) or CSIM Problem CSIM Problem object (API name: Problem) links to Incidents via ProblemIncident junction. Without CSIM, use a RecordType.
Change Case (RecordType: Change) or CSIM ChangeRequest CSIM ChangeRequest object (API name: ChangeRequest). Approval workflows must be rebuilt in Salesforce Approval Processes or Flow.
Release No native equivalent Archive or use a custom object.
User (requester) Contact SWSD users who submit tickets map to Contacts linked to Accounts.
User (agent/technician) User Salesforce Users require licenses. Map agent roles to Profiles and Permission Sets.
Group Queue SWSD groups map to Salesforce Queues for case ownership and routing.
Category / Subcategory Case picklist fields Map to Type, Reason, or a custom Category__c picklist.
Comment (on incident) CaseComment or EmailMessage Critical decision point — see section below.
Attachment ContentVersion + ContentDocumentLink Salesforce Lightning file model. Files must be uploaded as ContentVersion, then linked via ContentDocumentLink.
Knowledge Article KnowledgeArticleVersion Requires Knowledge to be enabled. Category mappings differ significantly.
Asset Asset Salesforce has a native Asset object, but it's designed for customer-facing product tracking, not ITSM configuration management.
Configuration Item Asset or custom object SWSD CMDB CI types with deep hierarchies need custom objects in Salesforce.
SLA Policy Entitlement + Milestone Cannot be migrated. Must be rebuilt manually in Setup.
Automation Rule Flow / Assignment Rule Cannot be migrated. Must be rebuilt.
Service Catalog Item No direct equivalent Use custom objects or integrate with Experience Cloud for self-service.
Warning

Case vs. CSIM Incident decision: If your SWSD instance was primarily used for external customer support or general HR/Facilities ticketing, the Case object is the better fit — it natively integrates with Salesforce Entitlements, Milestones, and Omni-Channel routing. If you require strict ITIL object separation with native Incident, Problem, and ChangeRequest objects, evaluate CSIM (available on Service Cloud licenses from API v57.0 onward). The practical tradeoff: CSIM objects have less AppExchange ecosystem coverage and fewer native integrations than Case as of 2025. Most migrations land on Cases with Record Types for ITIL differentiation.

The CaseComment vs. EmailMessage Decision

This is where most SolarWinds-to-Salesforce migrations silently lose data.

CaseComment stores plain text only. The standard CommentBody field has a 4,000-character limit (Salesforce CaseComment object reference). If your SWSD incident comments contain HTML formatting, inline images, or long technical notes, CaseComment will truncate or strip that content without warning during the load.

EmailMessage supports HTML via the HtmlBody field, accommodating up to 131,072 characters for inbound email bodies (Salesforce EmailMessage object reference). Set Incoming = true for customer messages and Incoming = false for agent replies. This preserves rich formatting, inline images, and long messages. The trade-off: EmailMessage requires Email-to-Case to be enabled in your org, and it adds structural complexity to the load.

Recommendation: Use EmailMessage for all public-facing correspondence and CaseComment only for internal notes. This prevents silent data truncation and keeps the Case Feed readable. If you use CaseComment for everything, expect data loss on any comment exceeding 4,000 characters — and SWSD incident descriptions regularly exceed this in technical environments.

Mapping Users and Requesters

SWSD Requesters must be mapped to Salesforce Contact records, while SWSD Assignees (Agents) map to Salesforce User records.

Warning

Constraint: You cannot assign a Case to a Salesforce Contact. The OwnerId field accepts only a Salesforce User ID or Queue ID. Ensure you have provisioned active Salesforce User licenses for all historical agents you wish to retain as owners, or map inactive agents to a generic "Legacy Agent" User record to preserve historical assignment data. SWSD users who are both requesters and agents require deduplication logic — if you create both a Contact and a User record for the same person, ensure they are linked via the Contact field on the User object to avoid data fragmentation.

How to Export Data from SolarWinds Service Desk

SWSD offers two extraction paths: CSV export from the UI and the Samanage REST API. The Samanage REST API is documented at https://api.samanage.com/.

CSV Export (UI)

You can export incidents, problems, changes, users, and assets to CSV directly from the index pages. This works for quick audits but has hard limits:

  • No relational context — you get flat rows, not parent-child links
  • Comments and attachments are not included in CSV exports
  • Cells with over 32,767 characters cause misaligned rows in Excel
  • No attachment binary data

CSV export is useful for record counts and field inventory. It is not a reliable migration source.

The API is the only reliable extraction path for a complete migration. Key constraints:

  • Base URL: https://api.samanage.com/
  • Authentication: Bearer token generated from the SWSD Admin portal under API Settings. Tokens are scoped to admin-level accounts only.
  • Pagination: Page-based, maximum 200 records per page. No cursor-based pagination. Use page and per_page query parameters.
  • Rate limits: Not publicly documented by SolarWinds. In practice, throttle to 2–3 requests per second and implement exponential backoff on 429 responses. This is an empirical safe threshold, not a guaranteed limit.
  • API access tiers: API access requires the SWSD Advanced or Premier plan. Confirm your license level before starting extraction.

Core endpoints for extraction:

GET /incidents.json?per_page=200&page=1
GET /incidents/{id}/comments.json
GET /incidents/{id}/attachments.json
GET /problems.json?per_page=200&page=1
GET /changes.json?per_page=200&page=1
GET /users.json?per_page=200&page=1
GET /groups.json
GET /hardwares.json           # Assets
GET /configuration_items.json # CMDB CIs
GET /solutions.json           # Knowledge articles
GET /categories.json
Tip

Incremental extraction: Use the updated_at filter to pull only records modified after your last extraction run. This is essential for the delta sync before cutover: GET /incidents.json?per_page=200&updated_at_gte=2026-07-01T00:00:00Z

Extraction Script Pattern

import requests
import time
import json
 
BASE_URL = "https://api.samanage.com"
TOKEN = "your-bearer-token"
HEADERS = {
    "X-Samanage-Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json"
}
 
def extract_all(endpoint, params=None):
    records = []
    page = 1
    while True:
        resp = requests.get(
            f"{BASE_URL}/{endpoint}",
            headers=HEADERS,
            params={"per_page": 200, "page": page, **(params or {})}
        )
        if resp.status_code == 429:
            time.sleep(int(resp.headers.get("Retry-After", 5)))
            continue
        resp.raise_for_status()
        batch = resp.json()
        if not batch:
            break
        records.extend(batch)
        page += 1
        time.sleep(0.4)  # ~2.5 req/sec
    return records
 
incidents = extract_all("incidents.json")
users = extract_all("users.json")

For each incident, you need a second pass to extract comments and attachments by incident ID — approximately 2–3 API calls per incident for full data extraction. On a dataset of 50,000 incidents, that yields roughly 150,000 API calls. At 2.5 requests/second with a 0.4-second sleep, extraction runs approximately 16–17 hours. Budget accordingly.

For large datasets (100,000+ records), offset pagination degrades in performance as page numbers increase because the API must skip progressively more records server-side. To mitigate this, use time-windowed extraction: query records in date-range chunks using created_at or updated_at filters to keep page numbers low throughout the run.

Preparing Salesforce for Ingestion

Before loading a single record into Salesforce, you must prepare the environment. Skipping this phase causes governor limit exceptions, API timeouts, invalid cross-reference errors, and corrupted data that is difficult to clean up after the fact.

Create External ID Fields

The key to maintaining referential integrity across the load sequence is external ID fields. Create these custom fields before migration begins and mark each as both External ID and Unique in Salesforce Setup:

Salesforce Object External ID Field Source Value
Account SWSD_Site_Id__c SWSD site ID
Contact SWSD_User_Id__c SWSD user ID (requesters)
Case SWSD_Incident_Id__c SWSD incident ID
Case SWSD_Problem_Id__c SWSD problem ID (if using RecordTypes)
Asset SWSD_Asset_Id__c SWSD hardware/asset ID

External ID fields allow you to use the Salesforce UPSERT operation and resolve relationships (such as assigning a Case to a Contact) using the SWSD source ID in your CSV, rather than querying for newly generated Salesforce 18-character record IDs. During the delta sync, UPSERT against the external ID ensures records are created or updated correctly without duplicates. Without external IDs, you must perform two-pass loading: insert records in one batch, query back their Salesforce IDs, then insert child records referencing those IDs — a significantly slower and more error-prone process.

Enable Audit Field Permissions

Danger

CreatedDate preservation: By default, Salesforce sets CreatedDate to the timestamp of record insertion, not the original record creation date. To preserve original creation dates, you must enable "Set Audit Fields upon Record Creation" in Setup → User Interface. This is a per-profile permission that must be activated before the data load begins. If you forget this step, every migrated record will reflect the migration date rather than its original date — and correcting this requires a complete data reload. Confirm this permission is active before running any load job.

Disable Automations Before Loading

Salesforce evaluates every inserted record against all active automations. Loading 100,000 historical tickets with active Flows and email alerts will fire those automations 100,000 times — potentially sending thousands of unwanted "Case Created" notifications to customers.

Before loading, temporarily disable:

  • Apex Triggers (deactivate via Setup → Apex Triggers)
  • Record-Triggered Flows (deactivate via Setup → Flows)
  • Workflow Rules
  • Validation Rules (historical data frequently violates current business rules that didn't exist when records were created)
  • Email Deliverability (set to "No Access" in Setup → Deliverability)

Loading Data into Salesforce via Bulk API 2.0

For any volume over 2,000 records, use Bulk API 2.0. It is designed for asynchronous batch processing and handles internal chunking automatically. The Salesforce Bulk API 2.0 developer documentation covers job creation, status polling, and result retrieval in detail (Salesforce Bulk API 2.0 Guide).

Required Load Order

Data must be loaded in dependency order to satisfy foreign key constraints. Loading out of sequence generates INVALID_CROSS_REFERENCE_KEY errors that require re-processing entire batches:

  1. Accounts — Create Account records for organizations. Map SWSD site or department to Account.
  2. Contacts — Create Contact records linked to Accounts. Map SWSD requester users. Store the SWSD user ID in SWSD_User_Id__c.
  3. Users — Provision Salesforce Users for agents/technicians. Requires licenses; cannot be done via Bulk API — use standard User provisioning through Setup or the SOAP-based User object.
  4. Queues — Create Queues to match SWSD groups. This is a Setup operation, not a data load.
  5. Assets/CIs — Load CMDB data.
  6. Cases — Load Cases with ContactId, AccountId, and OwnerId references resolved via external ID matching.
  7. CaseComments / EmailMessages — Load child records linked to Cases via ParentId.
  8. ContentVersion + ContentDocumentLink — Upload file binaries via REST, then insert ContentDocumentLink records to attach them to Cases.
  9. Knowledge Articles — Load via the Knowledge REST API if in scope.

Bulk API 2.0 Limits Reference

Limit Value
Internal batch size 10,000 records per batch (auto-chunked)
Daily batch submissions 15,000 per 24 hours (shared with Bulk API 1.0)
Daily record volume Up to 100 million records per 24-hour period
Max job file size 150 MB per job (CSV format)
Max concurrent open jobs 25

Source: Salesforce Bulk API 2.0 Limits

Preventing Row Lock Errors

When inserting Cases that reference the same parent Account or Contact, Salesforce locks the parent record to recalculate roll-up summary fields. If multiple parallel Bulk API batches insert Cases linked to the same Account concurrently, you will encounter UNABLE_TO_LOCK_ROW errors on some records.

Prevention strategies:

  1. Sort your CSV files by AccountId or ContactId (the external ID column) before upload. This causes Salesforce to process Cases sharing a parent in the same internal batch, reducing inter-batch contention.
  2. If sorting is insufficient for your data distribution, switch the Bulk API job from Parallel to Serial processing mode by setting lineEnding: CRLF and concurrencyMode: Serial in the job creation request. Serial mode eliminates row lock contention at the cost of significantly slower throughput — use it as a last resort.

SOQL Validation Queries

After each load phase, verify record counts before proceeding to the next phase:

-- Verify Cases loaded
SELECT COUNT() FROM Case WHERE SWSD_Incident_Id__c != null
 
-- Verify Contact relationships resolved
SELECT COUNT() FROM Case WHERE SWSD_Incident_Id__c != null AND ContactId != null
 
-- Identify Cases with missing Contact references (load failures)
SELECT SWSD_Incident_Id__c, Subject, CreatedDate
FROM Case
WHERE SWSD_Incident_Id__c != null AND ContactId = null
LIMIT 200
 
-- Verify CaseComments loaded
SELECT COUNT() FROM CaseComment WHERE Parent.SWSD_Incident_Id__c != null
 
-- Verify attachments linked
SELECT COUNT() FROM ContentDocumentLink
WHERE LinkedEntityId IN (SELECT Id FROM Case WHERE SWSD_Incident_Id__c != null)

Field Mapping: Incidents to Cases

Every field requires an explicit mapping decision. Below is the core mapping table for SWSD Incidents to Salesforce Cases:

SWSD Incident Field Salesforce Case Field Transformation Notes
id SWSD_Incident_Id__c External ID. Integer → Text. Max 255 chars in Salesforce.
name (title) Subject Direct map. Max 255 chars. Truncate if needed.
description Description Long Text Area. Max 32,000 chars. Strip or preserve HTML per org policy.
state Status Value mapping required. See picklist note below.
priority Priority Value mapping: Critical/High/Medium/Low → Salesforce standard picklist values.
assignee OwnerId Map to Salesforce User or Queue ID. Requires user provisioning before Case load.
requester ContactId Resolve to Contact via SWSD_User_Id__c external ID.
category Type or Category__c Custom picklist recommended for multi-level categories.
subcategory Subcategory__c Custom picklist. Define all values before load.
number Legacy_Ticket_Number__c Store SWSD ticket number in a custom field. Salesforce auto-generates its own CaseNumber.
created_at CreatedDate Requires "Set Audit Fields upon Record Creation" permission — see above.
updated_at LastModifiedDate Same audit field permission required.
due_at Due_Date__c Custom DateTime field.
site AccountId Map SWSD site → Salesforce Account via SWSD_Site_Id__c external ID.
Warning

Picklist value mismatches: SWSD allows freeform text values in some fields (category, state). Salesforce picklist fields reject values not defined in the picklist configuration — records with unmapped values will either fail insertion or silently null the field depending on your API error handling settings. Build a complete value mapping table before your first load attempt. Pay particular attention to SWSD custom states such as "Pending Vendor," "Awaiting Parts," or "Escalated" — none of these exist in the Salesforce default Status picklist and must be added to the picklist definition in Setup before loading.

Handling CMDB and Assets

SWSD has a proper CMDB with configuration items organized in hierarchical types — servers, workstations, switches, software licenses — with defined relationships between CIs, incidents, problems, and changes.

Salesforce has a native Asset object, but it is designed for customer-facing product tracking (serial numbers, warranties, account relationships), not IT configuration management. The structural mismatch is significant:

SWSD CMDB Concept Salesforce Option Tradeoff
CI types with custom attributes Asset with custom fields per Record Type Works for ≤5 CI types with shallow attributes
CI-to-CI parent/child hierarchy Asset ParentId lookup Single-level hierarchy only in standard model
CI-to-CI dependency relationships Custom junction object Requires custom development
CI-to-incident links AssetId lookup on Case Native Case-to-Asset relationship available
Spatial/location tracking Location and AssociatedLocation objects Native Salesforce objects, available on Service Cloud

Decision guidance:

  • Fewer than 5 CI types, no deep hierarchies: Use the native Asset object with Record Types for Hardware, Software, and Network Device. Add custom fields for SWSD-specific attributes. This is a data migration, not a development project.
  • 5–10 CI types with parent/child relationships: Use Asset with custom fields and the ParentId field for hierarchy. Plan for 2–4 weeks of configuration work beyond the core migration.
  • 10+ CI types with multi-level dependencies or cross-CI relationship mapping: Require custom Salesforce objects. This pushes the engagement from a data migration into a platform design project. Scope and timeline separately.

For organizations with complex CMDB requirements, also evaluate whether Salesforce is the right long-term home for CMDB data. Many teams migrate incidents and knowledge to Salesforce while maintaining CMDB in a dedicated tool (ServiceNow, Device42, Lansweeper) with a lightweight API integration for CI-to-Case lookups, or they bypass Salesforce entirely and migrate from SolarWinds Service Desk to ServiceNow for unified ITSM.

Knowledge Base Migration

SWSD knowledge articles (called "Solutions") map to Salesforce Knowledge articles (KnowledgeArticleVersion). The Salesforce Knowledge REST API handles article creation and publication (Salesforce Knowledge REST API Reference).

Migration steps:

  1. Enable Salesforce Knowledge in Setup → Knowledge Settings. This is a one-time, irreversible org configuration.
  2. Create article Record Types on KnowledgeArticleVersion matching your SWSD knowledge categories.
  3. Map fields: SWSD titleTitle, body → custom Rich Text Area field, category → Salesforce Data Category assignments.
  4. Create articles via REST API:
POST /services/data/v62.0/sobjects/Knowledge__kav
Content-Type: application/json

{
  "Title": "Article Title",
  "UrlName": "article-url-name",
  "Summary": "Short description",
  "Body__c": "<p>Article HTML content</p>"
}
  1. Publish articles by calling the Knowledge publish action:
POST /services/data/v62.0/knowledgeManagement/articleVersions/masterVersions/{articleId}
Content-Type: application/json

{"publishStatus": "Online"}

Note: The Knowledge API requires the "Manage Articles" permission in addition to standard API access. Verify this is enabled for your integration user before starting the knowledge migration phase.

Info

HTML content sanitization: SWSD article bodies frequently contain HTML. Salesforce Knowledge Rich Text fields support a subset of HTML tags — unsupported tags (<script>, <iframe>, <style>, custom CSS classes) are silently stripped on save, which can break article formatting. Run a sanitization pass using an HTML parser (Python's bleach library or equivalent) to strip unsupported tags and convert inline styles to Salesforce-compatible equivalents before loading. Test a representative 50-article sample against your org before running the full knowledge migration.

Migrating Attachments

Attachment migration is consistently the most complex and time-consuming phase of a help desk migration. SWSD stores attachment binaries in Amazon S3 and provides expiring download URLs via the API response payload.

Salesforce handles files using a three-object architecture (Salesforce Files and Content documentation):

  1. ContentVersion — The actual file binary and version metadata.
  2. ContentDocument — The parent container for all ContentVersion records (auto-generated on ContentVersion insert, cannot be created directly).
  3. ContentDocumentLink — The junction object linking the ContentDocument to a specific Case, Incident, or other record.

The Attachment Migration Workflow

For each SWSD attachment:
1. Parse attachment URL from SWSD API response
2. Download binary to local/staging storage
3. POST to Salesforce ContentVersion (REST API, multipart form)
4. Query ContentDocumentId from newly created ContentVersion
5. INSERT ContentDocumentLink (LinkedEntityId = Case Salesforce ID, ContentDocumentId from step 4)
import requests
 
SF_BASE = "https://yourorg.my.salesforce.com"
SF_TOKEN = "your-salesforce-access-token"
SF_HEADERS = {"Authorization": f"Bearer {SF_TOKEN}"}
 
def upload_attachment(file_path, file_name, case_id):
    # Step 1: Upload file as ContentVersion
    with open(file_path, "rb") as f:
        response = requests.post(
            f"{SF_BASE}/services/data/v62.0/sobjects/ContentVersion/",
            headers=SF_HEADERS,
            files={"entity_content": (
                None,
                '{"Title":"' + file_name + '","PathOnClient":"' + file_name + '"}',
                "application/json"
            ), "VersionData": (file_name, f, "application/octet-stream")}
        )
    content_version_id = response.json()["id"]
 
    # Step 2: Get the auto-generated ContentDocumentId
    cv = requests.get(
        f"{SF_BASE}/services/data/v62.0/sobjects/ContentVersion/{content_version_id}",
        headers=SF_HEADERS,
        params={"fields": "ContentDocumentId"}
    ).json()
    content_document_id = cv["ContentDocumentId"]
 
    # Step 3: Link to Case
    requests.post(
        f"{SF_BASE}/services/data/v62.0/sobjects/ContentDocumentLink/",
        headers={**SF_HEADERS, "Content-Type": "application/json"},
        json={"ContentDocumentId": content_document_id, "LinkedEntityId": case_id,
              "ShareType": "V"}
    )
Warning

ContentVersion constraint: Binary file uploads cannot use Bulk API. Each attachment must be uploaded individually via the REST API. For 10,000 attachments averaging 500KB each, budget 3–5 hours of upload time at typical API throughput. This is frequently the longest phase of the entire migration and is commonly underestimated by 2–3x.

Danger

Salesforce file storage costs: Salesforce file storage is allocated at 10GB per org plus 2GB per full user license, with additional storage purchased at approximately $5 per GB per month (pricing as of 2025). Audit your SWSD attachment inventory before migrating. Filter out attachments under 10KB, common image types used as email signatures (logo.png, signature.jpg, banner images), and files older than your data retention policy. For a dataset with 500GB of attachments, storage costs alone can exceed $2,500/month if migrated without filtering.

What Cannot Be Migrated

This list is as important as the mapping table. These items must be manually rebuilt in Salesforce before go-live:

Cannot Migrate Must Rebuild As
SWSD automation rules and triggers Salesforce Flow (Record-Triggered) or Apex triggers
SLA policies Salesforce Entitlements, Milestones, and Entitlement Processes
Service catalog items Custom objects + Screen Flows, or Experience Cloud self-service portal
Change approval workflows Salesforce Approval Processes
Dashboard layouts and saved reports Salesforce Reports and Dashboards (rebuild from scratch)
Email notification templates Salesforce Email Templates (Classic or Lightning)
User passwords and SSO configuration Reconfigure SSO in Salesforce Setup; credentials do not transfer

Budget the automation rebuild work separately from the data migration effort. For a SWSD instance with 50+ active automation rules, expect 2–4 additional weeks of Flow development and testing work.

Delta Sync and Cutover

A zero-downtime migration requires a delta sync strategy. You cannot freeze IT support operations for the duration of the full data load.

Executing the Delta

  1. Initial Load: Extract all data up to a defined cutoff timestamp (e.g., Friday at 5:00 PM UTC) and load it into Salesforce over the weekend. SWSD remains live for agents throughout.
  2. Freeze: On cutover night, set SWSD to read-only or restrict agent access to stop new record creation.
  3. Delta Extraction: Query the SWSD API using updated_at_gte for all records modified since the initial load cutoff timestamp.
  4. Upsert: Push delta records into Salesforce using the UPSERT operation against your external ID fields. This creates new tickets logged during the freeze window and updates the status of tickets modified since the initial load.
  5. Final Validation: Run SOQL record count comparisons (see queries in the load section above) and spot-check 50+ records for data fidelity.
  6. DNS/Routing Cutover: Update MX records or email routing to redirect support@company.com from SWSD Email-to-Case to Salesforce Email-to-Case.
  7. Enable Salesforce for agents and re-enable automations, Flows, and Validation Rules.

Post-Migration Validation Checklist

Once the data is in Salesforce, validate before re-enabling automations and granting agent access:

  • Count verification: Total records per object in SWSD must match totals in Salesforce. Use the SOQL queries in the load section above.
  • State mapping check: Verify SWSD custom states (e.g., "Pending Vendor") mapped correctly to Salesforce Case Status picklist values.
  • Comment content check: Spot-check at least 50 records across different time periods. Verify comment content is not truncated, particularly if CaseComment was used for any content over 3,500 characters.
  • Attachment accessibility: Open attachments on older closed tickets to confirm files are accessible and not corrupted. Verify ContentDocumentLink.ShareType is set correctly.
  • Timestamp verification: Confirm CreatedDate values on migrated Cases reflect original SWSD creation dates, not the migration run date.
  • Owner assignment verification: Confirm OwnerId references resolved correctly for both active User records and Queues. Cases with failed owner lookups default to the running user — identify these with: SELECT Id, CaseNumber, SWSD_Incident_Id__c FROM Case WHERE OwnerId = 'your-migration-user-id'
  • Re-enable automations only after all validation checks pass.

Common Failure Modes

These are the specific issues that most frequently derail SolarWinds-to-Salesforce migrations:

  • Loading records out of dependency order. Creating Cases before Contacts exist breaks the ContactId lookup and generates INVALID_CROSS_REFERENCE_KEY errors. Salesforce either rejects the record or nulls the relationship depending on your allOrNone setting. Always load in the sequence defined above.
  • Ignoring the 4,000-character CaseComment limit. SWSD comments longer than 4,000 characters are silently truncated. Validate comment lengths during extraction and route long comments to EmailMessage instead.
  • Forgetting audit field permissions before the load. Every record gets the migration run date as CreatedDate. You only discover this post-load, and correcting it requires re-running the entire migration from scratch.
  • Underestimating attachment upload time. Binary uploads to ContentVersion are single-threaded via REST API and cannot use Bulk API. Underestimating this phase by 2–3x is the most common timeline failure.
  • Unmapped picklist values causing silent nulls. SWSD allows freeform text in some fields. Build a complete value mapping table and define all custom picklist values in Salesforce Setup before loading. Use the Bulk API results file to identify rows where the field was accepted but silently nulled.
  • Duplicate Contact records from user-requester overlap. SWSD users who are both ticket requesters and agents need deduplication logic at extraction time. If you create both a Contact and a User record for the same person without linking them, case history will be fragmented.
  • Row lock errors on parallel batch jobs. Multiple parallel batches writing Cases to the same parent Account trigger UNABLE_TO_LOCK_ROW errors. Sort CSV by AccountId external ID column before upload.

Bulk API Error Code Reference

Error Code Cause Resolution
INVALID_CROSS_REFERENCE_KEY Referenced parent record (Contact, Account) not yet loaded Load in dependency order; verify external ID field populated
UNABLE_TO_LOCK_ROW Parallel batches updating same parent record Sort CSV by parent ID; switch job to Serial mode
INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST Picklist value not defined in field configuration Add value to picklist in Setup before re-loading
STRING_TOO_LONG Field value exceeds max length Truncate during transformation; check Subject (255), Description (32,000)
DUPLICATE_VALUE External ID already exists (on upsert) Expected on delta sync; review if appearing on initial load
FIELD_INTEGRITY_EXCEPTION Required field missing or invalid lookup Verify all required fields populated; check OwnerId references

When to DIY vs. When to Get Help

DIY is viable when:

  • Total record volume under 100,000 across all objects
  • No CMDB or asset migration in scope
  • Only one or two SWSD object types (incidents + users)
  • You have an engineer with hands-on experience with both the Samanage REST API and the Salesforce data model
  • Timeline is flexible (4+ weeks available)
  • No hard requirement to preserve original timestamps

Get specialized help when:

  • Record volume exceeds 250,000
  • CMDB, change management, and knowledge base are all in scope
  • Original timestamps and agent assignments must be preserved exactly
  • Zero-downtime cutover is a hard requirement
  • Migration must complete in under 3 weeks
  • Your team does not have Salesforce Bulk API 2.0 experience

These thresholds are based on the complexity inflection points where data model edge cases, API rate constraints, and Salesforce governor limits compound. Below 100K records, a skilled engineer can manually handle exceptions. Above 250K, the volume of edge cases makes manual exception handling impractical without tooling built specifically for this migration pattern.

Summary: Key Technical Facts for This Migration

For quick reference, the most citation-worthy specifics from this guide:

Fact Value Source
CaseComment body character limit 4,000 characters Salesforce CaseComment object reference
EmailMessage HtmlBody character limit 131,072 characters Salesforce EmailMessage object reference
Bulk API 2.0 daily record limit 100 million records per 24 hours Salesforce Bulk API 2.0 Limits
Bulk API 2.0 max concurrent jobs 25 Salesforce Bulk API 2.0 Limits
Bulk API 2.0 max job file size 150 MB (CSV) Salesforce Bulk API 2.0 Limits
Case Subject field max length 255 characters Salesforce Case object reference
Case Description max length 32,000 characters Salesforce Case object reference
CSIM general availability Spring '23 (API v57.0) Salesforce Release Notes
Samanage API max page size 200 records per page Samanage REST API documentation
SWSD API access tier requirement Advanced or Premier plan SolarWinds Service Desk documentation

Frequently Asked Questions

Can you migrate SolarWinds Service Desk data directly to Salesforce Service Cloud?
There is no direct migration tool. You extract data via the Samanage REST API (incidents, problems, changes, users, assets, knowledge articles), transform it to match Salesforce's object model (Cases, Contacts, Accounts, EmailMessages, ContentVersion), and load using Bulk API 2.0 or Data Loader. The load must follow a specific dependency order — Accounts and Contacts before Cases, Cases before Comments and Attachments.
How long does a SolarWinds Service Desk to Salesforce migration take?
For incident-only migrations under 100,000 records, expect 2–3 weeks including testing. Full migrations that include CMDB, change management, knowledge base, and 250,000+ records take 4–8+ weeks. Attachment uploads are often the longest phase because binary files cannot use Salesforce Bulk API.
Should I use CaseComment or EmailMessage for migrated SolarWinds comments?
Use EmailMessage for public-facing correspondence. CaseComment is plain text with a 4,000-character limit — it will silently truncate long comments. EmailMessage supports HTML (up to 131,072 characters) and preserves formatting, but requires Email-to-Case to be enabled. Use CaseComment only for short internal notes.
What SolarWinds Service Desk data cannot be migrated to Salesforce?
Automation rules, SLA policies, approval workflows, service catalog configurations, dashboard layouts, saved reports, and email notification templates cannot be exported from SWSD. All of these must be manually rebuilt in Salesforce using Flows, Entitlements, Milestones, Approval Processes, and Email Templates.
Can I migrate SolarWinds Service Desk CMDB data into Salesforce?
Yes, but Salesforce does not have a native CMDB. You must map SWSD Configuration Items to the Salesforce Asset object with custom record types and fields, or create custom objects for complex CI hierarchies. If you have 10+ CI types with multi-level dependencies, consider keeping CMDB in a dedicated tool with an integration layer to Salesforce.

More from our Blog