The Browser/showPdfWithPassword extension displays a PDF file directly in the browser. It uses PDF.js to load the file, render each page on a canvas, overlay SVG text for selectable/copyable text, and create transparent clickable areas for PDF link annotations.
This is useful when a journey needs to open and visually inspect a PDF document in the browser, including password-protected PDFs where the password is available at runtime.
Parameters:
-
filerequired, a string containing the URL or path of the PDF file to load and render in the browser; -
passwordoptional, a string containing the PDF password when the PDF is password-protected. Leave it empty or omit it for PDFs that do not require a password;
Note: The file input must point to a PDF file that PDF.js can fetch from the browser context. If the PDF is hosted on another domain, that server must allow browser access through CORS.
NLP usage
Use the extension in a journey by calling Browser/showPdfWithPassword with the execute command. Pass each value to the matching extension input using as file and, when needed, as password.
Note: This extension runs asynchronously because it loads the PDF with PDF.js, reads pages, renders canvas content, reads text content and annotations, and finishes through done or doneError.
execute "Browser/showPdfWithPassword" using "https://example.com/sample.pdf" as file and "myPdfPassword" as password returning $responseexecute "Browser/showPdfWithPassword" using "https://example.com/public-sample.pdf" as file returning $responseYou can also pass the PDF URL and password from journey variables.
execute "Browser/showPdfWithPassword" using "$pdfFileUrl" as file and "$pdfPassword" as password returning $responsestore value "https://example.com/protected-sample.pdf" in $pdfFileUrl
store value "myPdfPassword" in $pdfPassword
execute "Browser/showPdfWithPassword" using "$pdfFileUrl" as file and "$pdfPassword" as password returning $responseExample output:
PDF loaded in the browser. The rendered PDF content is displayed on the page.This extension requires the following resources:
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.min.jshttps://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.worker.min.js
The extension should be configured as:
- Run asynchronously: Yes
- Scope: Global
Limitation: This extension is designed for PDF files only and depends on PDF.js 2.2.228 in the browser. The file must be reachable from the browser and allowed by CORS, and password-protected PDFs require the correct password value. Large PDFs can consume browser memory because every page is appended to the DOM with a canvas and SVG overlay. The script overlays text with transparent SVG elements and creates clickable areas only for annotations that provide a URL, so complex form fields, embedded media, non-URL annotations, advanced PDF interactions, or scanned-image PDFs without text content may not behave like a full PDF viewer.
Add the extension to your Virtuoso instance
Select the domain that matches your Virtuoso account.
View source
Last updated: 25/05/2026
Resources:
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.min.jshttps://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.228/pdf.worker.min.js
async function showPdf (file) {
document.body.style = "background: grey; padding:5px;"
document.body.innerHTML = '<div id="pageContainer"></div>'
var PDF_PATH = file;
var SVG_NS = 'http://www.w3.org/2000/svg';
document.title = PDF_PATH
function buildSVG(viewport, textContent) {
var svg = document.createElementNS(SVG_NS, 'svg:svg');
svg.setAttribute('width', viewport.width + 'px');
svg.setAttribute('height', viewport.height + 'px');
svg.setAttribute('font-size', 1);
textContent.items.forEach(function (textItem) {
if (textItem.str.trim().length === 0){
return
}
var tx = pdfjsLib.Util.transform(
pdfjsLib.Util.transform(viewport.transform, textItem.transform),
[1, 0, 0, -1, 0, 0]);
var style = textContent.styles[textItem.fontName];
// adding text element
var text = document.createElementNS(SVG_NS, 'svg:text');
text.setAttribute('transform', 'matrix(' + tx.join(' ') + ')');
text.setAttribute('font-family', style.fontFamily);
text.setAttribute('fill-opacity', 0);
text.textContent = textItem.str;
svg.appendChild(text);
});
return svg;
}
var loadingTask = pdfjsLib.getDocument({url: PDF_PATH, password: password});
loadingTask.promise.then(function (pdfDocument) {
console.log('success')
for (let i = 0; i < pdfDocument.numPages; i++) {
const div = document.createElement("div")
div.id = "page-" + i
div.style = "border: 1px solid black; background: white; margin-bottom: 3px; relative"
document.getElementById('pageContainer').appendChild(div);
pdfDocument.getPage(i + 1).then(function (page) {
const normalViewport = page.getViewport(1.0)
const viewport = page.getViewport({ scale: window.innerWidth / normalViewport.width });
const scale = [
viewport.width / normalViewport.width,
viewport.height / normalViewport.height
]
page.getTextContent().then(function (textContent) {
const parentDiv = document.getElementById(div.id)
parentDiv.style.height = `${viewport.height}px`
parentDiv.style.width = `${viewport.width}px`
const pageDiv = document.createElement("div")
pageDiv.style = "position: relative; height: 100%;";
parentDiv.appendChild(pageDiv)
const canvas = document.createElement('canvas')
canvas.style = "width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; background: white;"
canvas.height = viewport.height
canvas.width = viewport.width
pageDiv.appendChild(canvas)
page.render({
canvasContext: canvas.getContext('2d'),
viewport: viewport
})
var svg = buildSVG(viewport, textContent);
svg.style = "opacity: 1; width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; z-index: 200"
pageDiv.appendChild(svg);
}).then(() => {
page.getAnnotations().then(function (annotations) {
const pageDiv = document.getElementById(div.id).lastChild
annotations.forEach(annotation => {
const link = document.createElement("a")
link.href = annotation.url
const button = document.createElement("button")
const rect = annotation.rect
link.style = `display: block; position: absolute; left: ${rect[0]*scale[0]}px; z-index:400;
bottom: ${rect[1]*scale[1]}px;`
button.style = `opacity: 0; width: ${(rect[2]-rect[0])*scale[0]}px;
height: ${(rect[3]-rect[1])*scale[1]}px;`
button.innerText = ""
link.appendChild(button)
pageDiv.appendChild(link);
})
})
});
});
}
}).then(() => done()).catch(error => {
console.error('failed')
console.error(error.details)
doneError(error)
})
}
try {
showPdf(file)
} catch (error){
console.error('err')
console.error(error.details)
doneError(error)
}
Comments
0 comments
Please sign in to leave a comment.