Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

Short answer: the ceiling depends entirely on which mechanism you use. Salesforce Files, stored as ContentVersion, allow far larger files than the old Attachment object, which is capped in the low tens of megabytes. But the file size limit is rarely what stops you: the request size limit and the Apex heap limit usually bite first, which is why anything large has to be uploaded in chunks rather than as one base64 blob.

Files and Attachments are not the same thing

Two storage models still exist in every org:

  • ContentVersion, the Salesforce Files model. A file has versions, is linked to records through ContentDocumentLink, and one file can be shared with many records. This is the current model and where all the platform investment goes.
  • Attachment, the legacy model. Binary held in a base64 field on a record, one parent, no versions, a much lower size cap.

Attachment is not deprecated in the sense of being removed, but it is finished as a design choice. New work uses ContentVersion. The Notes and Attachments related list can be replaced with the Files related list on a layout, and the migration tooling for existing attachments exists.

A minimal ContentVersion insert in Apex:

ContentVersion cv = new ContentVersion(
    Title          = 'Statement.pdf',
    PathOnClient   = 'Statement.pdf',
    VersionData    = fileBlob,
    FirstPublishLocationId = recordId
);
insert cv;

Setting FirstPublishLocationId creates the ContentDocumentLink for you. Without it you insert the version, query back the ContentDocumentId, and create the link yourself.

The limits that actually stop you

Work down the list. The first one you hit is your real ceiling.

  • Apex heap. Six megabytes synchronous, twelve asynchronous. A Blob in memory counts against it, and base64 encoding inflates the size by about a third. Apex is therefore the wrong place to handle a large file, and no amount of cleverness changes the arithmetic.
  • Request size. An API request carrying a base64 body has a maximum size well below the maximum file size. This is why a 500MB file cannot be sent in one POST even though the platform will happily store it.
  • Attachment and base64 field caps. Attachments and other base64 body fields are limited to a few tens of megabytes, and the limit applies to the encoded form.
  • Storage allocation. File storage is a separate allocation from data storage, priced separately, and a document-heavy org exhausts it long before it exhausts anything technical.
  • Visualforce and legacy UI paths have their own lower caps that do not track the API limits.

Rather than memorising the current numbers, know the shape: the file size ceiling is large, the single-request ceiling is much smaller, and Apex’s is smaller still.

Chunking large uploads

For anything beyond a single request, the pattern is the same across clients: send the first portion to create the ContentVersion, then send subsequent portions that append to it, each as its own request. Salesforce documents this for the REST and Connect APIs, and the platform stitches the parts into one file.

What matters in practice:

  • Upload directly from the client to Salesforce. Routing a large file through Apex to re-upload it is the design that fails at 7MB.
  • Send chunks sequentially, not in parallel. Order matters.
  • Handle a failure mid-upload explicitly. A partially uploaded file is a real state that needs cleaning up.
  • Multipart form requests, where the metadata and the binary are separate parts, avoid base64 inflation entirely and are the better choice whenever the client can build one.

Reading files back

The same constraints apply in reverse. Querying VersionData into Apex pulls the whole file into heap. For downloads, hand the user a URL and let the platform stream it rather than reading the blob in a controller. For an integration, fetch the binary from the ContentVersion’s data endpoint directly instead of through a SOQL query.

Two smaller behaviours worth knowing. Files are indexed for search, and a preview is generated asynchronously, so a file is visible before its preview is. And deleting a ContentDocument removes every version and every link at once, which is not obvious from a related list showing one entry.

Frequently asked

Does file storage count towards data storage? No. They are separate allocations, and files do not consume the record storage that governs how many records you can hold.

Can I attach the same file to several records? With Files, yes, through additional ContentDocumentLink records. With Attachments, no, you would copy it and pay for both.

Why did my upload fail with a heap error at a size well under the limit? Because the encoded form is larger than the file, and anything else already in heap counts too. Measure with Limits.getHeapSize() rather than guessing.