---
title: "Files, Attachments and Documents: which to migrate to"
url: "https://trailassembly.com/files-vs-attachments/"
topic: "Data and Integration"
published: "2026-09-12T07:35:39+00:00"
updated: "2026-10-06T13:53:31+00:00"
---

# Files, Attachments and Documents: which to migrate to

**The short version:** Files (ContentDocument) is the current model, Attachments and Documents are the legacy ones, and new work should use Files exclusively. The migration matters because Attachments cannot be shared to more than the one record they hang off, and that limitation is usually what sends people looking.

## The three models

**Attachment** belongs to exactly one parent record. Delete the parent, the attachment goes. It cannot be shared, versioned, or previewed well. It is the oldest model and it still exists because deleting it would break a great deal of code.

**Document** lives in a folder, not on a record. It was the place for logos and templates before Files existed. Mostly obsolete, still used by some email templates and [Visualforce](https://trailassembly.com/apex-visualforce-remoting/).

**File**, meaning ContentDocument and its versions, is the modern model. One file can be linked to many records, has version history, previews properly, and works in [Chatter](https://trailassembly.com/salesforce-chatter-configuration/), communities and the mobile app.

## The object model behind Files, which is three objects

```apex
ContentDocument        the file's identity, one per file
  ContentVersion       each uploaded version; the newest is IsLatest
  ContentDocumentLink  one row per record the file is linked to
```

You insert a `ContentVersion`, and the platform creates the `ContentDocument` for you. To attach it to a record you then insert a `ContentDocumentLink`. Two DML operations, and the second one is what people forget.

```apex
ContentVersion cv = new ContentVersion(
    Title = 'Signed contract',
    PathOnClient = 'contract.pdf',
    VersionData = fileBody
);
insert cv;

Id docId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId;

insert new ContentDocumentLink(
    ContentDocumentId = docId,
    LinkedEntityId    = accountId,
    ShareType         = 'V',
    Visibility        = 'AllUsers'
);
```

## ShareType and Visibility decide who sees it

This is the part that produces surprises. `ShareType` is `V` for viewer, `C` for collaborator, `I` for inferred from the record. `Visibility` is `AllUsers`, `InternalUsers` or `SharedUsers`.

The combination that catches people is a link with `ShareType = 'I'`, which infers permission from the linked record. That sounds right and it means anyone who can see the account can see the file, including community users if the account is shared to them. A contract linked to an account with broad sharing becomes broadly visible, and nothing about the file itself says so.

Check what a file is linked to before assuming its access:

```apex
SELECT ContentDocumentId, LinkedEntityId, LinkedEntity.Name, ShareType, Visibility
FROM ContentDocumentLink
WHERE ContentDocumentId = :docId
```

That query needs a filter on ContentDocumentId or LinkedEntityId. It cannot be run unfiltered, which is a deliberate platform restriction.

## Migrating from Attachments

There is no button. The pattern is to read each Attachment, create a ContentVersion from its Body, create a [ContentDocumentLink](https://trailassembly.com/contentdocumentlink-sharing/) to the same parent, verify, then delete the Attachment.

Three things to plan for. Heap: a file’s body counts against the 6 MB synchronous heap, so this is batch work with a small scope size. Storage: during the migration both copies exist and file storage doubles. And references: any code, formula or integration that reads Attachment breaks, so find them before the delete step and not after.

## Storage counts differently

Files and Attachments consume file storage, not data storage, and the two have separate allowances. A scratch or developer org has very little file storage, which is why a migration rehearsal there tells you nothing about [capacity](https://trailassembly.com/salesforce-capacity-planning/).

Versions each consume storage. A file uploaded ten times holds ten versions and ten times the space, and nothing prunes them automatically.

## Frequently asked

**Can one file be on several records?** Yes, that is the main advantage. Insert one ContentDocumentLink per record.

**What happens when I delete the last link?** The ContentDocument remains, owned by whoever uploaded it, visible in their Files. It is not deleted.

**Are Notes the same thing?** Enhanced Notes are stored as ContentNote, which is a ContentDocument subtype. Old Notes are a separate legacy object alongside Attachment.

---

Source: https://trailassembly.com/files-vs-attachments/
Trail Assembly — independent Salesforce write-ups.
