Generate a PDF From a Web Page With jsPDF and jQuery

Quick Summary
- Load jsPDF 4.2.1 and html2canvas 1.4.1, then call jsPDF's html() on the element in a click handler and save the result; html() doesn't work without html2canvas.
- Use autoPaging: 'text' so lines aren't cut at page breaks and compress: true for smaller files; the standard PDF fonts only cover Western European characters, so other scripts need a TTF font via fontFaces.
- For a copy the visitor saves themselves, the browser's Print dialog with a @media print stylesheet is simpler and keeps real fonts, links and CSS page breaks.
To generate a PDF from a web page that downloads when the visitor clicks a button, load jsPDF and html2canvas. Then call jsPDF’s html() method on the part of the page you want and save the result. jsPDF 4.2.1, released in March 2026, is the current version. html() lays the element out with html2canvas and draws it into the PDF as real, selectable text. That’s handy for invoices, certificates, order summaries or any block of text people may want to keep. If visitors only need a copy of the page, the browser’s own Print dialog with Save as PDF and a print stylesheet is simpler, and it handles fonts, links and page breaks better. The sections below show both.
Load jsPDF, html2canvas and jQuery
Add these three script tags to your page before your own JavaScript file:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/4.2.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>- The first loads jsPDF 4.2.1 from cdnjs. It puts the library in
window.jspdf. - The second loads html2canvas 1.4.1, the latest release of html2canvas, from January 2022. jsPDF lists it as an optional dependency because only
html()needs it. Without it, clicking the button in Chrome only loggedError: Could not load html2canvasand no file was saved. - The third loads jQuery 3.7.1 from Google’s CDN. Leave it out on a site that already loads jQuery, such as a WordPress site whose theme or plugins load it.
All three URLs returned HTTP 200 at this post’s last update. If you copy the tags through a word processor, check that the src values are still wrapped in straight double quotes. Curly quotes break the tags. If your project uses npm, npm install jspdf html2canvas and import { jsPDF } from 'jspdf'; replace the first two tags; jsPDF then loads html2canvas itself when html() runs.
Loading the scripts in WordPress
In WordPress, enqueue the libraries from your child theme’s functions.php and make your own script depend on the jquery that WordPress already includes:
function my_enqueue_pdf_scripts() {
wp_enqueue_script( 'html2canvas', 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', array(), null, true );
wp_enqueue_script( 'jspdf', 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/4.2.1/jspdf.umd.min.js', array( 'html2canvas' ), null, true );
wp_enqueue_script( 'my-pdf', get_stylesheet_directory_uri() . '/js/pdf.js', array( 'jquery', 'jspdf' ), '1.0', true );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_pdf_scripts' );In WordPress 7.1.2 this printed WordPress’s own jQuery 3.7.1 and jQuery Migrate, then html2canvas, jsPDF and js/pdf.js, in that order, in the footer. The null version keeps WordPress from adding ?ver= to the CDN URLs. To load the libraries only where they’re used, wrap the three calls in a condition such as if ( is_page( 'invoice' ) ).
Generate the PDF on click
Then put this code in your JavaScript file, js/pdf.js in the example above:
//PRINT PDF
var jsPDF = window.jspdf.jsPDF;
jQuery(function ($) {
$("#gpdf").on("click", function() {
var pdfdoc = new jsPDF({ compress: true });
var pdfBlock = document.getElementById('pdfBlock');
pdfdoc.html(pdfBlock, {
margin: [10, 10, 10, 10],
autoPaging: 'text',
width: 190,
windowWidth: pdfBlock.offsetWidth,
html2canvas: {
ignoreElements: function(element) {
return element.id === 'ignoreContent';
}
},
callback: function(doc) {
doc.save('result.pdf');
}
});
});
});- The first line takes the
jsPDFclass fromwindow.jspdf, where the script tag put it. jQuery(function ($) { … })runs the code once the page is ready and passes jQuery in as$. The older$(document).ready(…)form fails in WordPress, where jQuery runs in no-conflict mode and$is undefined (see fixing “$ is not a function”)..on("click", …)replaces the.click(handler)shorthand, which has been deprecated since jQuery 3.3.new jsPDF()creates an empty document, by default an A4 page in portrait with millimeters as the unit. The click handler creates it, so every click starts a fresh PDF.compress: truecompresses the page content: in a test, a five-page PDF shrank from 681 KB to 32 KB.marginsets the top, right, bottom and left margins in millimeters, on every page.widthandwindowWidthwork together: the block is rendered at its current width in pixels and scaled to 190 mm, the width of an A4 page (210 mm) minus both margins.autoPaging: 'text'continues long content on new pages and tries not to cut lines of text in half. More on that below.- The
ignoreElementsoption of html2canvas skips the element with the IDignoreContent, so you can keep buttons or notes out of the PDF. Adding adata-html2canvas-ignoreattribute to an element does the same. - Rendering takes a moment, so
html()calls thecallbackwhen it’s done, andsave('result.pdf')downloads the file under that name.
Your page needs a button with the ID gpdf and a container with the ID pdfBlock. Tested in headless Chrome 153 with a short invoice block: the click saved a one-page result.pdf whose text could be selected and searched, and the #ignoreContent paragraph wasn’t in it.
How does jsPDF handle page breaks?
With the autoPaging option, which is on by default, content taller than one page continues on the next. Its default mode, true or 'slice', cuts the drawing at the page edge wherever it falls. In a test with sixty paragraphs, that cut the last line on page 3 in half, with the rest of the line at the top of page 4. With autoPaging: 'text', the same content broke between lines instead. The jsPDF documentation says 'text' works best for content that is mostly a single column of text. It still splits a paragraph between pages when it has to.
Fonts and non-Latin text
html() doesn’t embed the fonts your page uses. It matches each font-family to one of the standard PDF fonts: Arial, Helvetica and sans-serif become Helvetica, serif becomes Times, monospace becomes Courier, and any other family also becomes Times. The standard fonts only cover Western European characters. In the test, Café, naïve, €12 and dashes came out fine, while Greek (Ω), Russian and Bengali text turned into unrelated symbols.
For other characters, give html() a TrueType font with the fontFaces option. jsPDF only reads .ttf files, not WOFF2:
pdfdoc.html(pdfBlock, {
// ...the same options as above, plus:
fontFaces: [{
family: 'DejaVu Sans',
weight: 400,
src: [{ url: '/wp-content/themes/my-theme/fonts/DejaVuSans.ttf', format: 'truetype' }]
}],
callback: function(doc) {
doc.save('result.pdf');
}
});The family must match the font-family the block uses in your CSS, and the font must contain the characters. With DejaVu Sans set on the block, Ω and the Russian text came out correctly, but the Bengali text was left out, because DejaVu Sans has no Bengali letters.
What else doesn’t carry over?
- Some CSS. html2canvas redraws the page from its styles instead of taking a screenshot, and its list of unsupported properties includes
box-shadow,filter,object-fitandmix-blend-mode. Check the result. - Newer color syntax. A block with
color: oklch(0.45 0.2 260)stopped the export withError: Attempting to parse an unsupported color function "oklch", and no PDF was saved. Use hex orrgb()colors inside the block. - Links. A link in the block came out as blue, underlined text that nobody can click.
Older tutorials use fromHTML() with jsPDF 1.x. That method isn’t part of current jsPDF releases, and html() replaces it.
The simpler option: print to PDF
Chrome, Edge, Firefox and Safari can all save a page as a PDF from the Print dialog, and a print stylesheet decides what goes into it. For a document the visitor saves for their own records, that’s often enough, and it needs no library. A button opens the dialog:
<button type="button" onclick="window.print()">Save as PDF</button>This CSS prints only the #pdfBlock element, on A4 paper with 15 mm margins:
@media print {
body *:not(:has(#pdfBlock), #pdfBlock, #pdfBlock *) {
display: none;
}
#ignoreContent {
display: none;
}
#pdfBlock table,
#pdfBlock .keep-together {
break-inside: avoid;
}
}
@page {
size: A4;
margin: 15mm;
}- The first rule hides every element except
#pdfBlock, the elements that contain it (found with:has()) and everything inside it.:has()works in Chrome 105, Firefox 121 and Safari 15.4 and later. break-inside: avoidkeeps a table or a box with thekeep-togetherclass on one page when it fits.@pagesets the paper size and margins. Safari supports it from version 18.2; older versions use the settings from the Print dialog.
Printed to PDF in headless Chrome 153, the page came out on A4 with only the block, without #ignoreContent. The text was real text, including the Greek, Russian, Bengali and Chinese that the standard jsPDF fonts couldn’t show, and the link could still be clicked. A bordered box that would have started at the bottom of page 2 moved to page 3 as a whole. An older trick hides the rest of the page with visibility: hidden instead of display: none; hidden elements still take up space, and on a test page with a long sidebar that printed eight blank pages after the block.
The trade-off: the visitor has to choose Save as PDF in the dialog, and you can’t set the file name or start the download yourself. When you need a one-click download with a fixed name, use jsPDF.
