multipart/form-data HTTP request is the standard way to send binary files (like PDFs or images) along with text parameters to an external server. In Salesforce, you can achieve this in two ways: via LWC directly from the browser (which is fast and avoids server limits), or via Apex (which is highly secure and perfect for system-to-system integrations).
Integrating Salesforce with external document systems like AWS S3, Google Drive, or custom OCR APIs almost always requires uploading files. Standard JSON REST APIs break when handling raw binary data, so developers must use the multipart/form-data content type. However, building these requests requires specific techniques depending on whether you are working on the client-side (LWC) or the server-side (Apex).
📌 Key Points
- LWC (Client-Side): Uses the native browser
FormDataAPI andfetch()to automatically generate boundaries and stream files without hitting Apex heap limits. - Apex (Server-Side): Requires manually building the request boundaries and using hex encoding to safely convert strings and binary blobs without corrupting the file.
- Security: Apex is best when you need to hide secret API keys using Named Credentials. LWC is best for pre-signed URLs or public endpoints.
- CSP Requirements: LWC callouts require whitelisting the external URL in Salesforce's CSP Trusted Sites.
🖥️ 1. The LWC Approach (Client-Side Uploads)
Streaming files directly from the user's browser is incredibly efficient. It bypasses the Salesforce server entirely, meaning you don't have to worry about the strict 6 MB synchronous Apex heap limit. Modern JavaScript handles the heavy lifting automatically.
fileUploader.js)This code grabs a local file, packages it into a
FormData container, and sends it using the modern fetch() API.
import { LightningElement } from 'lwc';
export default class FileUploader extends LightningElement {
fileToUpload = null;
handleFileSelection(event) {
if (event.target.files.length > 0) {
this.fileToUpload = event.target.files[0];
}
}
async uploadFile() {
if (!this.fileToUpload) return;
// 1. Build the payload container
const formData = new FormData();
formData.append('file', this.fileToUpload, this.fileToUpload.name);
formData.append('description', 'Uploaded via LWC');
try {
// 2. Dispatch via Fetch API
// Note: DO NOT set the Content-Type header manually!
const response = await fetch('https://api.externalstorage.com/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
console.log('Upload successful!');
}
} catch (error) {
console.error('Upload failed:', error);
}
}
}
Do not add
headers: { 'Content-Type': 'multipart/form-data' } to your fetch() request! The browser must generate a unique "boundary string" to separate the file chunks. If you force the header manually, the browser won't append the boundary, and the external server will reject the file as corrupted. Let the browser do it automatically.
☁️ 2. The Apex Approach (Server-Side Uploads)
If you need to upload a file already stored in Salesforce (like a ContentVersion record), or if the external API requires a secret authentication token that you cannot expose in LWC, you must use Apex. Because Apex doesn't have a native FormData builder, you have to construct the binary payload manually using Hex encoding.
MultipartService.cls)This method safely combines text boundaries and binary file blobs using
EncodingUtil.convertToHex().
public with sharing class MultipartService {
public static HttpResponse sendFile(String fileName, Blob fileBody, String mimeType) {
// 1. Generate a unique boundary
String boundary = '----Boundary' + String.valueOf(Crypto.getRandomLong());
// 2. Build text headers and footers
String header = '--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="file"; filename="' + fileName + '"\r\n' +
'Content-Type: ' + mimeType + '\r\n\r\n';
String footer = '\r\n--' + boundary + '--\r\n';
// 3. Convert parts to Hex so they don't corrupt the binary data when combined
String headerHex = EncodingUtil.convertToHex(Blob.valueOf(header));
String bodyHex = EncodingUtil.convertToHex(fileBody);
String footerHex = EncodingUtil.convertToHex(Blob.valueOf(footer));
// 4. Combine and decode back into a final binary Blob
Blob payload = EncodingUtil.convertFromHex(headerHex + bodyHex + footerHex);
// 5. Send using Named Credentials
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_External_API/upload');
req.setMethod('POST');
req.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
req.setHeader('Content-Length', String.valueOf(payload.size()));
req.setBodyAsBlob(payload);
return new Http().send(req);
}
}
A common mistake is using
EncodingUtil.base64Encode(fileBody) and combining it with standard strings using the + operator, then setting the request via req.setBody(). This sends literal Base64 text characters to the server, not a binary file! Always assemble via hex strings and use req.setBodyAsBlob().
- Use LWC When: You want to upload massive files (bypassing Apex heap limits) directly from the user's PC to an endpoint that supports CORS or pre-signed URLs (like AWS S3).
- Use Apex When: The file is already saved in Salesforce (
ContentVersion) or when you need to use Named Credentials to keep your API keys securely hidden on the server. - Apex Limits: Hex conversion doubles memory usage. Keep synchronous Apex uploads under 3 MB, or use Queueable Apex for files up to 6 MB.
🎤 3. Core Interview Q&A
Q: What exactly is a "boundary" in a multipart request?
Q: If I do the upload via LWC using fetch(), why is my request being blocked by Salesforce?
Answer: By default, Salesforce blocks outgoing HTTP requests from the browser for security reasons. You must navigate to Setup > CSP Trusted Sites, add the external API's URL, and ensure you check the box for connect-src to allow API callouts.
🔗 4. Follow-Up Questions to Connect the Dots
Q: Can I use Salesforce Named Credentials inside an LWC fetch() request?
Answer: No. Named Credentials are a server-side Salesforce feature designed to keep authentication tokens secure. If you use LWC's fetch(), the request originates from the user's browser, which has no access to Named Credentials. If the external API requires secure, hidden API keys, you must route the upload through an Apex controller.
Q: How can we handle files larger than 12 MB in Salesforce?
Answer: Since Apex completely maxes out at a 12 MB asynchronous heap limit, you cannot upload larger files by storing them in memory. The best architectural solution is to have Apex request a "Pre-Signed URL" from your external storage provider (like AWS S3) and pass that temporary URL to LWC. LWC then streams the massive file directly from the user's computer to AWS, entirely bypassing Salesforce limitations.
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment