Insights

Migrating Salesforce files without losing their record links

A practical migration plan for Salesforce file content, version history and record links, with worked examples of the mapping and reconciliation.

Diagram linking one Salesforce ContentDocument to its ContentVersion records and ContentDocumentLink relationships
Files have content, history and links. A migration needs to account for each.

What this covers

  • Choose a latest-version or full-history migration deliberately.
  • Keep one target document mapped to each in-scope source document.
  • Reconcile document, version and link records separately.

Uploading every file does not prove a Salesforce Files migration is complete. A document may have several versions, and the same document may be linked to more than one customer, case or contract.

ContentVersion holds the version and its content. ContentDocument groups the versions. ContentDocumentLink connects the document to Salesforce records and other supported destinations. The migration needs a plan for all three.

Choose what you are moving

Agree the migration scope before export
DecisionEffect on the migration
Latest version or full historyDetermines how many ContentVersion records need to be exported and recreated.
Which business records are in scopeDetermines the record links that need a target destination.
Ownership, sharing and audit fieldsDetermines the permissions, user mappings and additional checks needed.
Libraries, private files and exceptional contentMay require a separate migration path rather than a standard record-link load.
File sizes and total contentDetermines the upload route, batching and storage requirements.

A small example exposes the relationships

Suppose one agreement has two versions and is attached to both an Account and a Contract. These symbolic IDs describe the mapping; they are not Salesforce record IDs to paste into a load.

Illustrative full-history migration
SourceTarget requirementReconciliation
Document AOne target ContentDocumentOne source document maps to one target document.
Document A: versions 1 and 2Two ContentVersion records under that documentBoth versions exist and the intended latest version is current.
Document A → Account ALink to the migrated AccountThe document is available from the correct Account.
Document A → Contract ALink to the migrated ContractThe same document is available from the correct Contract.

In this example a full-history migration has one document, two versions and two business-record links. A latest-only migration has one document and one version, while still requiring both links. Keep those expectations separate.

Export metadata and content together

Retain the complete source export, including the binary content and the metadata needed to connect it. Keep archives unchanged and record checksums before preparing working copies. Split exports must stay together so a metadata file is not separated from content in another archive.

For selected documents, an inventory can include the fields below. Replace the example document ID with the exact in-scope IDs. For a latest-only export add the IsLatest filter; for full history retain all required versions and sort VersionNumber numerically when preparing the load.

SELECT Id, ContentDocumentId, Title, PathOnClient,
       ContentSize, VersionNumber, IsLatest
FROM ContentVersion
WHERE ContentDocumentId IN ('069...')
SELECT ContentDocumentId, LinkedEntityId, ShareType, Visibility
FROM ContentDocumentLink
WHERE ContentDocumentId IN ('069...')

Keep versions under the same target document

Create the first target version by inserting ContentVersion without a ContentDocumentId. Salesforce creates the document. Read the inserted version back to obtain its ContentDocumentId, then record the source-to-target mapping.

For full history, process the required source versions oldest first. Supply that same target ContentDocumentId when inserting each later version. Inserting every source version as a new file would create separate documents instead of the intended history.

FirstPublishLocationId can create an initial relationship when the first version of a new document is inserted. It is only available on that first insert. If you use it, account for the link already created when preparing the remaining links.

Each link requires the target document ID and the target LinkedEntityId. The Account, Contract and other record migrations therefore need their own mapping tables before the file links can be rebuilt.

  • Translate both sides of each source link through the appropriate mappings.
  • Check that ShareType and Visibility are valid for the target relationship.
  • Hold links with a missing or out-of-scope target for an explicit decision.
  • Treat library and other non-record destinations as separate paths where required.
  • Check whether an initial publish step or earlier batch already created the relationship.

Make retries and sign-off explainable

Record the exact batch input, successful target IDs and failures. Retry only the rows that need another attempt. If a batch needs correction or removal, use its recorded target IDs and an agreed recovery step; matching files by title is not a reliable rollback plan.

Reconciliation before acceptance
CheckEvidence
CompletenessExpected documents, versions and links compared separately with the target.
ContentFile sizes or checksums where reliable, plus downloaded samples that open correctly.
RelationshipsFiles are available from the correct records, including documents with multiple links.
AccessRepresentative end users can see the intended files and cannot see excluded material.
ExceptionsEvery failed, excluded or held item has a recorded reason and owner.

The result to sign off is a usable file history on the right Salesforce records. The upload job is one part of that evidence.

Official references