Introduction
File handling is an essential part of many Salesforce applications, especially when working with records like Accounts, Opportunities, or Cases. Salesforce developers frequently implement file download features in Lightning Web Components (LWC). They do this when downloading an file from a record or fetching content from an external system.
In this blog, we’ll explore below powerful methods for downloading files using LWC:
- Download Files from Attachments Object
- Download Files From Files Object (Content Document)
- Downloading files by calling a REST API directly from LWC
🎯 Scenario 1: Download Files from Attachments Object
Use Case 1- Download Single File From Attachment
a) Example – Using Salesforce URL in browser
This example shows you how to download the most recent Attachment related to a Salesforce Account record.It uses a standard Salesforce file download URL. The URL is constructed in Apex. It is returned to the Lightning Web Component. The component then opens it in the browser to trigger the download.
This approach is ideal for working with native Salesforce attachments. It helps avoid dealing with file blobs or base64 encoding. It leverages Salesforce’s built-in file-serving capabilities via the /servlet/servlet.FileDownload endpoint.
| <template> | |
| <lightning-card title="Download Single Attachment" icon-name="utility:download"> | |
| <div class="slds-p-around_medium"> | |
| <template if:true={isLoading}> | |
| <lightning-spinner alternative-text="Loading …" size="medium"></lightning-spinner> | |
| </template> | |
| <lightning-button | |
| label="Download" | |
| onclick={handleDownload} | |
| disabled={isLoading} | |
| ></lightning-button> | |
| </div> | |
| </lightning-card> | |
| </template> |
| /** | |
| * What it does: | |
| The component uses the recordId passed as parameter to the apex class, to get the related attachments. | |
| When the Download button is clicked: | |
| A loading spinner is shown by setting isLoading = true. | |
| It calls the Apex method to get the file download URL. | |
| It uses window.open() to open the download link in a new browser tab or window. | |
| Spinner is hidden once the download link is opened or in case of an error. | |
| */ | |
| import { LightningElement, api } from 'lwc'; | |
| import getAttachmentDownloadUrl from '@salesforce/apex/FilesDownloaderCtrl.getAttachmentDownloadUrl'; | |
| export default class FilesDownloader extends LightningElement { | |
| @api recordId; | |
| isLoading = false; | |
| /** | |
| * @description: handleDownload method handles the download of attachments related to the record. | |
| * It uses the recordId to fetch the download URL of Attachment from Apex | |
| * and triggers the download process. | |
| * @returns {Promise<void>} – Resolves when the download is complete or fails. | |
| */ | |
| async handleDownload() { | |
| // Set loading state to true to show spinner | |
| this.isLoading = true; | |
| try { | |
| // Call Apex method to fetch the download URL for the attachments related to the recordId | |
| // this waits for Apex method to return the download URL | |
| const url = await getAttachmentDownloadUrl({ parentId: this.recordId }); | |
| // Open the download link in a new tab or window | |
| window.open(url, '_blank'); | |
| } catch (error) { | |
| // Handle any errors that occur during the download process | |
| console.error('Download error:', error); | |
| } finally { | |
| // Ensure the loading state is reset regardless of success or failure | |
| this.isLoading = false; | |
| } | |
| } | |
| } |
| /** | |
| * What it does: | |
| The method getAttachmentDownloadUrl(): | |
| Accepts a parentId, which is the ID of the record (e.g., a Case or Account) whose attachment we want to fetch. | |
| Queries for the most recently modified Attachment associated with that record. | |
| Constructs and returns a standard Salesforce file download URL using the Attachment ID: | |
| /servlet/servlet.FileDownload?file=<AttachmentId> | |
| This URL will trigger the browser to download the file when opened. | |
| */ | |
| public with sharing class FilesDownloaderCtrl { | |
| /** | |
| * @description: This method retrieves the download URL for the latest attachment related to a given record. | |
| * @param parentId – The ID of the record for which the latest attachment's download URL is to be fetched. | |
| * @returns {String} – The download URL of the latest | |
| */ | |
| @AuraEnabled | |
| public static String getAttachmentDownloadUrl(Id parentId) { | |
| // Query the latest attachment related to the given parentId | |
| Attachment att = [SELECT Id, Name FROM Attachment WHERE parentId = :parentId ORDER BY LastModifiedDate DESC LIMIT 1]; | |
| // Return the download URL for the latest attachment found | |
| return '/servlet/servlet.FileDownload?file=' + att.Id; | |
| } | |
| } |
When to Use This Approach:
- You’re working with standard
Attachmentobjects (notContentDocument). - You want to avoid using
fetch()or manually handling file streams. - You want a clean, server-assisted file download mechanism.
- The file is already stored in Salesforce, and you simply need to expose a download link.
b) Example – Using Base64 File
This example demonstrates how to download the most recent Attachment related to a Salesforce record using base64 encoding. The file is fetched from an Apex controller as a base64-encoded string. It is then converted into a Blob. Finally, it is downloaded directly in the browser.
This approach is ideal when you need to inject client-side logic before triggering the download. For instance, you might handle filename formatting, validation, or enforcing conditional access. It gives you full control over the download process through JavaScript. This control makes it particularly useful for mobile-first applications. It is also beneficial in scenarios that require enhanced interactivity and flexibility on the client side.
| <template> | |
| <lightning-card title="Download Single Attachment" icon-name="utility:download"> | |
| <div class="slds-p-around_medium"> | |
| <template if:true={isLoading}> | |
| <lightning-spinner alternative-text="Loading …" size="medium"></lightning-spinner> | |
| </template> | |
| <lightning-button | |
| label="Download" | |
| onclick={handleDownload} | |
| disabled={isLoading} | |
| ></lightning-button> | |
| </div> | |
| </lightning-card> | |
| </template> |
| /** | |
| * What it does: | |
| The component uses the recordId passed as parameter to the apex class, to get the related attachments. | |
| When the user clicks the Download button: | |
| It shows a loading spinner. | |
| Calls the Apex method to fetch the latest attachment’s metadata and base64 content. | |
| Converts the base64 string back to a binary Blob object using the base64ToBlob() method. | |
| Creates a temporary anchor (<a>) element to trigger the file download with a proper filename. | |
| Cleans up the blob URL and DOM after the download is triggered. | |
| */ | |
| import { LightningElement, api } from 'lwc'; | |
| import getAttachmentAsBlob from '@salesforce/apex/FilesDownloaderCtrl.getAttachmentAsBlob'; | |
| export default class FilesDownloader extends LightningElement { | |
| @api recordId; | |
| isLoading = false; | |
| /** | |
| * @description: handleDownload method handles the download of the latest attachment for the given recordId. | |
| * It fetches the attachment data from Apex, converts it to a Blob, | |
| * and triggers the download process. | |
| * @returns {Promise<void>} – Resolves when the download is complete or fails. | |
| */ | |
| async handleDownload() { | |
| // Set loading state to true to show spinner | |
| this.isLoading = true; | |
| try { | |
| // Call Apex method to get the attachment as Blob | |
| // this waits for Apex to return the blob | |
| const result = await getAttachmentAsBlob({ parentId: this.recordId }); | |
| // Convert Base64 to Blob | |
| const blob = this.base64ToBlob(result.base64Data, result.mimeType || 'application/octet-stream'); | |
| // Create a URL for the Blob | |
| const url = URL.createObjectURL(blob); | |
| // Create a temporary anchor element to trigger the download | |
| const link = document.createElement('a'); | |
| // Set the href and download attributes | |
| link.href = url; | |
| // Use the filename from the result or a default name | |
| link.download = result.fileName || 'DownloadedFile'; | |
| // Append the link to the body | |
| document.body.appendChild(link); | |
| // Trigger the download | |
| link.click(); | |
| // Clean up: remove the link and revoke the object URL | |
| document.body.removeChild(link); | |
| // Revoke the object URL to free up memory | |
| URL.revokeObjectURL(url); | |
| } catch (error) { | |
| // Handle any errors that occur during the download process | |
| console.error('Download error:', error); | |
| } finally { | |
| // Ensure the loading state is reset regardless of success or failure | |
| this.isLoading = false; | |
| } | |
| } | |
| /** | |
| * @description – Converts a Base64 string to a Blob object. | |
| * @param {string} base64 – The Base64 encoded string. | |
| * @param {string} mimeType – The MIME type of the file. | |
| * @returns {Blob} – The Blob object created from the Base64 string. | |
| */ | |
| base64ToBlob(base64, mimeType) { | |
| // Decode Base64 string to binary data | |
| const byteChars = atob(base64); | |
| // Create a Uint8Array from the binary data | |
| const byteArray = new Uint8Array(byteChars.length); | |
| // Loop through the binary data and fill the Uint8Array | |
| for (let i = 0; i < byteChars.length; i++) { | |
| // Convert each character to its char code and assign it to the Uint8Array | |
| byteArray[i] = byteChars.charCodeAt(i); | |
| } | |
| // Create and return a Blob object with the binary data and specified MIME type | |
| return new Blob([byteArray], { type: mimeType }); | |
| } | |
| } |
| /** | |
| * What it does: | |
| Defines a nested FileWrapper class that holds: | |
| fileName: Name of the file. | |
| mimeType: MIME type like application/pdf or image/png. | |
| base64Data: Encoded file content as a base64 string. | |
| The method getAttachmentAsBlob(): | |
| Fetches the most recently updated Attachment record for the given parent record. | |
| Converts the binary content (Body) to base64. | |
| Wraps it into a FileWrapper object and returns it to the frontend. | |
| */ | |
| public with sharing class FilesDownloaderCtrl { | |
| public class FileWrapper { | |
| @AuraEnabled public String fileName; | |
| @AuraEnabled public String mimeType; | |
| @AuraEnabled public String base64Data; | |
| } | |
| /** | |
| * @description – Fetches the most recent Attachment for a given parent record ID. | |
| * Returns the attachment data wrapped in a FileWrapper object. | |
| * @param – parentId The ID of the parent record to fetch attachments for. | |
| * @return – A FileWrapper object containing the attachment details. | |
| */ | |
| @AuraEnabled | |
| public static FileWrapper getAttachmentAsBlob(Id parentId) { | |
| Attachment att = [ | |
| SELECT Id, Name, Body, ContentType | |
| FROM Attachment | |
| WHERE ParentId = :parentId | |
| ORDER BY LastModifiedDate DESC | |
| LIMIT 1 | |
| ]; | |
| // Create a FileWrapper object to return | |
| FileWrapper fileData = new FileWrapper(); | |
| fileData.fileName = att.Name; | |
| fileData.mimeType = att.ContentType; | |
| fileData.base64Data = EncodingUtil.base64Encode(att.Body); | |
| return fileData; | |
| } | |
| } |
When to Use This Approach:
- You want to avoid opening a new browser tab with
window.open(). - You need to customize the file download logic (e.g., add watermarking, enforce client-side conditions).
- You’re working with older Salesforce attachments and not
ContentDocument-based Files. - You’re building for environments like Salesforce Mobile App, where URL-based downloads may not work as reliably.
Use Case 2- Downloading All Attachments as a ZIP
When working with Salesforce records like Accounts, it’s common to have multiple related attachments. This example demonstrates how to let users download all attachments as a single ZIP file with a simple click.
| <template> | |
| <lightning-card title="Download All Attachment As Zip" icon-name="utility:download"> | |
| <div class="slds-p-around_medium"> | |
| <template if:true={isLoading}> | |
| <lightning-spinner alternative-text="Loading ZIP…" size="medium"></lightning-spinner> | |
| </template> | |
| <lightning-button | |
| label="Download" | |
| onclick={handleDownload} | |
| disabled={isLoading} | |
| ></lightning-button> | |
| </div> | |
| </lightning-card> | |
| </template> |
| /** | |
| * What it does: | |
| The component used the recordId passed as parameter to the Apex class, to get the related attachments. | |
| When the Download button is clicked: | |
| A loading spinner is shown by setting isLoading = true. | |
| It calls the Apex method to get the attachments as a ZIP file. | |
| It converts the Base64-encoded ZIP file to a Blob. | |
| It creates a download link for the ZIP file and triggers the download. | |
| Spinner is hidden once the download is initiated or in case of an error. | |
| */ | |
| import { LightningElement, api } from 'lwc'; | |
| import getAttachmentsAsZip from '@salesforce/apex/FilesDownloaderCtrl.getAttachmentsAsZip'; | |
| export default class FilesDownloader extends LightningElement { | |
| @api recordId; | |
| isLoading = false; | |
| /** | |
| * @description: handleDownload method handles the download of attachments related to the record. | |
| * It uses the recordId to fetch the download URL of Attachment from Apex | |
| * and triggers the download process. | |
| * @returns {Promise<void>} – Resolves when the download is complete or fails. | |
| */ | |
| async handleDownload() { | |
| debugger; | |
| this.isLoading = true; | |
| try { | |
| // Call Apex method to get the Base64-encoded ZIP file | |
| // this waits for Apex method to return the Base64-encoded ZIP file | |
| const base64Zip = await getAttachmentsAsZip({ parentId: this.recordId }); | |
| // Convert Base64 to Blob | |
| const blob = this.base64ToBlob(base64Zip, 'application/zip'); | |
| // Create a URL for the Blob | |
| const url = URL.createObjectURL(blob); | |
| // Create a temporary anchor element to trigger the download | |
| const link = document.createElement('a'); | |
| // Set the href and download attributes | |
| link.href = url; | |
| // Set the filename for the downloaded file | |
| link.download = 'Attachments.zip'; | |
| // Trigger the download | |
| link.click(); | |
| // Clean up the URL object | |
| URL.revokeObjectURL(url); | |
| } catch (error) { | |
| // Handle any errors that occur during the download process | |
| console.error('Download failed:', error); | |
| } finally { | |
| // Ensure the loading state is reset regardless of success or failure | |
| this.isLoading = false; | |
| } | |
| } | |
| /** | |
| * @description: Converts a Base64-encoded string to a Blob object. | |
| * @param {string} base64 – The Base64-encoded string to convert. | |
| * @param {string} mime – The MIME type of the Blob. | |
| * @returns {Blob} – The resulting Blob object. | |
| */ | |
| base64ToBlob(base64, mime) { | |
| // Decode Base64 string to binary data | |
| const byteChars = atob(base64); | |
| // Create a Uint8Array from the binary data | |
| const byteNumbers = new Array(byteChars.length); | |
| // Loop through the binary data and convert it to byte numbers | |
| for (let i = 0; i < byteChars.length; i++) { | |
| // Convert each character to its char code | |
| byteNumbers[i] = byteChars.charCodeAt(i); | |
| } | |
| // Create a Uint8Array from the byte numbers and return as a Blob | |
| const byteArray = new Uint8Array(byteNumbers); | |
| // return a new Blob object with the byte array and specified MIME type | |
| return new Blob([byteArray], { type: mime }); | |
| } | |
| } |
| /** | |
| * What it does: | |
| The method getAttachmentsAsZip(): | |
| Accepts a parentId, which is the ID of the record (e.g., a Case or Account) whose attachments we want to fetch. | |
| Queries for all attachments associated with that record. | |
| Uses the Compression.ZipWriter to create a ZIP archive containing all attachments. | |
| Returns the ZIP file as a Base64-encoded string, which can be used in a Lightning Web Component (LWC) to trigger a download. | |
| */ | |
| public with sharing class FilesDownloaderCtrl { | |
| /** | |
| * @description: This method retrieves all attachments related to a given record and returns them as a ZIP file. | |
| * @param parentId – The ID of the record for which attachments are to be fetched. | |
| * @returns {String} – A Base64-encoded string representing the ZIP file containing | |
| */ | |
| @AuraEnabled | |
| public static String getAttachmentsAsZip(Id parentId) { | |
| // Retrieve all attachments related to the specified parentId | |
| List<Attachment> attachments = [ | |
| SELECT Id, Name, Body FROM Attachment | |
| WHERE ParentId = :parentId | |
| ]; | |
| // Declare a map to hold file names and their corresponding Blob data | |
| Map<String, Blob> fileMap = new Map<String, Blob>(); | |
| // Loop through the attachments and populate the fileMap | |
| for (Attachment attachmentFile : attachments) { | |
| fileMap.put(attachmentFile.Name, attachmentFile.Body); | |
| } | |
| // Declare a ZipWriter to create the ZIP archive | |
| Compression.ZipWriter zip = new Compression.ZipWriter(); | |
| // Loop through the fileMap and add each file to the ZIP archive | |
| for (String fileName : fileMap.keySet()) { | |
| zip.addEntry(fileName, fileMap.get(fileName)); | |
| } | |
| // Get the ZIP archive as a Blob | |
| Blob zipBlob = zip.getArchive(); | |
| // Return the ZIP file as a Base64-encoded string | |
| return EncodingUtil.base64Encode(zipBlob); | |
| } | |
| } |
🎯 Scenario 2: Download Files From Files Object (Content Document)
Use Case 1- Download Single File From Content Version
This example shows how to download the most recent File related to a Salesforce record. It is stored as a ContentDocument / ContentVersion. This is done using a base64-to-blob approach. The file is retrieved from the Salesforce Files object. It is encoded as a Base64 string via Apex. Then it is converted to a Blob in JavaScript. Finally, it is downloaded in the browser.
This approach is ideal for modern file storage in Salesforce. It supports the ContentDocument model, which is used by Lightning Files and Notes. It also gives you fine-grained control over the download process within an LWC.
| .spinner-container { | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 120px; /* Adjust as needed for vertical centering */ | |
| width: 100%; | |
| } | |
| .spinner-text { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 1rem; | |
| font-weight: bold; | |
| color: black; | |
| } |
| <template> | |
| <lightning-card title="Download File From Files (Content Document)" icon-name="utility:download"> | |
| <div class="slds-p-around_medium"> | |
| <template if:true={isLoading}> | |
| <div class="spinner-container"> | |
| <lightning-spinner alternative-text="Loading ZIP…" size="large"></lightning-spinner> | |
| <div class="spinner-text">{progress}%</div> | |
| </div> | |
| </template> | |
| <lightning-button | |
| label="Download" | |
| onclick={handleDownload} | |
| disabled={isLoading} | |
| ></lightning-button> | |
| </div> | |
| </lightning-card> | |
| </template> |
| /** | |
| * What it does: | |
| The component uses the recordId passed as parameter to the apex class, to get the related files | |
| When the user clicks the Download button: | |
| It shows a loading spinner. | |
| It calls the Apex method getFileAsBlob to get the file as a Blob. | |
| It creates a download link and triggers the download. | |
| It updates the progress of the download. | |
| */ | |
| /* eslint-disable @lwc/lwc/no-async-operation */ | |
| /* eslint-disable no-await-in-loop */ | |
| import { LightningElement, api } from 'lwc'; | |
| import getFileAsBlob from '@salesforce/apex/FilesDownloaderCtrl.getFileAsBlob'; | |
| export default class FilesDownloader extends LightningElement { | |
| @api recordId; | |
| isLoading = false; | |
| progress = 0; // Initialize progress to 0 | |
| /** | |
| * @description – handleDownload method handles the download of a file associated with a record. | |
| * It retrieves the file as a Blob from the server, creates a download link, and triggers the download. | |
| * @returns {Promise<void>} – Resolves when the download is complete or fails. | |
| */ | |
| async handleDownload() { | |
| this.isLoading = true; | |
| this.progress = 0; // Reset progress | |
| try { | |
| // Call the Apex method to get the file as a Blob | |
| const result = await getFileAsBlob({ parentId: this.recordId }); | |
| // Convert Base64 to Blob | |
| const blob = await this.base64ToBlob(result.base64Data, result.mimeType || 'application/octet-stream'); | |
| // Create a URL for the Blob | |
| const url = URL.createObjectURL(blob); | |
| // Create a temporary anchor element to trigger the download | |
| const link = document.createElement('a'); | |
| // Set the href | |
| link.href = url; | |
| // Use the filename from the result or a default name | |
| link.download = result.fileName || 'DownloadedFile'; | |
| // Append the link to the body | |
| document.body.appendChild(link); | |
| // Trigger the download | |
| link.click(); | |
| // Clean up: remove the link and revoke the object URL | |
| document.body.removeChild(link); | |
| // Revoke the object URL to free up memory | |
| URL.revokeObjectURL(url); | |
| } catch (error) { | |
| // Handle any errors that occur during the download process | |
| console.error('Download error:', error); | |
| } finally { | |
| // Ensure the loading state is reset regardless of success or failure | |
| this.isLoading = false; | |
| // Reset progress to 0 | |
| this.progress = 0; | |
| } | |
| } | |
| /** | |
| * @description – Converts a base64 string to a Blob object. | |
| * @param {string} base64 – The base64 encoded string of the file. | |
| * @param {string} mimeType – The MIME type of the file. | |
| * @returns {Promise<Blob>} – A Promise that resolves to a Blob object. | |
| */ | |
| async base64ToBlob(base64, mimeType) { | |
| // Decode the base64 string | |
| const byteChars = atob(base64); | |
| // Calculate the total number of bytes | |
| const totalBytes = byteChars.length; | |
| //Declare chunk size based on file size | |
| let chunkSize; | |
| // Calculate the file size in MB | |
| const fileSizeMB = totalBytes / (1024 * 1024); | |
| // Set chunk size dynamically | |
| if (fileSizeMB < 2) { | |
| chunkSize = 1024; // Small chunk size | |
| } else if (fileSizeMB < 5) { | |
| chunkSize = 4096; // Medium chunk size | |
| } else if (fileSizeMB < 10) { | |
| chunkSize = 8192; // Large chunk size | |
| } else { | |
| chunkSize = 16384; // Very large chunk size | |
| } | |
| // Create an array to hold the byte arrays | |
| const byteArrays = []; | |
| // Process the byte characters in chunks | |
| let offset = 0; | |
| // Loop through the byte characters and create Uint8Array chunks | |
| while (offset < totalBytes) { | |
| // Slice the byte characters into chunks | |
| const slice = byteChars.slice(offset, offset + chunkSize); | |
| // Convert the slice to a byte array | |
| const byteNumbers = new Array(slice.length); | |
| // Loop through the slice and convert each character to its char code | |
| for (let i = 0; i < slice.length; i++) { | |
| byteNumbers[i] = slice.charCodeAt(i); | |
| } | |
| // Create a Uint8Array from the byte numbers | |
| const byteArray = new Uint8Array(byteNumbers); | |
| // Push the Uint8Array to the byteArrays array | |
| byteArrays.push(byteArray); | |
| // Update the progress | |
| this.progress = Math.min(100, Math.floor((offset / totalBytes) * 100)); | |
| // Allow the UI to update | |
| await new Promise(resolve => setTimeout(resolve, 0)); | |
| // Move the offset forward by the chunk size | |
| offset += chunkSize; | |
| } | |
| // Set progress to 100% when done | |
| this.progress = 100; | |
| // Create and return a Blob from the byte arrays | |
| return new Blob(byteArrays, { type: mimeType }); | |
| } | |
| } |
| /** | |
| * What it does: | |
| Defines a nested FileWrapper class that holds: | |
| fileName: Name of the file. | |
| mimeType: MIME type like application/pdf or image/png. | |
| base64Data: Encoded file content as a base64 string. | |
| The method getFileAsBlob(): | |
| Fetches the most recent ContentDocumentLink for the given parentId. | |
| Retrieves the latest ContentVersion for that ContentDocumentId. | |
| Converts the binary data to a Base64 string. | |
| Wraps it into a FileWrapper object and returns it to the frontend. | |
| */ | |
| public with sharing class FilesDownloaderCtrl { | |
| public class FileWrapper { | |
| @AuraEnabled public String fileName; | |
| @AuraEnabled public String mimeType; | |
| @AuraEnabled public String base64Data; | |
| } | |
| /** | |
| * @description – This method retrieves the latest ContentDocumentLink for the given parentId. | |
| * – It then retrieves the latest ContentVersion for that ContentDocumentId. | |
| * @param parentId – The ID of the record for which attachments are to be fetched. | |
| * @return {FileWrapper} – A FileWrapper object containing the file name, Base64-encoded data, and MIME type of the attachment. | |
| */ | |
| @AuraEnabled | |
| public static FileWrapper getFileAsBlob(Id parentId) { | |
| // Query the latest ContentDocumentLink for the given parentId | |
| ContentDocumentLink cdl = [ | |
| SELECT ContentDocumentId | |
| FROM ContentDocumentLink | |
| WHERE LinkedEntityId = :parentId | |
| ORDER BY ContentDocument.LatestPublishedVersionId DESC | |
| LIMIT 1 | |
| ]; | |
| // QUery the latest ContentVersion for the ContentDocumentId | |
| ContentVersion cv = [ | |
| SELECT Title, VersionData, FileExtension, FileType | |
| FROM ContentVersion | |
| WHERE ContentDocumentId = :cdl.ContentDocumentId | |
| ORDER BY VersionNumber DESC | |
| LIMIT 1 | |
| ]; | |
| // Create a FileWrapper object to return | |
| FileWrapper fw = new FileWrapper(); | |
| fw.fileName = cv.Title + '.' + cv.FileExtension; | |
| fw.mimeType = getMimeType(cv.FileExtension); | |
| fw.base64Data = EncodingUtil.base64Encode(cv.VersionData); | |
| return fw; | |
| } | |
| /** | |
| * @description – Returns the MIME type based on the file extension. | |
| * @param ext – The file extension (e.g., pdf, png). | |
| * @return {String} – The corresponding MIME type or application/octet-stream if unknown. | |
| */ | |
| private static String getMimeType(String ext) { | |
| Map<String, String> types = new Map<String, String>{ | |
| 'pdf' => 'application/pdf', | |
| 'png' => 'image/png', | |
| 'jpg' => 'image/jpeg', | |
| 'jpeg' => 'image/jpeg', | |
| 'csv' => 'text/csv', | |
| 'txt' => 'text/plain', | |
| 'doc' => 'application/msword', | |
| 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | |
| }; | |
| return types.containsKey(ext.toLowerCase()) ? types.get(ext.toLowerCase()) : 'application/octet-stream'; | |
| } | |
| } |
When to Use This Approach:
- You’re working with Files (
ContentVersion/ContentDocument) instead of legacyAttachmentobjects. - You want to download files without using window-based redirects like
window.open(). - You need programmatic control in LWC (e.g., setting custom file names, MIME types, or conditionally allowing downloads).
- Useful for scenarios in mobile apps, custom file viewer interfaces, or multi-file ZIP packaging logic (via base64 blobs).
🎯 Scenario 3: Downloading files by calling a REST API directly from LWC
In many real-world scenarios, Salesforce developers need to download files from external systems like Amazon S3 / Azure Blob etc. They can achieve this without Apex by using only the client-side fetch API in LWC. This use case shows how to trigger a file download directly from an LWC. The download is a Pdf file, executed using a REST API call.
Note – Lightning Web Security only supports some of the MIME Types. Text/csv is not supported. You need to use Apex for text/csv.
| <template> | |
| <lightning-card title="Download File From Rest Api Response" icon-name="utility:download"> | |
| <div class="slds-p-around_medium"> | |
| <template if:true={isLoading}> | |
| <lightning-spinner alternative-text="Loading ZIP…" size="medium"></lightning-spinner> | |
| </template> | |
| <lightning-button | |
| label="Download" | |
| onclick={handleDownload} | |
| disabled={isLoading} | |
| ></lightning-button> | |
| </div> | |
| </lightning-card> | |
| </template> |
| /** | |
| * What it does: | |
| When the user clicks the Download button: | |
| The component fetches a file from a mock API endpoint and triggers its download. | |
| It uses the Fetch API to retrieve the file, converts it to a Blob, and then creates a temporary link to download the file. | |
| Creates a temporary anchor (<a>) element to trigger the file download with a proper filename. | |
| Cleans up the blob URL and DOM after the download is triggered. | |
| */ | |
| import { LightningElement } from 'lwc'; | |
| export default class FilesDownloader extends LightningElement { | |
| isLoading = false; | |
| /** | |
| * @description – handleDownload method handles the download of a file from a mock API endpoint. | |
| * It fetches the file, converts it to a Blob, and triggers the download process. | |
| * @returns {Promise<void>} – Resolves when the download is complete or fails. | |
| */ | |
| async handleDownload() { | |
| // Set loading state to true to show spinner | |
| this.isLoading = true; | |
| try { | |
| // Fetch the file from the mock API endpoint | |
| const response = await fetch('https://api.mockfly.dev/mocks/Your_Key/test-pdf'); | |
| // Get the response as a Blob | |
| const blob = await response.blob(); | |
| // Create a URL for the Blob | |
| const url = window.URL.createObjectURL(blob); | |
| // Create a temporary anchor element to trigger the download | |
| const link = document.createElement('a'); | |
| // Set the href | |
| link.href = url; | |
| // Set the download attribute with a filename | |
| link.download = 'test-pdf'; | |
| // Trigger the download | |
| link.click(); | |
| // Clean up the URL object | |
| window.URL.revokeObjectURL(url); | |
| } catch (error) { | |
| // Handle any errors that occur during the download process | |
| console.error('Download error:', error); | |
| } finally { | |
| // Ensure the loading state is reset regardless of success or failure | |
| this.isLoading = false; | |
| } | |
| } | |
| } |


Leave a comment