case-history-migration

Use when migrating historical Case records — including CaseComments, EmailMessages, email attachments, and related files — from a legacy system into Salesforce Service Cloud. Covers strict object load order, EmailMessage Status='3' permanent locking, ContentDocumentLink Bulk API limitation, CaseHistory non-insertable constraint, and EmailMessageRelation setup. NOT for generic multi-object data migration planning (use data-migration-planning). NOT for ongoing email sync or Einstein Activity Capture configuration. NOT for Contact or Account record migration.

Best use case

case-history-migration is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when migrating historical Case records — including CaseComments, EmailMessages, email attachments, and related files — from a legacy system into Salesforce Service Cloud. Covers strict object load order, EmailMessage Status='3' permanent locking, ContentDocumentLink Bulk API limitation, CaseHistory non-insertable constraint, and EmailMessageRelation setup. NOT for generic multi-object data migration planning (use data-migration-planning). NOT for ongoing email sync or Einstein Activity Capture configuration. NOT for Contact or Account record migration.

Teams using case-history-migration should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/case-history-migration/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/data/case-history-migration/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/case-history-migration/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How case-history-migration Compares

Feature / Agentcase-history-migrationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when migrating historical Case records — including CaseComments, EmailMessages, email attachments, and related files — from a legacy system into Salesforce Service Cloud. Covers strict object load order, EmailMessage Status='3' permanent locking, ContentDocumentLink Bulk API limitation, CaseHistory non-insertable constraint, and EmailMessageRelation setup. NOT for generic multi-object data migration planning (use data-migration-planning). NOT for ongoing email sync or Einstein Activity Capture configuration. NOT for Contact or Account record migration.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Case History Migration

This skill activates when a practitioner needs to migrate historical Case records — including comments, inbound and outbound emails, email attachments, and associated files — from a legacy help desk or CRM system into Salesforce Service Cloud. It covers the mandatory object load order enforced by referential integrity, the permanent read-only lock triggered by EmailMessage Status='3', the Bulk API batching restriction on ContentDocumentLink, and the non-insertable nature of the CaseHistory object.

---

## Before Starting

Gather this context before working on anything in this domain:

- **CaseHistory is not insertable**: The `CaseHistory` object records field-level changes on Case (e.g., Status, Priority, Owner changes) and is system-generated by Salesforce. It cannot be inserted via API. Attempts to directly insert CaseHistory records return `INVALID_TYPE_FOR_OPERATION`. Historical audit data must be approximated via Task records or FeedItem entries on the Case.
- **EmailMessage Status='3' permanently locks the record**: Setting `Status` to `'3'` (Sent) on an EmailMessage record at insert time marks the record as a sent outbound email and makes it permanently read-only — it cannot be updated or deleted by any user or API caller, including System Administrator and Bulk API. This is the most common destructive mistake in Case email migrations. Always load EmailMessage records with `Status='0'` (New) or `Status='1'` (Read) unless you explicitly need the permanent locking behavior.
- **ContentDocumentLink does not support Bulk API 2.0 batching**: ContentDocumentLink (the junction object that links a ContentDocument/file to a Case, EmailMessage, or other record) must be inserted via the standard REST API or SOAP API. Bulk API 2.0 jobs that include ContentDocumentLink will fail or silently drop rows. Use single-record REST API calls or small SOAP API batches (200 rows max) for ContentDocumentLink inserts.
- **EmailMessageRelation is required for addressee linkage**: EmailMessage records store raw address strings in `ToAddress`, `FromAddress`, `CcAddress`, and `BccAddress`, but those strings are not automatically linked to Contact, Lead, or User records. `EmailMessageRelation` is the junction object that creates the explicit relationship between an EmailMessage and a Salesforce Contact/Lead/User. Without EmailMessageRelation rows, the email displays in the case timeline but the Contact is not linked and email-to-contact reporting is broken.
- **Strict load order is mandatory**: Salesforce referential integrity requires: Case → CaseComment → EmailMessage → EmailMessageRelation → ContentVersion → ContentDocumentLink. Each object depends on its parent existing before insert.

---

## Core Concepts

### Load Sequence: Strict Dependency Order

Every object in the Case email migration chain has a parent that must exist first. The required sequence is:

