Insights

Migrating Salesforce files without losing their record links

A practical guide to inventorying, moving and reconciling Salesforce Files through ContentVersion and ContentDocumentLink.

Secure Salesforce file migration between cloud environments
A file is only useful after its content, metadata and Salesforce record links have all been reconciled.

What this covers

  • Define whether the migration needs latest files, version history, libraries and every existing record link.
  • Keep the source export immutable and maintain an external source-to-target ID map.
  • Reconcile documents, versions, links and failed rows separately before sign-off.

Salesforce Files are not stored as a single record. The binary data sits on ContentVersion, Salesforce groups versions under ContentDocument, and ContentDocumentLink connects the document to records, users or groups. A migration can upload every file successfully and still fail if those relationships are incomplete.

Treat the work as a data migration with an immutable source, explicit mappings, repeatable batches and reconciliation evidence. Do not make the target org the first place where missing links or ambiguous scope are discovered.

Define the migration boundary first

Agree what a file means for this migration before exporting anything. The answer affects volume, storage, API choice, run time and the evidence needed at sign-off.

  • Latest version only, or complete version history.
  • Files linked to in-scope records only, or every file owned by selected users.
  • Private files, shared files, library content and files linked to groups or users.
  • Archived, deleted, externally hosted or otherwise exceptional content.
  • Original ownership, created dates, sharing behaviour and audit-field requirements.
  • Maximum file size, total storage and the limits of the selected upload route.

Export metadata and content together

Use Salesforce Data Export or another controlled extraction method that can retrieve both file content and the related CSV data. For a standard Salesforce export, include Salesforce Files and Salesforce CRM Content document versions. If the export is split across archives, keep the complete set together; later archives can contain binary content even when only one archive contains the CSV metadata.

Capture the standard source IDs in an external reconciliation table. Start with one row per source ContentVersion containing its ContentDocumentId, file path, version number and latest-version flag, plus a separate row for every source ContentDocumentLink. Do not assume the target org has a custom legacy-ID field.

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

Preserve the original export

Store the downloaded archives and checksums as read-only evidence. Prepare loads from a working copy so a failed transformation or retry cannot alter the only source export. Use a task-specific folder and keep generated manifests, load files and results beside the batch they belong to.

mkdir -p work/file-migration/prepared/source-copy
cp -R source-export/. work/file-migration/prepared/source-copy/

Load ContentVersion in controlled batches

A new file is created by inserting ContentVersion. Typical load columns include Title, PathOnClient and VersionData, with ownership or audit fields included only where the migration route and org permissions support them. FirstPublishLocationId can create the initial relationship when one target record is known, but it is not a substitute for rebuilding every source link.

Keep batches small enough to diagnose and retry. Record the source ContentVersion ID beside the target insert result. After each successful upload, resolve the target ContentDocumentId from the inserted ContentVersion and add it to the external mapping table. This map, rather than file names, should drive the link load.

Rebuild every valid relationship

To create ContentDocumentLink rows, both sides need target IDs: the new ContentDocumentId and the target LinkedEntityId. Record migration therefore needs its own reliable source-to-target ID mapping before file links can be rebuilt.

  • Translate the source ContentDocumentId through the file mapping table.
  • Translate the source LinkedEntityId through the relevant object mapping table.
  • Carry ShareType and Visibility only after confirming that the values remain valid for the target relationship.
  • Quarantine links whose target record is out of scope instead of silently dropping them.
  • Handle libraries and other non-record destinations as an explicit migration stream where required.

Design retries before the first load

A repeatable process should know which target records each batch created. Keep success and failure files, target IDs and the exact input manifest for every run. Retry failed rows only; blindly re-running a successful file batch can create duplicate ContentDocument records.

If a batch must be removed, use the recorded target IDs and an approved rollback step. Never use broad title or date matching to decide which target files to delete.

Reconcile before sign-off

  • Compare in-scope source and target counts for documents, versions and links.
  • Account for every rejected, excluded and out-of-scope row.
  • Compare file sizes or checksums where the extraction and upload tooling exposes reliable values.
  • Download and open a sample across file types, sizes, owners and business-critical records.
  • Check records with several files and files linked to several records.
  • Test visibility with representative end users rather than only an administrator.
  • Retain manifests, mappings, load logs and signed reconciliation results with the migration evidence.

A completed upload job is only one checkpoint. Success means every in-scope file can be found, opened and accessed from the right Salesforce records, with every exception explained.

Official references