Skip to main content

File Upload in LWC: lightning-file-upload, ContentDocument & Best Practices

In plain words: The <lightning-file-upload> component is a native Salesforce LWC tool that lets users drag and drop or browse files directly from their computer into Salesforce. It automatically handles chunked file uploads, converts files into ContentVersion / ContentDocument records, and links them directly to the active record ID without requiring custom Apex upload endpoints or hitting the 6 MB synchronous heap size limit.

Managing file attachments—such as customer invoices, identification proofs, contracts, and inspection photos—is a standard requirement across Salesforce implementations. Building custom binary file uploaders with Apex controllers often results in heap size exceptions when files exceed 4–5 MB. The standard <lightning-file-upload> component solves this by uploading files directly to Salesforce Files via optimized streaming APIs, supporting large uploads up to 2 GB out of the box.

1. How Salesforce Handles Files Under the Hood

When a user uploads a file using <lightning-file-upload>, Salesforce creates and links three interrelated records automatically:

  • ContentVersion: Stores the binary payload, file extension, and version history.
  • ContentDocument: The parent container holding all versions of the file.
  • ContentDocumentLink: The junction record that links the ContentDocument directly to your target record via the supplied record-id.
360 File Upload Architecture Card:
  • Standard Component: <lightning-file-upload>
  • Maximum File Size: Up to 2 GB per file in Lightning Experience.
  • Supported Formats Filter: accept=".pdf,.png,.jpg,.jpeg,.docx" (comma-separated extensions or MIME types).
  • Multiple Uploads: Controlled by the boolean attribute multiple.
  • Event Payload: event.detail.files returns an array of uploaded file objects containing name, documentId, and contentVersionId.

2. Step-by-Step Implementation: Building a Production-Ready File Uploader

The following example creates a flexible file upload component that restricts file types, handles multiple uploads, displays success/error toasts, and captures the generated file IDs for downstream processing.

Step 1: Create the HTML Template (customFileUploader.html)
<template>
    <lightning-card title="Secure Document Upload" icon-name="standard:file">
        <div class="slds-p-around_medium">
            <lightning-file-upload
                label="Attach Customer Documentation"
                name="fileUploader"
                accept={acceptedFormats}
                record-id={recordId}
                multiple
                onuploadfinished={handleUploadFinished}>
            </lightning-file-upload>

            <!-- Display list of uploaded files in the current session -->
            <template lwc:if={hasFiles}>
                <div class="slds-m-top_medium">
                    <h4 class="slds-text-title_bold slds-m-bottom_x-small">Recently Uploaded Files:</h4>
                    <ul class="slds-list_dotted">
                        <template for:each={uploadedFileList} for:item="file">
                            <li key={file.documentId}>
                                <strong>{file.name}</strong> (Doc ID: {file.documentId})
                            </li>
                        </template>
                    </ul>
                </div>
            </template>
        </div>
    </lightning-card>
</template>
Step 2: Implement the JavaScript Controller (customFileUploader.js)
import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class CustomFileUploader extends LightningElement {
    // Automatically populated when placed on a Lightning Record Page
    @api recordId;

    @track uploadedFileList = [];

    // Define accepted file formats
    get acceptedFormats() {
        return ['.pdf', '.png', '.jpg', '.jpeg', '.docx', '.xlsx'];
    }

    get hasFiles() {
        return this.uploadedFileList.length > 0;
    }

    handleUploadFinished(event) {
        // Retrieve array of uploaded file metadata
        const uploadedFiles = event.detail.files;

        if (uploadedFiles && uploadedFiles.length > 0) {
            // Append newly uploaded files to reactive display list
            this.uploadedFileList = [...this.uploadedFileList, ...uploadedFiles];

            // Notify user with success toast
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Upload Successful',
                    message: `${uploadedFiles.length} file(s) uploaded and linked to record.`,
                    variant: 'success',
                    mode: 'dismissable'
                })
            );

            // Optional: Dispatch custom event to parent or call Apex to update record status
            uploadedFiles.forEach(file => {
                console.log(`Uploaded: ${file.name} | Document ID: ${file.documentId}`);
            });
        }
    }
}
Step 3: Configure Component Metadata (customFileUploader.js-meta.xml)
Expose the component to Lightning App Builder record pages across desktop and mobile.
<?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__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
</LightningComponentBundle>

3. Embedding the Component on Record Pages or Inside Parent Components

To use this component within another custom LWC, instantiate it and pass the record ID dynamically:

<!-- Parent Component Template -->
<template>
    <c-custom-file-uploader record-id={targetAccountId}></c-custom-file-uploader>
</template>

4. Common Traps & Platform Best Practices

Architecture Trap: Passing an Empty or Null record-id
If record-id is null or undefined at the time of upload, Salesforce still creates the ContentDocument and ContentVersion records, but they will not be linked to any parent record. The files will remain orphaned in the user's private library. Always ensure recordId is populated before rendering the upload component.
Core Rule: Use <lightning-file-upload> to stream files up to 2 GB without hitting Apex heap limits, validate the presence of record-id, and process generated documentId tokens inside handleUploadFinished.
  • Experience Cloud Guest User Restrictions: By default, Salesforce restricts unauthenticated guest users from uploading files via <lightning-file-upload>. To allow guest uploads, enable "Allow site guest users to upload files" in Setup under General Settings > Salesforce Files.
  • Enforce Multiple File Control: If your workflow requires exactly one document (such as a single passport photo), omit the multiple attribute so users can select only one file per transaction.
  • Post-Upload Apex Trigger Processing: If you need to classify or rename files automatically upon upload, write an Apex trigger on ContentDocumentLink or ContentVersion rather than building custom HTTP upload endpoints.

Summary

Implementing file uploads with <lightning-file-upload> in Lightning Web Components provides an enterprise-ready, accessible, and high-performance solution. By eliminating custom Apex binary streaming, respecting platform file storage architectures, and handling upload events reactively, developers can deliver seamless document management workflows across Salesforce.