Last Updated on June 9, 2024 by Rakesh Gupta
Big Idea or Enduring Question:
- How to Create and Download Multiple Files as a ZIP Folder in Salesforce?
Objectives:
After reading this blog, you’ll be able to:
- Load and utilize a JavaScript library like JSZip to create ZIP files on the client side.
- Create and configure a Lightning Web Component (LWC) to bundle and download files as a ZIP folder.
- Launch a Lightning Web Component from a quick action on a Case.
- Display success and error messages to users using toast notifications.
- and much more.
In the past, I’ve written a few articles on Lightning Web Component. Why not check them out while you are at it?!
- A Step-by-Step Guide to Merging and Displaying PDFs in Salesforce
- Send Email as a Quick Action Using Lightning Web Component
Business Use case
Jordan Brown is working as a Salesforce Developer at Gurukul on Cloud (GoC). During a conversation with the Support Manager, Jackie Carter, Jordan discovered that Jackie spends a significant amount of time downloading each file from a case and storing them in external storage.
Each case may have one or more files attached to it. As shown in the screenshot below, case 00001012 has three files attached to it.
Jackie asked Jordan to explore and provide a solution that allows her to download all files in a ZIP folder.
What is a Zip File?
A ZIP file is a file format that can contain multiple files combined and compressed into one single file. Files that are zipped have a file extension of .zip. Since it’s a type of compressed file, a ZIP file can be smaller in size than the files it contains. This makes the ZIP file easier and faster to download. Compressing your files helps them take up less space on your computer and can significantly reduce download times, especially when dealing with multiple files or large file sizes.
Benefits of Using ZIP Files in Salesforce
- Efficiency: Instead of downloading each file individually, users can download a single ZIP file, saving time and effort.
- Space Saving: Compressed files take up less space, both in transit and on disk.
- Convenience: All related files are packaged together, ensuring that no file is missed or left behind.
- Improved Performance: Reduced file sizes lead to faster download times, enhancing user experience.
- Organization: Keeps files organized in a single package, which can be easily stored and shared.
What Solutions Does Salesforce Offers for Creating Zip Folders?
While Salesforce introduced a new feature for compressing and extracting files in Apex (Compress and Extract Zip Files) with the Spring’24 release, it’s currently in Developer Preview. It is not production-ready yet.
Therefore, if you need ZIP creation functionality right now, you’ll need to use an alternative solution. Fortunately, Apex allows integration with third-party libraries like JSZip, which can handle the actual creation of the ZIP folder on the client side. These libraries can provide the functionality to create ZIP folders even though Apex itself doesn’t have this capability built-in..
Automation Champion Approach (I-do):
In this blog post, I will guide you through the process of creating and downloading multiple files as a Zip folder within Salesforce using the JSZip JavaScript library. This approach ensures that sensitive data remains within your Salesforce environment, by not using external API, which is crucial for projects in healthcare, financial, or public sectors.
I really appreciate JSZip’s simplicity.  There are more advanced and complicated libraries available, like zip.js, but JSZip will likely cover most use cases.Â
How It Works
- User Action: The user clicks on Download Case Files button on the Case record page.
- LWC Initialization: This action triggers a Lightning Web Component that passes the Case record ID to an Apex class.
- Apex Class Processing: The Apex class retrieves the related files, converts them to base64, and returns this data along with the Case Number to the LWC.
- ZIP Creation: The LWC uses JSZip to create a ZIP folder containing the files on the client side.
- Download: Finally, the ZIP file is automatically downloaded to the user’s computer.
This solution ensures that all file retrieval, compression, and download processes are handled securely within Salesforce, adhering to data privacy requirements.
Step 1: Upload JSZip as a Static Resource
- Download JSZip:
- Go to the official JSZip website.
- Download the latest version of the JSZip library, typically provided as jszip.min.js.
- Compress (zip) the JSZip folder.
- Upload to Salesforce:
- Click Setup.
- In the Quick Find box, type Static Resources and click on the Static Resources.
- Click the New button to create a new static resource.
- Enter a meaningful name for the static resource, such as JSZip.
- Click Choose File to upload the JSZip.zip file.
- Set the cache control to Public.
- Click Save to upload the static resource.

