File Handling in Salesforce: Real-World Architecture and Pitfalls
The Problem Nobody Talks About
Pick any Salesforce file handling tutorial and you'll find sanitized examples: upload a file, store it in ContentVersion, retrieve it with SOQL. Perfect for demos, useless for production. The moment you need images for a public website, process PDFs with complex layouts, or handle API file size limits, you discover that Salesforce file storage is a minefield of architectural decisions that can make or break your application.
“I've built systems handling everything from CV parsing to auction site galleries. Every approach has brutal trade-offs that documentation conveniently omits. This article covers what actually works, what fails spectacularly, and the architectural decisions you need to make before writing code.”
ContentVersion vs Attachment: The Decision That Matters
The Real Differences:
Query Performance: ContentVersion requires three-object joins (ContentVersion → ContentDocument → ContentDocumentLink). Attachment is straightforward parent-child. In loops processing 200 records, ContentVersion hits SOQL limits. Attachment doesn't.
File Sharing: ContentVersion works in Experience Cloud with granular permissions. Attachment access control is primitive - see parent record or nothing.
API Limits: Both support 2GB via UI, but API uploads typically face smaller limits - generally around 38MB for ContentVersion and 25MB for Attachment, though this varies by upload method (REST, SOAP, multipart). Base64 encoding adds 33% overhead, further reducing practical limits.
Version Control: ContentVersion tracks versions natively. Attachment is single-version only.
Use ContentVersion when: file sharing across records, version history, Experience Cloud access. The use of Attachment is considered a deprecated practice and should be avoided unless there are strict system restrictions.
Image Loading: URL vs Base64
Architecture 1: Image URLs
How it works: Store in ContentVersion, generate URLs, browser loads from Salesforce CDN.
✓ Clean separation, browser caching, CDN delivery
✗ Authentication nightmares, URL expiration, CORS issues, no image optimization control, expensive three-object queries
Architecture 2: Base64 Data
How it works: Store binary as a base64 string in the Long Text Area field (max 131,072 characters), render as data:image URLs.
✓ Single object queries, no auth issues, works everywhere, immediate delete, version control
✗ Size limit (~98KB after encoding), SOQL character limits, heap consumption, 33% storage overhead, no CDN, slower browser parsing
Real Implementation:
An auction platform was implemented using both approaches simultaneously, controlled by Custom Metadata (Images_Loading_Type__c). Story thumbnails (<50KB) used Base64 for instant loading. Full-size images (200KB-2MB) used ContentVersion URLs with CDN caching. User profiles used Base64. PDF receipts used ContentVersion for version history.
The Heap Limit Nightmare
The 6MB Wall
Apex heap limit: 6MB sync, 12MB async. Base64 encoding adds 33% overhead, and you need memory for BOTH original Blob AND encoded String. Practical limit: ~4MB files before heap explosions.
Real Example:
CV parsing system: retrieve ContentVersion.VersionData, encode base64, send to OpenAI API. Worked perfectly with 500KB test PDFs. Production users uploaded 5MB CVs with embedded images. Heap limit exception. Every. Single. Time.
Solutions:
Winter '26 External Services: Use ContentDocument ID pointers instead of loading into the heap. Supports files up to 16MB, depending on the integration pattern and API capabilities.
File size validation: Reject >4MB at upload. Simple but user-hostile.
External storage: Upload to S3/Google Drive, store reference in Salesforce.
Chunking: Split files, process sequentially. Requires API multipart upload support.
File Format Hell
Salesforce Has Zero Native Parsing
PDF, DOCX, DOC - Salesforce can't parse any of them. ContentVersion.VersionData gives raw binary. Your options:
External API (OpenAI, AWS Textract): Accurate but expensive. Best for complex layouts.
Pre-process before upload: Convert to text externally, upload both.
Accept plain text only: Simplest but limits user experience.
Real CV Parsing Issues:
PDFs with heavy images return garbage text. Scanned PDFs need OCR (lower accuracy, higher cost). Multi-column layouts confuse extraction order. Solution: Validate format client-side, reject scanned PDFs, provide clear error messages.
Security: The Silent Failure Mode
ContentDocument Sharing Complexity:
Inferred sharing doesn't work in Communities - requires ContentDistribution
Parent record sharing ≠ ContentDocument sharing
ContentDocumentLink not included in sharing rules
Guest users may need to use ContentDistribution (paid) or Base64 to get access to ContentDocument
Secure Pattern:
Store sensitive files in ContentVersion with Viewer ShareType. Link only to user-owned records. Use without sharing Apex with custom access logic. For public files, use ContentDistribution with expiration or Base64 in public objects. Log access via Platform Events.
Performance Patterns
Loading Strategies:
Lazy Loading: Load on demand. Best for galleries where users view few files. Pros: Fast initial load. Cons: Delay on click.
Eager Loading: Load everything upfront. Best for single-file displays. Pros: Instant display. Cons: Slow if unused.
Hybrid: Load thumbnails eager, full files lazy. Best for most cases. Pros: Fast previews + quality on demand.
Batch Processing Trick:
Don't process files in batch execute() - heap limit applies to entire chunk. Instead, batch creates Queueable jobs for each file. Each Queueable gets its own 12MB heap limit. Failures isolated, better error reporting.
Decision Framework
File Size:
Under 50KB → Base64 in custom fields
50KB - 4MB → ContentVersion (careful heap management)
4MB - 16MB → External Services (Winter '26)
Over 16MB → External storage (S3, Drive)
Access Pattern:
Internal users only → ContentVersion + URLs
External/guest users → Base64 or ContentDistribution
Frequent reads → URLs with CDN
Rare reads → Either works
Processing Needs:
Display only → URLs
Text extraction → External API
Image manipulation → Client-side pre-upload
Complex operations → External worker service
Top 10 Disasters
- Processing in triggers: Always queue async. Triggers have strict limits and shared heap.
- Assuming UTF-8: Windows users upload various encodings. Explicitly handle character encoding.
- No ContentDocument cleanup: Deleting records doesn't delete files. Storage costs explode.
- Loading all files together: Querying VersionData for multiple files blows heap. One at a time.
- Trusting file extensions: Extensions lie. Check actual MIME type.
- No async error handling: Queueable fails silently. Implement logging and notifications.
- Forgetting IsLatest: ContentVersion creates versions on update. Always use IsLatest = true.
- Client-side validation only: Users bypass it. Always re-validate in Apex.
- Not testing edge cases: Test corrupted, oversized, wrong format, concurrent uploads.
- Hardcoding file types: Use Custom Metadata to switch URL/Base64. Build for migration.
The Reality Check
There Is No Perfect Solution
Every file handling approach is a compromise. URLs have auth complexity. Base64 has size limits. ContentVersion has query overhead. External storage has integration costs.
What Works:
Start with ContentVersion + URLs unless you have specific reasons
Move to Base64 for small, frequently-accessed community images
Always process files asynchronously
Plan for cleanup from day one
Test with realistic sizes and formats, not toy examples
Use Custom Metadata to switch approaches - you WILL need to refactor
Treat File Architecture as a Strategic Decision
In practice, file handling decisions should be driven not only by technical constraints but by business context, expected scale, and long-term maintainability. The "right" approach depends on how often files are accessed, who needs access, how they are processed, and how the system is expected to evolve. A community site serving 10,000 guest users needs different architecture than an internal app for 50 employees. A document management system processing 1,000 PDFs daily needs different infrastructure than a profile picture uploader.
Treat file architecture as a foundational decision — not an implementation detail — because changing it later is expensive. Migrating from Attachment to ContentVersion means rebuilding sharing logic and access patterns. Switching from Base64 to URLs requires data migration and front-end refactoring. Moving from on-platform storage to external services impacts authentication, performance, and compliance.
File handling is where theory meets reality and theory loses. Build for edge cases, test failures, never trust documentation examples. Production files are malformed, oversized, and uploaded by users who don't understand file types. Your architecture needs to handle that, not the happy path. Make architectural decisions early, document the trade-offs, and build flexibility to adapt when requirements inevitably change.

