How to Merge Image Attachments into PDF in Salesforce Using a Lightning Web Component
In Salesforce, the prestigious CRM system, we may often attach images in records, like expenses and invoices, and need to merge all the images into a PDF file for printing and archiving. Since Salesforce does not provide such a feature by default, in this article, we are going to build a lightning web component to merge images to PDF.
Dynamsoft Document Viewer is used as the SDK for image editing and PDF generation. The SDK compiles several PDF and image processing libraries as WASM, which cannot be directly used in a lightning web component, but we can use iframe to bypass this restriction.
You can learn about how to start a lightning web component from scratch in the previous blog and the usage of Dynamsoft Document Viewer to merge images to PDF in another blog. We are going to focus on the implementation of the component.
Screenshot of the final result:

What you’ll build: A Salesforce Lightning Web Component that loads images into an in-browser editor and merges them into a single PDF file using Dynamsoft Document Viewer inside an iframe.
Key Takeaways
- Salesforce does not natively support merging image attachments into PDF; a Lightning Web Component with Dynamsoft Document Viewer fills this gap.
- WASM-based libraries cannot run directly in LWC, but embedding them in an iframe loaded from a Static Resource is a reliable workaround.
- The component communicates with the iframe via
postMessage, sending a merge trigger and receiving the PDF as a dataURL. - This pattern applies to any Salesforce org where users attach receipts, invoices, or scanned documents that need PDF consolidation.
Common Developer Questions
- How do I merge image attachments into a single PDF in Salesforce?
- Can I use WebAssembly libraries inside a Salesforce Lightning Web Component?
- How do I pass data between an LWC and an embedded iframe using postMessage?
Prerequisites
- A Salesforce Developer Edition or sandbox org with Lightning Web Components enabled.
- Node.js and Salesforce CLI installed for local LWC development.
- Basic familiarity with Lightning Web Components — see the previous blog for a from-scratch walkthrough.
- A Dynamsoft Document Viewer license key. Get a 30-day free trial license for Dynamsoft Document Viewer.
Step 1: Create an HTML5 Page for Image-to-PDF Conversion
-
Create a new HTML file with the following template:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>DDV - HelloWorld</title> </head> <style> html,body { width: 100%; height: 100%; margin:0; padding:0; overscroll-behavior-y: none; overflow: hidden; } #container { width: 100%; height: 100%; } </style> <body> <div id="container"></div> </body> <script type="module"> </script> </html> -
Include dependencies.
Include Dynamsoft Document Viewer in the page.
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/ddv.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/ddv.css"> -
Initialize Dynamsoft Document Viewer. You can apply for a license here.
(async () => { // Public trial license which is valid for 24 hours // You can request a 30-day trial key from https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform Dynamsoft.DDV.Core.license = "LICENSE-KEY"; Dynamsoft.DDV.Core.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@1.1.0/dist/engine";// Lead to a folder containing the distributed WASM files await Dynamsoft.DDV.Core.init(); Dynamsoft.DDV.setProcessingHandler("imageFilter", new Dynamsoft.DDV.ImageFilter()); })(); -
Create an EditViewer. We can add images into it.
const editViewer = new Dynamsoft.DDV.EditViewer({ container: "container", }); -
Add an event listening to messages from another window. If it receives a message, save the images as PDF and post it as dataURL for the parent window.
window.addEventListener( "message", async (event) => { console.log("received message in iframe"); console.log(event); let pdf = await editViewer.currentDocument.saveToPdf(); let dataURL = await blobToDataURL(pdf); window.parent.postMessage(dataURL); }, false, ); function blobToDataURL(blob) { return new Promise((resolve,reject)=>{ var reader = new FileReader(); reader.readAsDataURL(blob); reader.addEventListener("error",reject); reader.onload = function (e) { resolve(e.target.result); } }) }
Step 2: Add the HTML5 Page as a Salesforce Static Resource
-
Create a
ddvfolder understaticresources. -
Add
ddv.resource-meta.xmlas the meta file understaticresources.<?xml version="1.0" encoding="UTF-8"?> <StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"> <cacheControl>Private</cacheControl> <contentType>application/zip</contentType> </StaticResource> -
Add the HTML page as
index.htmlin theddvfolder.
Step 3: Build the Lightning Web Component
-
Create a new lightning web component.
-
In the HTML, add a button to start the merging, a container for the HTML5 page and an anchor to store the PDF dataURL.
<template> <lightning-card title="Dynamsoft Document Viewer" icon-name="custom:custom14"> <div class="slds-m-around_medium"> <lightning-button label="Merge into PDF" onclick={merge}></lightning-button> <div> <label> Received dataURL: <a target="_blank" href={dataURL}>{dataURLLabel}</a> </label> </div> </div> <div class="ddvViewer" id='ddvViewer' style="height:500px;width:400px;"></div> </lightning-card> </template> -
In the
renderedCallbacklifecycle event, append an iframe with its src set to the HTML5 page we wrote. We also add an event listening to dataURL messages from the iframe.import { LightningElement } from 'lwc'; import ddv from '@salesforce/resourceUrl/ddv'; export default class DocumentManager extends LightningElement { ddvInitialized = false; ddvFrame; dataURL = ""; get dataURLLabel() { if (this.dataURL.length > 15) { return this.dataURL.substring(0,10) + "..."; }else{ return this.dataURL; } } renderedCallback() { if (this.ddvInitialized) { return; } this.ddvInitialized = true; window.addEventListener( "message", (event) => { this.dataURL = event.data; }, false, ); this.ddvFrame = document.createElement('iframe'); this.ddvFrame.src = ddv + "/index.html"; // div tag in which iframe will be added should have id attribute with value myDIV this.template.querySelector("div.ddvViewer").appendChild(this.ddvFrame); // provide height and width to it this.ddvFrame.setAttribute("style","height:100%;width:100%;"); } } -
The
mergefunction which is called when themerge images to PDFbutton is pressed.merge(){ this.ddvFrame.contentWindow.postMessage("merge"); }
All right, we’ve completed the component. We can take a step further to use APEX to get all the files on Salesforce. We may discuss this in the future.
Common Issues & Edge Cases
- WASM fails to load in the iframe: Salesforce’s Content Security Policy can block external CDN scripts. Make sure the Dynamsoft Document Viewer JS and WASM files are included inside the Static Resource zip rather than loaded from a CDN, or add the CDN domain to your org’s CSP Trusted Sites.
postMessagenot received by the parent LWC: Salesforce sandboxes the iframe origin. Always omit thetargetOrigincheck during development or set it to"*"for testing, then lock it down to the correct Salesforce domain in production.- Large images cause slow PDF generation: The WASM engine runs in the browser thread. If users routinely merge 20+ high-resolution photos, consider resizing images before adding them to the EditViewer, or show a loading spinner to signal progress.