This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /blog/how-to-convert-html-to-pdf-using-html2pdf.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Convert HTML to PDF in JavaScript with html2pdf.js

Table of contents

    Convert HTML to PDF in JavaScript with html2pdf.js
    Convert HTML to PDF with one API call

    Nutrient DWS handles rendering, fonts, and CSS for you — start with 50 free credits.

    TL;DR

    html2pdf.js converts a rendered HTML element into a PDF in the browser. Select the element, pass options to set(), and call from(element).save(). It uses html2canvas to capture the content and jsPDF to create the PDF, so the output’s text is an image rather than selectable text.

    Use html2pdf.js(opens in a new tab) for downloadable receipts, invoices, and short reports generated from content already displayed on a webpage. You don’t need a conversion server, but you do need to wait for the content, fonts, and images to finish loading.

    This tutorial includes a complete browser example, page-break options, and a React component. It also explains when a server-side renderer or Nutrient’s HTML-to-PDF API better fits the output you need.

    Create a working html2pdf.js example

    The example uses html2pdf.js 0.14.0. Create two files in an empty directory: index.html and index.js. Serve the directory over HTTP using your editor’s local web server, rather than opening it as a file:// URL.

    Add the HTML content and download button

    Save the following as index.html. The download button sits outside #content, so it won’t appear in the exported PDF:

    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>HTML-to-PDF invoice</title>
    <style>
    body {
    margin: 24px;
    font-family: Arial, sans-serif;
    color: #222;
    }
    #content {
    max-width: 680px;
    background: white;
    padding: 20px;
    }
    table {
    border-collapse: collapse;
    width: 100%;
    }
    th, td {
    border: 1px solid #999;
    padding: 10px;
    text-align: left;
    }
    </style>
    <script
    defer
    src="https://cdn.jsdelivr.net/npm/html2pdf.js@0.14.0/dist/html2pdf.bundle.min.js"
    ></script>
    <script defer src="index.js"></script>
    </head>
    <body>
    <div id="content">
    <h1>Invoice #1234</h1>
    <p>Customer: Acme Company</p>
    <table>
    <thead>
    <tr><th>Item</th><th>Amount</th></tr>
    </thead>
    <tbody>
    <tr><td>Document review</td><td>$150.00</td></tr>
    </tbody>
    </table>
    <p>Total due: $150.00</p>
    </div>
    <button id="download" type="button">Download PDF</button>
    <p id="status" role="status"></p>
    </body>
    </html>

    The bundled script includes its rendering dependencies. You can download that versioned bundle and host it yourself instead of using the content delivery network (CDN).

    Generate the PDF on demand

    Save this code as index.js. It checks that the target exists, waits for fonts and images, and reports conversion failures:

    const button = document.getElementById('download');
    const status = document.getElementById('status');
    button.addEventListener('click', async () => {
    button.disabled = true;
    status.textContent = 'Preparing PDF…';
    try {
    const element = document.getElementById('content');
    if (!element) throw new Error('The export element was not found.');
    if (typeof html2pdf !== 'function') {
    throw new Error('The html2pdf.js script did not load.');
    }
    await document.fonts.ready;
    await Promise.all(
    Array.from(element.querySelectorAll('img'), (image) => image.decode()),
    );
    await html2pdf()
    .set({
    filename: 'invoice.pdf',
    margin: 10,
    image: { type: 'jpeg', quality: 0.95 },
    html2canvas: { scale: 2, useCORS: true },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
    pagebreak: { mode: ['css', 'legacy'], avoid: 'tr' },
    })
    .from(element)
    .save();
    status.textContent = 'PDF generated.';
    } catch (error) {
    status.textContent = 'Export failed: ' + error.message;
    } finally {
    button.disabled = false;
    }
    });

    Open the page through your local server and click Download PDF. Check the resulting invoice, including the table and margins. Selecting the text in the PDF won’t work because the content was captured as an image.

    PDF output from an earlier html2pdf.js example

    The screenshot shows output from the earlier example; the current code exports the invoice above.

    Configure page size, margins, and quality

    Pass conversion settings through set(). The options reference(opens in a new tab) documents the complete configuration.

    OptionEffect
    filenameSets the downloaded file name.
    margin: 10Adds a margin of 10 in the units selected by jsPDF.unit — millimeters in this example.
    jsPDF.format and orientationSelect the page size and portrait or landscape layout.
    image.type and qualityControl the image embedded in the PDF. JPEG quality accepts values from zero to one.
    html2canvas.scaleControls capture resolution. Higher values use more memory and can increase file size.
    html2canvas.useCORSAttempts cross-origin image loading when the image server permits it.
    pagebreakControls where elements can begin or end pages.

    To export in landscape, change orientation to 'landscape' in the complete example. For US letter paper, set format to 'letter'.

    The underlying html2canvas default scale(opens in a new tab) is the browser’s device pixel ratio, not always one. The example sets it explicitly to make the capture resolution predictable. Higher resolution improves raster detail but doesn’t create selectable text.

    Control page breaks in multipage PDFs

    For a forced break, add a class to the section that should begin a new page. For example, place this section inside #content after the invoice:

    <section class="new-page">
    <h2>Payment details</h2>
    <p>Please include invoice number 1234 with your payment.</p>
    </section>

    Replace the pagebreak setting in the complete JavaScript example with:

    pagebreak: {
    mode: ['css', 'legacy'],
    before: '.new-page',
    avoid: 'tr',
    },

    This is a property inside the existing options object, rather than a separate script. The selector forces a break before the payment section.

    The css mode reads supported CSS break rules, while legacy handles the html2pdf__page-break class. Use avoid for elements you want to keep together. An element taller than a page still needs to split or be redesigned.

    Avoid applying avoid-all as a universal fix. Test actual tables and long paragraphs, because keeping every element together can leave unwanted whitespace.

    Use html2pdf.js with React or Next.js

    Install the package in your React project:

    Terminal window
    npm install --save-exact html2pdf.js@0.14.0

    The component below uses a ref for the export element. It imports html2pdf.js inside the click handler so browser-only code isn’t evaluated during Next.js server rendering:

    'use client';
    import { useRef, useState } from 'react';
    export default function Invoice() {
    const contentRef = useRef(null);
    const exportingRef = useRef(false);
    const [busy, setBusy] = useState(false);
    const [message, setMessage] = useState('');
    async function downloadPDF() {
    if (exportingRef.current) return;
    exportingRef.current = true;
    setBusy(true);
    setMessage('');
    try {
    const element = contentRef.current;
    if (!element) throw new Error('The invoice is not ready.');
    const html2pdf = (await import('html2pdf.js')).default;
    await document.fonts.ready;
    await Promise.all(
    Array.from(element.querySelectorAll('img'), (image) => image.decode()),
    );
    await html2pdf()
    .set({
    filename: 'invoice.pdf',
    margin: 10,
    html2canvas: { scale: 2, useCORS: true },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
    })
    .from(element)
    .save();
    setMessage('PDF generated.');
    } catch (error) {
    setMessage('Export failed: ' + error.message);
    } finally {
    exportingRef.current = false;
    setBusy(false);
    }
    }
    return (
    <section>
    <div ref={contentRef} style={{ background: 'white', color: '#222', padding: 20 }}>
    <h1>Invoice #1234</h1>
    <p>Amount due: $150.00</p>
    </div>
    <button type="button" disabled={busy} onClick={downloadPDF}>
    {busy ? 'Exporting…' : 'Download PDF'}
    </button>
    <p role="status">{message}</p>
    </section>
    );
    }

    In a Next.js App Router project, save it as a component and render it from your page, or use it directly as src/app/page.js. In a browser-only React app, the 'use client' directive isn’t needed.

    Troubleshoot blank PDFs and missing images

    Check the source element and its assets before changing quality settings.

    ProblemWhat to check
    Blank PDFConfirm the selected element exists, is visible, and has nonzero dimensions. Wait for rendering and assets rather than adding an arbitrary timeout.
    Missing imagesServe images from the same origin or configure cross-origin resource sharing (CORS) on their server. useCORS: true doesn’t bypass server restrictions.
    Unsupported colors or missing CSS effectsCompare your styles against the html2canvas feature list(opens in a new tab). Simplify unsupported styles or use a browser print engine.
    Blurry textIncrease scale after checking memory and file-size costs. Text remains rasterized.
    Content split unexpectedlyAdd targeted before or avoid selectors, reduce oversized elements, and inspect the page layout.
    Export fails for a long documentBrowser canvas dimensions and memory are limited. Split the content or use a server-side renderer.
    self is not defined in Next.jsImport html2pdf.js in a browser event handler or another browser-only boundary.
    Form values appear as a pictureThis export captures appearance; it doesn’t create interactive PDF form fields.

    There’s no universal safe page-count limit. Document dimensions, images, capture scale, and the browser all affect memory use. See the html2canvas FAQ(opens in a new tab) for canvas limits and cross-origin behavior.

    When to use html2pdf.js

    Choose html2pdf.js when the source is already on the page, a browser download is convenient, and image-based output is acceptable.

    Consider another approach when readers need searchable text, accessible document structure, interactive form fields, or consistent pagination for long reports. Test the output against those requirements as well as checking its appearance.

    For a broader comparison, see the following blog posts:

    Convert HTML to PDF with Nutrient

    Nutrient’s HTML-to-PDF API processes HTML on a server. This keeps the conversion workload off the user’s device and produces PDF text from rendered text rather than a screenshot of the entire page.

    For styled templates, package the required fonts, CSS, and images according to the PDF generation guides. Check your actual output for layout and font requirements; browser-based rendering isn’t a guarantee that every CSS feature or page layout will behave identically.

    Node.js example

    This example uses Node.js 24’s built-in fetch, FormData, and Blob APIs. Create a self-contained index.html containing the document you want to convert. Keep the API key in your server environment as NUTRIENT_API_KEY; don’t put it in browser JavaScript.

    Save the following as convert.mjs:

    import { readFile, writeFile } from 'node:fs/promises';
    async function convertHTML() {
    const apiKey = process.env.NUTRIENT_API_KEY;
    if (!apiKey) throw new Error('Set NUTRIENT_API_KEY before running this script.');
    const form = new FormData();
    form.append(
    'html',
    new Blob([await readFile('index.html')], { type: 'text/html' }),
    'index.html',
    );
    const response = await fetch(
    'https://api.nutrient.io/processor/generate_pdf',
    {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + apiKey },
    body: form,
    },
    );
    if (!response.ok) {
    throw new Error('Conversion failed (' + response.status + '): ' + await response.text());
    }
    await writeFile('result.pdf', Buffer.from(await response.arrayBuffer()));
    console.log('Saved result.pdf');
    }
    convertHTML().catch((error) => {
    console.error(error.message);
    process.exitCode = 1;
    });

    After configuring the environment variable, run:

    Terminal window
    node convert.mjs

    This request uses the html multipart field documented in the DWS Processor API getting started guide. This example buffers the response in memory; use a streaming implementation if your application needs to handle larger outputs.

    Self-hosted conversion with Document Engine

    For conversion on your own infrastructure, use Document Engine. With a running, licensed installation and an API token, the build endpoint can return a PDF:

    Terminal window
    curl --fail-with-body --request POST http://localhost:5000/api/build \
    --header "Authorization: Token token=<API token>" \
    --form 'page.html=@index.html' \
    --form 'instructions={"parts":[{"html":"page.html"}]}' \
    --output result.pdf

    The multipart field name must match the html value in the instructions. For interactive fields, follow the separate fillable form generation guide and its supported HTML elements.

    Compare the conversion approaches

    Requirementhtml2pdf.jsNutrient server conversion
    ExecutionRuns in the user’s browserRuns through a cloud API or a self-hosted Document Engine
    TextCaptured as an imageRendered text can remain selectable
    StylingUses html2canvas’s supported CSS subsetUses server-side HTML rendering; verify templates and assets
    Form fieldsCaptures visual appearanceSupports documented form-generation workflows
    SetupBrowser script or npm dependencyCloud API credentials or a licensed server installation
    LicensingMIT-licensed libraryCommercial service or server license

    Create a DWS account(opens in a new tab) to evaluate the cloud API, or consult Processor API pricing for current credits and plan details.

    Other HTML-to-PDF options

    Puppeteer(opens in a new tab) and Playwright(opens in a new tab) can print webpages to PDF on a server. In Playwright, PDF generation uses Chromium. Test print styles, fonts, and page breaks with the browser version you deploy.

    jsPDF(opens in a new tab) can create text and shapes directly in the browser. It also has an HTML API; output behavior depends on the method and options you use.

    wkhtmltopdf(opens in a new tab) uses an older Qt WebKit rendering stack, and its repository is archived. Existing applications may still use it, but check its maintenance status and CSS limitations before choosing it for new work.

    FAQ

    How do I convert an HTML element to PDF with html2pdf.js?

    Include the library, select a rendered element, and call html2pdf().set(options).from(element).save(). Run it after the content and assets are ready, and handle the returned promise.

    Does html2pdf.js create searchable text?

    Its normal HTML export renders content to an image and places that image in a PDF. Choose a text-producing method when selection, search, or extraction is required.

    How do I add a page break?

    Use the pagebreak.before option with a selector for the element that should start a new page. You can also use supported CSS break rules and the legacy page-break class.

    Why are images missing from the exported PDF?

    Check that images have loaded and that their server permits cross-origin access. The useCORS option requests permitted cross-origin images; it doesn’t override CORS restrictions.

    Can I use html2pdf.js in Next.js?

    Yes. Reference the element with a React ref and import the library inside a browser event handler. A static import can evaluate browser-only code during server rendering.

    Can html2pdf.js export fillable PDF forms?

    The standard HTML export captures the form’s appearance as an image. Use a PDF form generation API when users must interact with fields after downloading the document.

    When should I use Nutrient instead?

    Evaluate Nutrient when you need server-side conversion, text output, or a documented PDF form generation workflow. Choose the cloud API or Document Engine according to where you need processing to run.

    Hulya Masharipov

    Hulya Masharipov

    Technical Writer

    Hulya is a frontend web developer and technical writer who enjoys creating responsive, scalable, and maintainable web experiences. She’s passionate about open source, web accessibility, cybersecurity privacy, and blockchain.

    Explore related topics

    50 free credits Start converting HTML to PDF