Salesforce stores files in two places depending on how old your org is and how records were created: Attachments (the legacy system) and Salesforce Files (the modern ContentDocument/ContentVersion system). Bulk exporting these — especially when you're doing a data migration or org cleanup — can be tricky if you don't know the right approach.

In this guide, I'll walk you through every practical method to bulk export Salesforce Files and Attachments, including Apex code you can run today.

Advertisement (336×280)

Understanding the Salesforce File System

Before exporting anything, it's important to understand the two file objects:

  • Attachment — Legacy object. Files are attached directly to a record (ParentId). Not shareable across records.
  • ContentDocument — Modern file system. Files are stored as ContentVersion records and linked to records via ContentDocumentLink.

Most orgs created after 2014 use ContentDocument/ContentVersion for new files, but may have old Attachment records from earlier in the org's history.

Method 1: Export with Data Loader (No Code)

For exporting metadata about files (names, sizes, dates, parent IDs), the Salesforce Data Loader is the simplest option.

For Salesforce Files (ContentVersion)

Use this SOQL query in Data Loader:

SELECT Id, Title, FileType, FileExtension, ContentSize,
       ContentDocumentId, CreatedDate, LastModifiedDate,
       FirstPublishLocationId, VersionNumber
FROM ContentVersion
WHERE IsLatest = TRUE
ORDER BY CreatedDate DESC

For Legacy Attachments

SELECT Id, Name, ContentType, BodyLength,
       ParentId, CreatedDate, LastModifiedDate,
       OwnerId
FROM Attachment
ORDER BY CreatedDate DESC

Limitation: Data Loader exports file metadata only — not the actual file binaries. For the actual files, use Method 2 or 3.

Method 2: Apex Batch Export with REST API URLs

To export the actual file contents, you need to generate download URLs and fetch them. Here's an Apex class that builds export-ready REST URLs for all ContentVersion records:

// ContentFileExporter.cls
public class ContentFileExporter {

    public static List<Map<String,String>> getFileDownloadUrls(String parentId) {
        List<Map<String,String>> result = new List<Map<String,String>>();

        // Query all files linked to the record
        List<ContentDocumentLink> links = [
            SELECT ContentDocumentId
            FROM ContentDocumentLink
            WHERE LinkedEntityId = :parentId
        ];

        Set<Id> docIds = new Set<Id>();
        for (ContentDocumentLink link : links) {
            docIds.add(link.ContentDocumentId);
        }

        // Get latest versions
        List<ContentVersion> versions = [
            SELECT Id, Title, FileExtension, ContentDocumentId, FileType
            FROM ContentVersion
            WHERE ContentDocumentId IN :docIds
            AND IsLatest = TRUE
        ];

        String baseUrl = URL.getOrgDomainUrl().toExternalForm();

        for (ContentVersion cv : versions) {
            Map<String,String> fileInfo = new Map<String,String>{
                'Id'       => cv.Id,
                'Title'    => cv.Title,
                'Extension'=> cv.FileExtension,
                'DownloadUrl' => baseUrl + '/sfc/servlet.shepherd/version/download/' + cv.Id
            };
            result.add(fileInfo);
        }

        return result;
    }
}

Method 3: Bulk Export All Files via Apex Batch

For org-wide exports of all files, use an Apex Batch class that chunks ContentVersion records and either generates a CSV log or calls an external endpoint for each file:

// ContentVersionBatchExporter.cls
global class ContentVersionBatchExporter
    implements Database.Batchable<sObject> {

    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id, Title, FileExtension, ContentSize, ' +
            'ContentDocumentId, VersionData ' +
            'FROM ContentVersion ' +
            'WHERE IsLatest = TRUE'
        );
    }

    global void execute(Database.BatchableContext bc,
                        List<ContentVersion> scope) {
        // Process each file — write metadata to custom object
        // or send to external endpoint via callout
        List<Export_Log__c> logs = new List<Export_Log__c>();

        for (ContentVersion cv : scope) {
            Export_Log__c log = new Export_Log__c(
                File_Id__c = cv.Id,
                File_Name__c = cv.Title + '.' + cv.FileExtension,
                File_Size__c = cv.ContentSize,
                Document_Id__c = cv.ContentDocumentId
            );
            logs.add(log);
        }

        insert logs;
    }

    global void finish(Database.BatchableContext bc) {
        System.debug('File export batch complete.');
    }
}

// Run from Developer Console:
// Database.executeBatch(new ContentVersionBatchExporter(), 50);

Method 4: Export Attachments (Legacy)

For old Attachment records, you can fetch the binary body using Apex's Attachment.Body field (Blob type):

// Query attachments for a specific parent
List<Attachment> attachments = [
    SELECT Id, Name, ContentType, Body, BodyLength, ParentId
    FROM Attachment
    WHERE ParentId = :someRecordId
    LIMIT 200
];

for (Attachment att : attachments) {
    // att.Body is a Blob — can be base64 encoded for API transfer
    String base64Body = EncodingUtil.base64Encode(att.Body);
    System.debug('File: ' + att.Name + ' | Size: ' + att.BodyLength);
}
Advertisement (336×280)

Comparing All Methods

Method Gets Binary Files Bulk-Safe No Code Best For
Data Loader (SOQL) Metadata only
Apex + REST URLs Per-record downloads
Apex Batch Full org export log
Attachment.Body ⚠️ (heap limits) Small attachment sets

Important Governor Limit Considerations

  • Querying VersionData in bulk hits heap size limits — use batch sizes of 10-20 when fetching blob data.
  • ContentVersion queries with IsLatest = TRUE are always safer than querying all versions.
  • For orgs with 100,000+ files, always use a Database.Batchable approach — never a single SOQL loop.
  • The ContentDocumentLink object requires appropriate access permissions — verify sharing settings before bulk queries.

Frequently Asked Questions

Use Data Loader to query ContentVersion for metadata, or use an Apex Batch class to generate download URLs. For actual file binaries at scale, the REST API download endpoint combined with an Apex-generated URL list is the most reliable approach.

Salesforce Attachments (the legacy Attachment object) are directly linked to one record. Salesforce Files (ContentDocument/ContentVersion) are the modern system and can be shared across multiple records. Most orgs migrated to Files around 2017-2019.

For metadata only — yes, using Data Loader or Dataloader.io. For actual file content (the binary files themselves), you'll need either Apex code or a third-party tool that uses the Salesforce REST API under the hood.

Wrapping Up

Exporting Salesforce Files in bulk doesn't have to be painful. For metadata, Data Loader is your quickest option. For actual file binaries, Apex with REST download URLs is the most reliable approach at scale.

If you're doing a full org migration, I'd recommend combining the batch Apex logger with an external script (Python, Node.js) that iterates over the generated URL list and downloads each file — keeping all file processing outside Salesforce governor limits.

Got questions or edge cases I haven't covered? Drop me a message — I respond to every genuine inquiry.