1. **Case** — the root record. All other objects require a valid `ParentId` or `LinkedEntityId` pointing to a Case record.
2. **CaseComment** — requires a valid `ParentId` (Case). Comments must be loaded before emails if any email references a comment thread.
3. **EmailMessage** — requires a valid `ParentId` (Case). `Status` must be set carefully (see Core Concepts: EmailMessage Status Locking).
4. **EmailMessageRelation** — requires a valid `EmailMessageId`. Load after all EmailMessage records are confirmed inserted.
5. **ContentVersion** — the file content record. Load file binary content here. `ContentVersion` auto-creates a parent `ContentDocument` on insert. Does not require a Case to exist yet.
6. **ContentDocumentLink** — the junction object linking a ContentDocument (created from ContentVersion) to a Case or EmailMessage. Requires both `ContentDocumentId` (from ContentVersion's auto-created parent) and `LinkedEntityId` (Case or EmailMessage). Must be loaded via REST/SOAP API — not Bulk API 2.0.

Violating this order causes referential integrity failures that appear as row-level errors in the Bulk API job results and are time-consuming to diagnose when mixed in large batches.

### EmailMessage Status Field and Permanent Locking

The `Status` field on EmailMessage controls the read/write state of the record:

| Status Value | Label | Read-Only After Insert? |
|---|---|---|
| 0 | New | No — record remains editable |
| 1 | Read | No — record remains editable |
| 2 | Replied | No — record remains editable |
| 3 | Sent | Yes — **permanently locked, no updates or deletes possible** |
| 4 | Forwarded | No — record remains editable |

Setting `Status='3'` (Sent) at insert time is intended for Salesforce's native outbound email sending workflow, where it signals that the message has been delivered. When set during a data migration load, it permanently freezes the record. There is no API override, no permission set, and no org configuration that unlocks a Status='3' EmailMessage. The only remediation is to delete and re-insert (which is also blocked — Status='3' records cannot be deleted by the API or UI).

**Correct approach for migration**: Load all inbound emails with `Status='1'` (Read). For outbound emails that should appear as sent, use `Status='2'` (Replied) or `Status='4'` (Forwarded) as appropriate. Reserve `Status='3'` only if the business explicitly accepts permanent locking and will never need to modify those records.

### ContentDocumentLink and the Bulk API Restriction

Files in Salesforce are stored as:
- **ContentVersion** — the versioned file content (body, title, MIME type, path on client)
- **ContentDocument** — the parent document record, auto-created when a ContentVersion is inserted
- **ContentDocumentLink** — the junction linking a ContentDocument to a record (Case, EmailMessage, Account, etc.)

ContentVersion supports Bulk API 2.0 and can be loaded in large batches. ContentDocumentLink does **not** support Bulk API 2.0. Bulk API jobs that include ContentDocumentLink rows will fail with `OPERATION_TOO_LARGE` or process silently without creating links.

For large file volumes, use a Python or Node.js script that issues REST API `POST /services/data/vXX.X/sobjects/ContentDocumentLink` calls in serial or small parallel batches. Respect the per-org API limit (typically 15,000 REST calls per 24 hours for Developer Edition; higher for Enterprise/Unlimited).

After inserting a ContentVersion, query `SELECT ContentDocumentId FROM ContentVersion WHERE Id = '<inserted_id>'` to resolve the auto-created ContentDocument Id before inserting the ContentDocumentLink.

### CaseHistory: Non-Insertable System Object

`CaseHistory` is a child object of Case that Salesforce generates automatically when tracked fields on a Case record are changed. It captures the old value, new value, field name, and timestamp of the change. It is read-only via the API — `sObject not insertable` is returned for any insert attempt.

The practical consequence for migrations: historical case audit trails (status changes, owner reassignments, priority escalations from the legacy system) cannot be brought into CaseHistory natively. Options:

- **Task records (recommended)**: For each historical field change event in the source export, create a Task on the Case (`WhatId` = Case Id, `Subject` = "Historical Change: Status New → In Progress", `ActivityDate` = change date, `Status` = "Completed"). Tasks are visible in the Case activity timeline.
- **FeedItem (Chatter posts)**: Insert FeedItem records with `ParentId` = Case Id and `Body` = the historical change summary. These appear in the Case feed and are searchable via SOSL.
- **Custom object**: Load into a custom object (e.g., `Case_Audit_History__c`) with a lookup to Case for structured reporting that does not depend on the activity timeline.

---

## Common Patterns

### Pattern: Full Case Email and Attachment Migration

**When to use:** The source system has a complete case history including inbound and outbound email threads with file attachments. The business requires the full email timeline in Salesforce.

**How it works:**
1. Export Cases from source. Map to Salesforce Case fields. Assign a `Legacy_Case_Id__c` external ID field. Upsert Cases via Bulk API 2.0.
2. Export CaseComments. Map `ParentId` using `Case.Legacy_Case_Id__c` cross-reference. Load via Bulk API 2.0.
3. Export EmailMessages. Set `Status='1'` for inbound emails and `Status='2'` for outbound emails (never `Status='3'` at load time). Map `ParentId` via cross-reference. Include `MessageDate` from source to preserve the original email timestamp. Load via Bulk API 2.0.
4. Export email addressees. For each email, create EmailMessageRelation rows: one for each To/CC/BCC recipient that maps to a Contact, Lead, or User. Set `RelationObjectType` and `RelationId` appropriately. Load via Bulk API 2.0.
5. Export file attachments. Create ContentVersion rows with `Title`, `PathOnClient`, `VersionData` (base64-encoded), and `FirstPublishLocationId` set to the parent Case Id (optional — you can also link via ContentDocumentLink). Load ContentVersion via Bulk API 2.0.
6. For each ContentVersion inserted, query its auto-created `ContentDocumentId`. Build ContentDocumentLink rows linking to the Case or EmailMessage. Insert via REST API (not Bulk API) in batches of 200.

**Why not use Bulk API for ContentDocumentLink:** Bulk API 2.0 does not support ContentDocumentLink. Using it produces silent failures or explicit job errors without useful row-level diagnostics.

### Pattern: Comments-Only Case Migration (No Emails)

**When to use:** The source system used a comment/note model rather than email threading. No actual email messages need to be migrated — only case notes and internal comments.

**How it works:**
1. Upsert Cases with external IDs.
2. Load CaseComments with `IsPublished = true` for customer-visible comments and `IsPublished = false` for internal notes.
3. For attachments on comments, load ContentVersion first, then link via ContentDocumentLink to the Case using the REST API.
4. Skip EmailMessage, EmailMessageRelation, and any email-specific steps.

**Why not use Attachment object:** The legacy `Attachment` object (linked to Case via `ParentId`) is deprecated in favor of Files (ContentVersion/ContentDocument/ContentDocumentLink). Salesforce recommends against loading new Attachment records; use ContentVersion instead.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Loading outbound emails that should appear as sent | Use `Status='2'` (Replied) at load time | Status='3' permanently locks — cannot be updated or deleted after insert |
| Files attached to cases | ContentVersion → ContentDocumentLink via REST API | ContentDocumentLink does not support Bulk API 2.0 |
| Historical status/owner changes need to be preserved | Task records per change event on the Case | CaseHistory is not insertable; Tasks appear in activity timeline |
| Email recipients need to be linked to Contact records | Load EmailMessageRelation after EmailMessage | ToAddress/FromAddress strings do not auto-link to Contact records |
| High volume of file attachments (10,000+) | Script REST API calls for ContentDocumentLink in parallel threads | REST API rate limits apply; plan for multi-day load windows or request a limit increase |
| Source emails have inline images | Embed as ContentVersion, link with ContentDocumentLink `ShareType='I'` | Inline images are ContentDocuments with ShareType Internal |
| Source system had threaded email replies | Link all reply messages to the same Case ParentId; use `ReplyToEmailMessageId` to chain them | EmailMessage supports a `ReplyToEmailMessageId` field for threading |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner activating this skill:

1. **Confirm prerequisites and org configuration**: Verify Email-to-Case is enabled if inbound emails are being migrated. Confirm external ID fields exist on Case (`Legacy_Case_Id__c`). Check API limits and ContentDocumentLink volume estimate to plan the REST API load window. Map all source user/agent IDs to Salesforce UserIds.
2. **Load Case records first**: Upsert Cases via Bulk API 2.0 using `Legacy_Case_Id__c` as the external ID. Verify row counts and spot-check field values before proceeding to child objects.
3. **Load CaseComments**: Use cross-reference notation (`Case.Legacy_Case_Id__c`) for `ParentId` to avoid pre-resolving Salesforce IDs. Set `IsPublished` correctly for customer-visible vs. internal comments. Load via Bulk API 2.0.
4. **Load EmailMessages with correct Status**: Set `Status='1'` for inbound, `Status='2'` for outbound. Never set `Status='3'` at load time. Include `MessageDate` from source. Load via Bulk API 2.0.
5. **Load EmailMessageRelation records**: For each email, create relation rows for To, CC, BCC addressees that map to Contact, Lead, or User records. Load after EmailMessage is confirmed complete.
6. **Load ContentVersion (file content)**: Load all file content via Bulk API 2.0. After each batch, query `ContentDocumentId` from the inserted ContentVersion records.
7. **Link files via ContentDocumentLink using REST API**: Build ContentDocumentLink rows from the resolved ContentDocumentId values and the target Case or EmailMessage Ids. Insert via REST API in serial batches of 200. Validate attachment count per case matches source.

---

## Review Checklist

Run through these before marking case history migration complete:

- [ ] All Cases loaded and row counts verified before any child object loads begin
- [ ] EmailMessage `Status` field value confirmed — no `Status='3'` used at load time
- [ ] EmailMessageRelation rows exist for all To/CC/BCC addressees that map to Contact or User records
- [ ] CaseHistory insert was NOT attempted directly (use Task records or FeedItem instead)
- [ ] ContentVersion loaded via Bulk API 2.0 — ContentDocumentLink loaded via REST API only
- [ ] ContentDocumentId resolved from ContentVersion before ContentDocumentLink rows are built
- [ ] ContentDocumentLink row count per case matches source attachment count
- [ ] CaseComment IsPublished flag set correctly for customer-visible vs. internal comments
- [ ] Post-migration validation: case comment counts, email counts, and attachment linkage spot-checked against source

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **EmailMessage Status='3' permanently locks the record** — Setting `Status='3'` (Sent) at insert time makes the EmailMessage record permanently read-only. It cannot be updated or deleted via UI, Apex, REST, SOAP, or Bulk API — by any user including System Administrator. The only recovery is a destructive re-migration: delete the EmailMessage (impossible via API once locked) using Salesforce Data Export and a special support case, or leave the locked records and re-insert corrected copies. Use `Status='1'` (Read) or `Status='2'` (Replied) for historical emails.

2. **ContentDocumentLink does not support Bulk API 2.0** — Any Bulk API 2.0 job that includes ContentDocumentLink rows will either fail immediately or process silently without creating links. This causes phantom attachment failures where ContentVersion records exist and ContentDocuments are created, but no files appear on the Case record. Always use the standard REST or SOAP API for ContentDocumentLink inserts.

3. **CaseHistory is not insertable** — `CaseHistory` is a system-generated read-only object. API calls that attempt to insert CaseHistory records return `INVALID_TYPE_FOR_OPERATION: entity type Case History does not support create`. Historical case audit data must be stored in Task records, FeedItem records, or a custom object.

4. **EmailMessageRelation is required for Contact linkage** — The `ToAddress` and `FromAddress` fields on EmailMessage are plain text strings. They do not automatically resolve to Contact or Lead records. Without explicit `EmailMessageRelation` rows, the email appears in the case timeline but the associated Contact is not linked. Reports that rely on email-to-contact associations will return incomplete data.

5. **ContentVersion auto-creates ContentDocument — Id not available until after insert** — When a ContentVersion is inserted, Salesforce automatically creates a parent ContentDocument and populates `ContentVersion.ContentDocumentId`. This Id is not known before the insert, so ContentDocumentLink rows cannot be pre-built in the same batch. The workflow must be: insert ContentVersion → query ContentDocumentId → build ContentDocumentLink rows → insert ContentDocumentLink via REST API.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `case-history-migration-template.md` | Fill-in-the-blank work template capturing load sequence decisions, external ID mapping, EmailMessage Status strategy, ContentDocumentLink volume estimate, and post-migration validation plan |
| `check_case_history_migration.py` | stdlib Python checker that validates migration CSVs for common sequencing violations, EmailMessage Status='3' usage, and ContentDocumentLink Bulk API misuse |

---

## Related Skills

- `data-migration-planning` — use for multi-object migration architecture, tool selection, external ID strategy, validation rule bypass, and rollback planning; this skill focuses specifically on the Case object family
- `bulk-api-and-large-data-loads` — use when load volume exceeds standard limits or when optimizing Bulk API 2.0 job configuration
- `data-archival-strategies` — use when historical Case data should be archived to external storage rather than migrated into Salesforce

---

## Official Sources Used

- EmailMessage Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_emailmessage.htm
- CaseComment Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_casecomment.htm
- ContentVersion Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_contentversion.htm
- ContentDocumentLink Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_contentdocumentlink.htm
- EmailMessageRelation Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_emailmessagerelation.htm
- CaseHistory Object Reference — https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_casehistory.htm
- Bulk API 2.0 Developer Guide — https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_intro.htm

Related Skills

vlocity-to-native-omnistudio-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when migrating an org from the Vlocity managed package (vlocity_ins, vlocity_cmt, vlocity_ps) to native OmniStudio. Trigger keywords: Vlocity to OmniStudio migration, namespace migration, vlocity_ins to omnistudio, OmniStudio Migration Tool, DataRaptor namespace update, OmniScript JSON export, managed package to native. NOT for new OmniStudio setup in greenfield orgs, nor for migrating between OmniStudio-native orgs, nor for Salesforce CPQ to Industries CPQ migration.

omnistudio-lwc-omniscript-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrate classic Visualforce-based OmniScripts to LWC-based runtime with feature parity and regression testing. NOT for new OmniScript design.

visualforce-to-lwc-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrating Visualforce pages and components to Lightning Web Components: controller-to-Apex-method translation, viewstate replacement, custom URL parameter handling, PageReference-to-NavigationMixin mapping, Lightning Out coexistence, and inline VF retention strategy. NOT for new LWC development from scratch (use lwc-fundamentals) or Aura-to-LWC migration (use aura-to-lwc-migration).

lwc-mobile-offline-and-briefcase

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing LWCs that must work offline in the standard Salesforce Mobile App, and when configuring Briefcase Builder rules to prime records onto user devices. Covers form-factor detection (`@salesforce/client/formFactor`), Lightning Data Service offline-cache behavior, Briefcase priming-rule design, conflict resolution on reconnect, and the legacy SmartStore / IndexedDB picture for context. Triggers: 'briefcase builder rule design', 'lwc not working offline', 'how do I prime records to user devices', 'sync conflict on reconnect', 'offline draft persistence in lwc'. NOT for Field Service Mobile offline priming (use admin/fsl-mobile-app-setup or architect/fsl-offline-architecture — FSL has its own priming pipeline). NOT for general LWC mobile container patterns / device APIs (use lwc/lwc-offline-and-mobile).

lwc-locker-to-lws-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrating LWCs from Lightning Locker Service to Lightning Web Security (LWS) — flipping the org switch safely, identifying components likely to break, removing Locker workarounds that are now insecure, and validating third-party libraries that previously failed under SecureWindow/SecureElement proxies. NOT for Aura → LWC migration — see lwc/aura-to-lwc-migration. NOT for general LWC security review (XSS, public-API hardening) — see lwc/lwc-security and lwc/lwc-public-api-hardening.

aura-to-lwc-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrating Aura components to LWC: feature mapping, interoperability wrappers, event translation, navigation patterns, and Aura-LWC coexistence strategies. NOT for new LWC development from scratch or Aura feature development.

workflow-rule-to-flow-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrate Workflow Rules to record-triggered Flows: field update mapping, email alert migration, outbound message alternatives using Flow Core Actions, time-based workflow replacement with Scheduled Paths. NOT for Process Builder migration (use process-builder-to-flow-migration), NOT for building new flows from scratch.

process-builder-to-flow-migration

8
from PranavNagrecha/AwesomeSalesforceSkills

Migrate Process Builder processes to record-triggered Flows using the native Migrate to Flow tool or manual rebuild. Covers conversion tool usage, pattern mapping, order-of-execution changes, testing migrated flows, and bulk behavior improvements. NOT for building new flows from scratch, NOT for Workflow Rule migration (use workflow-rule-to-flow-migration), NOT for net-new automation design.

flow-migration-from-trigger

8
from PranavNagrecha/AwesomeSalesforceSkills

Decide whether an existing Apex trigger should be rewritten as a Flow, and execute the migration safely. Covers the decision criteria (complexity, ownership, performance), side-by-side rollout, test-coverage parity, and the inverse case (recognize when Flow should stay Apex). NOT for migrating Process Builder / Workflow Rule to Flow (use those migration skills). NOT for brand-new automation decisions (use automation-selection.md).

migration-from-change-sets-to-sfdx

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when planning or executing a migration from change-set-based deployments to Salesforce DX source-driven development. Trigger keywords: 'migrate from change sets', 'move to SFDX', 'convert metadata to source format', 'sf project convert mdapi', 'source-driven development adoption'. NOT for greenfield SFDX project setup (use sf-cli-and-sfdx-essentials), unlocked package design (use unlocked-package-development), or DevOps Center pipeline creation (use devops-center-pipeline).

product-catalog-migration-cpq

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when bulk-loading or migrating Salesforce CPQ product catalog configuration data — SBQQ-namespaced objects including Product2, ProductOption, PriceRule, PriceAction, DiscountSchedule, DiscountCategory, ConfigurationAttribute, and OptionConstraint — across orgs or from an external source system. Trigger keywords: CPQ bulk load, SBQQ product migration, ProductOption insert order, CPQ trigger disable, price rule migration, CPQ sandbox refresh catalog, bundle migration. NOT for standard product import or CRM product migration (use product-catalog-data-model). NOT for CPQ quote or subscription data migration. NOT for Industries CPQ (Vlocity) catalog migration.

product-catalog-migration-commerce

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when migrating product catalog data into Salesforce B2B Commerce — covers category hierarchy, product attributes, images, pricing, and variant structure using the Commerce Import API. NOT for CPQ product catalog migration, post-migration catalog configuration, or commerce catalog taxonomy planning.