boundary delimiter when you append file objects to a standard FormData instance.
Enterprise applications often require streaming files directly from the user's browser to third-party endpoints—such as Amazon S3, Azure Blob Storage, Box, or external OCR microservices. Rather than routing large files through Apex and consuming restrictive heap size limits, you can construct and dispatch multipart uploads directly from an LWC component using the standard FormData and fetch() APIs.
1. How Multipart Form Data & Boundaries Work
When sending files over HTTP, the request needs a mechanism to separate text metadata from raw binary file streams:
- The Boundary Delimiter: A unique string that marks the start and end of every field and file part in the request body.
- The
FormDataInterface: A native browser utility that collects key-value pairs and binary blobs. - Automatic Header Generation: When you pass a
FormDataobject tofetch(), the browser automatically configures theContent-Type: multipart/form-data; boundary=...header, calculating exact byte boundaries on the fly.
- Payload Container: Native JavaScript
new FormData(). - HTTP Client: Modern
fetch(url, options)withasync/await. - Salesforce Network Rule: External domain must be whitelisted in Setup > CSP Trusted Sites (
connect-src). - UX Feedback: Progress tracking via
<lightning-spinner>and alerts viaShowToastEvent.
2. Step-by-Step Implementation
Below is a complete, production-ready component that accepts local files, validates attachments, packages the data into a FormData container, and sends it via fetch().
fileUploaderMultipart.html)
<template>
<lightning-card title="Direct Multipart File Upload" icon-name="doctype:attachment">
<div class="slds-p-around_medium">
<!-- Loading Spinner -->
<template lwc:if={isUploading}>
<lightning-spinner alternative-text="Uploading file..." size="small"></lightning-spinner>
</template>
<lightning-input
type="file"
label="Select Document"
accept=".pdf,.png,.jpg,.jpeg,.docx"
onchange={handleFileSelection}>
</lightning-input>
<template lwc:if={selectedFileName}>
<div class="slds-box slds-theme_shade slds-m-vertical_small">
<p>Selected File: <strong>{selectedFileName}</strong> ({selectedFileSize})</p>
</div>
</template>
<lightning-button
label="Upload File"
variant="brand"
icon-name="utility:upload"
disabled={isUploadDisabled}
onclick={handleFileUpload}>
</lightning-button>
</div>
</lightning-card>
</template>
fileUploaderMultipart.js)
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class FileUploaderMultipart extends LightningElement {
fileToUpload = null;
selectedFileName = '';
selectedFileSize = '';
isUploading = false;
get isUploadDisabled() {
return !this.fileToUpload || this.isUploading;
}
handleFileSelection(event) {
const files = event.target.files;
if (files && files.length > 0) {
this.fileToUpload = files[0];
this.selectedFileName = this.fileToUpload.name;
// Format file size
const sizeInKb = (this.fileToUpload.size / 1024).toFixed(2);
this.selectedFileSize = `${sizeInKb} KB`;
}
}
async handleFileUpload() {
if (!this.fileToUpload) {
return;
}
this.isUploading = true;
// 1. Build standard FormData payload
const formData = new FormData();
formData.append('file', this.fileToUpload, this.fileToUpload.name);
formData.append('uploadedFrom', 'Salesforce_LWC');
formData.append('timestamp', new Date().toISOString());
const targetEndpoint = 'https://api.externalstorage.com/v1/upload';
try {
// 2. Dispatch request via Fetch API
// NOTE: Do NOT manually set Content-Type header; the browser sets boundary automatically
const response = await fetch(targetEndpoint, {
method: 'POST',
headers: {
'Accept': 'application/json'
// Add authorization headers if needed
},
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed with HTTP status ${response.status}: ${response.statusText}`);
}
const responseData = await response.json();
console.log('Upload successful:', responseData);
this.showToast('Success', 'File uploaded successfully via multipart form data.', 'success');
this.resetForm();
} catch (error) {
console.error('File Upload Error:', error);
this.showToast('Upload Error', error.message || 'Network error during file transfer.', 'error');
} finally {
this.isUploading = false;
}
}
resetForm() {
this.fileToUpload = null;
this.selectedFileName = '';
this.selectedFileSize = '';
}
showToast(title, message, variant) {
this.dispatchEvent(
new ShowToastEvent({
title: title,
message: message,
variant: variant
})
);
}
}
fileUploaderMultipart.js-meta.xml)
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
3. Salesforce Security & Setup Requirements
To enable direct browser HTTP requests to external endpoints, you must configure network trust policies in Salesforce:
- Add to CSP Trusted Sites: Navigate to Setup > Security > CSP Trusted Sites and register the destination base URL (e.g.,
https://api.externalstorage.com). - Enable
connect-src: Check the connect-src directive on the CSP Trusted Site record to permit asynchronous Fetch and XHR network traffic.
4. Common Traps & Development Best Practices
Content-Type Header on FormDataManually setting
headers: { 'Content-Type': 'multipart/form-data' } or adding a hardcoded boundary string breaks the request. The browser needs to generate its own internal boundary parameter to match the exact multipart format. When using FormData with fetch(), omit the Content-Type header completely so the browser assigns the correct header and matching boundary string automatically.
XMLHttpRequest Instead of fetch()Writing raw
XMLHttpRequest (XHR) boilerplate is outdated and error-prone compared to modern ECMAScript standards. Use the promise-based fetch() API with async/await for cleaner code readability, standardized error catching, and compatibility with Lightning Web Security (LWS).
- Client vs. Server-Side Routing: Direct browser uploads are ideal for non-sensitive public uploads or signed URL architectures (like AWS S3 Presigned URLs). If the upload requires secret API tokens, delegate the request to an Apex callout backed by Named Credentials.
- File Size Validation: Always validate
file.sizeon the client side before callingfetch()to reject oversized files before consuming network bandwidth. - Disable Action Controls: Toggle the upload button's
disabledstate and display a<lightning-spinner>during transit to prevent duplicate file submissions.
Summary
Uploading files using multipart/form-data in Lightning Web Components provides a modern, high-performance way to send documents directly from the user's browser to external endpoints. By combining the native FormData interface with the fetch() API and CSP whitelisting, developers can build scalable file uploaders while bypassing Apex heap limits entirely.