This will make the JSZip library available for use in your Lightning Web Component.
Step 2: Develop an Apex Class for Retrieve and Prepare Files
In this step, I will create an Apex class that handles the retrieval and preparation of files associated with a Case. This class will query the necessary data, convert the files to a suitable format for zipping, and return the data to the Lightning Web Component.
The primary role of this Apex class is to fetch all the files attached to a specific Case and prepare them for zipping. This involves querying the ContentDocumentLink and ContentVersion objects, converting the file content to base64, and structuring the data in a way that the LWC can easily consume and process.
I start by querying the Case record to get the Case Number. This identifier will be used as the name for the ZIP folder, making it easy to identify each ZIP file. Next, I fetch the related files by querying the ContentDocumentLink object to find all files associated with the Case. I collect the IDs of the latest versions of these files for further processing.
Using the collected version IDs, I then query the ContentVersion object to retrieve the actual file content and metadata. Each file’s content is converted to a base64-encoded string. Finally, the files and their metadata are packaged into a structured format, including the file names and their base64-encoded content. This structured data is then returned to the LWC.
public with sharing class CaseFileDownloadController {
@AuraEnabled
public static CaseFileWrapper downloadCaseFiles(Id caseId) {
List<FileWrapper> files = new List<FileWrapper>();
// Query Case to get Case Number
Case caseRecord = [SELECT CaseNumber FROM Case WHERE Id = :caseId LIMIT 1];
// Query ContentDocumentLink records associated with the Case
List<ContentDocumentLink> contentDocumentLinks = [
SELECT ContentDocumentId, ContentDocument.Title, ContentDocument.FileType, ContentDocument.ContentSize, ContentDocument.LatestPublishedVersionId
FROM ContentDocumentLink
WHERE LinkedEntityId = :caseId
ORDER BY SystemModstamp DESC
];
// Store LatestPublishedVersionIds
Set<Id> versionIds = new Set<Id>();
for (ContentDocumentLink link : contentDocumentLinks) {
versionIds.add(link.ContentDocument.LatestPublishedVersionId);
}
Map<Id, ContentVersion> contentVersions = new Map<Id, ContentVersion>(
[SELECT Id, VersionData, FileExtension FROM ContentVersion WHERE Id IN :versionIds]
);
for (ContentDocumentLink link : contentDocumentLinks) {
ContentVersion contentVersion = contentVersions.get(link.ContentDocument.LatestPublishedVersionId);
if (contentVersion != null) {
String base64Data = EncodingUtil.base64Encode(contentVersion.VersionData);
String fileName = link.ContentDocument.Title + '.' + contentVersion.FileExtension;
files.add(new FileWrapper(fileName, base64Data));
}
}
return new CaseFileWrapper(caseRecord.CaseNumber, files);
}
public class FileWrapper {
@AuraEnabled public String fileName { get; set; }
@AuraEnabled public String fileContent { get; set; }
public FileWrapper(String fileName, String fileContent) {
this.fileName = fileName;
this.fileContent = fileContent;
}
}
public class CaseFileWrapper {
@AuraEnabled public String caseNumber { get; set; }
@AuraEnabled public List<FileWrapper> files { get; set; }
public CaseFileWrapper(String caseNumber, List<FileWrapper> files) {
this.caseNumber = caseNumber;
this.files = files;
}
}
}
Step 3: Create a Lightning Web Component (LWC) to Zip and Download
In this step, I will create a Lightning Web Component (LWC) that interacts with the Apex class to retrieve file data, use JSZip to create a ZIP file on the client side, and trigger the download of the ZIP file.
If you don’t know how to create a lightning component, please review this developer guide, Create a Lightning Web Component.
caseFileDownload.html
This template file is responsible for defining the structure and appearance of the component. In this specific case, the component is designed to function as a headless quick action, meaning it does not render any visible UI elements. Instead, it performs background operations, such as creating and downloading a ZIP file.
<template>
</template>
caseFileDownload.js
This JavaScript file for the caseFileDownload LWC handles the entire process of loading the JSZip library, retrieving files from an Apex method, creating a ZIP file, and initiating the download. It provides a seamless user experience with toast notifications for success and error handling, ensuring efficient file management within Salesforce.
import { LightningElement, api } from 'lwc';
import downloadCaseFiles from '@salesforce/apex/CaseFileDownloadController.downloadCaseFiles';
import JSZipResource from '@salesforce/resourceUrl/JSZip';
import { loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class CaseFileDownload extends LightningElement {
@api recordId;
zipInitialized = false;
jsZip;
@api
invoke() {
if (!this.zipInitialized) {
loadScript(this, JSZipResource + '/jszip.min.js')
.then(() => {
this.jsZip = new window.JSZip();
this.zipInitialized = true;
this.downloadFiles();
})
.catch(error => {
console.error('Error loading JSZip: ', error);
this.showToast('Error', 'Error loading JSZip library.', 'error');
this.closeQuickAction();
});
} else {
this.downloadFiles();
}
}
downloadFiles() {
downloadCaseFiles({ caseId: this.recordId })
.then(result => {
if (result.files.length > 0) {
this.createZipFile(result.caseNumber, result.files);
} else {
this.showToast('Info', 'No files to download.', 'info');
this.closeQuickAction();
}
})
.catch(error => {
console.error('Error downloading files: ', error);
this.showToast('Error', 'Error downloading files.', 'error');
this.closeQuickAction();
});
}
createZipFile(caseNumber, files) {
const folder = this.jsZip.folder(caseNumber);
files.forEach(file => {
folder.file(file.fileName, file.fileContent, { base64: true });
});
this.jsZip.generateAsync({ type: 'blob' })
.then(content => {
const element = document.createElement('a');
element.href = URL.createObjectURL(content);
element.download = caseNumber + '.zip';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
this.showToast('Success', 'Download was successful.', 'success');
this.closeQuickAction();
})
.catch(error => {
console.error('Error creating ZIP file: ', error);
this.showToast('Error', 'Error creating ZIP file.', 'error');
this.closeQuickAction();
});
}
showToast(title, message, variant) {
const event = new ShowToastEvent({
title: title,
message: message,
variant: variant,
});
this.dispatchEvent(event);
}
closeQuickAction() {
const closeEvent = new CustomEvent('close');
this.dispatchEvent(closeEvent);
}
}
caseFileDownload.js-meta.xml
This XML configuration file marks the component as exposed, making it available for use. By defining lightning__RecordAction as its target, the component is enabled to function as an action button on record pages. Furthermore, the configuration restricts this action to the Case object, ensuring it can be invoked directly from Case records.Â
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>61.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordAction</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordAction">
<objects>
<object>Case</object>
</objects>
<actionType>Action</actionType>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Step 4: Create a Quick Action to Launch the Lightning Web Component
The next step is to create a Quick Action on the Case object to launch the caseFileDownload Lightning Web Component. To allow users to easily trigger the file download process.
- Click Setup.
- In the Object Manager, type Case.
- Select Buttons, Links, and Action, then click New Action.
- Input the following information:
- Click Save.
- In the end, make sure to add the newly created quick action to the Case Lightning record page.
Proof of Concept
Going forward, if a support manager wants to download all files for a case in a ZIP folder, they simply need to click on the Download Case Files button, and Salesforce will handle the rest.
If a case does not have any files attached, Salesforce will display an appropriate message: No files to download.
Formative Assessment:
I want to hear from you!
What is one thing you learned from this post? How do you envision applying this new knowledge in the real world? Feel free to share in the comments below.





Thank you for the post. One issue I encountered was that the file extensions weren’t being created correctly, resulting in all files being zipped with unknown file types. To fix this, I added the .pdf extension to the fileName variable in the FileWrapper class. If there is a dynamic way or a different solution, please include it to the post. Thanks again.