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:

HTML
<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 logged Error: Could not load html2canvas and 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:

PHP
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:

JavaScript
//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 jsPDF class from window.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: true compresses the page content: in a test, a five-page PDF shrank from 681 KB to 32 KB.
  • margin sets the top, right, bottom and left margins in millimeters, on every page. width and windowWidth work 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 ignoreElements option of html2canvas skips the element with the ID ignoreContent, so you can keep buttons or notes out of the PDF. Adding a data-html2canvas-ignore attribute to an element does the same.
  • Rendering takes a moment, so html() calls the callback when it’s done, and save('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:

JavaScript
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-fit and mix-blend-mode. Check the result.
  • Newer color syntax. A block with color: oklch(0.45 0.2 260) stopped the export with Error: Attempting to parse an unsupported color function "oklch", and no PDF was saved. Use hex or rgb() 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:

HTML
<button type="button" onclick="window.print()">Save as PDF</button>

This CSS prints only the #pdfBlock element, on A4 paper with 15 mm margins:

CSS
@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: avoid keeps a table or a box with the keep-together class on one page when it fits.
  • @page sets 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.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More