Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How you generate a PDF in an Express application depends on what the PDF represents. Use PDFKit when your data should become a document that you lay out with drawing and text primitives. Use Puppeteer when an existing HTML page and its CSS are the source of truth. Use pdf-lib when you need to create, load, edit, merge, or save PDF documents in JavaScript.

This guide shows a complete Express route with PDFKit, then covers HTML-to-PDF with Puppeteer, document editing with pdf-lib, response headers, security, errors, deployment checks, and an alternative that avoids browser setup.

Choose the PDF generation model first

Requirement Starting point Why it fits Trade-offs
Invoices, certificates, reports, or other structured data PDFKit PDFDocument is a readable Node.js stream that can pipe directly to an HTTP response. You control layout, pagination, fonts, and images yourself.
Print an existing HTML/CSS view Puppeteer Chromium renders the page and page.pdf() prints it using print media by default. You must operate a browser process and test fonts, page breaks, timeouts, and the target host.
Create, modify, or assemble PDF files pdf-lib Its JavaScript API creates documents, adds pages, draws content, embeds pages, and saves bytes. Check that its editing APIs cover the structure of the PDFs you receive.

There is no documented universal speed, memory, page-count, or fidelity winner. Compare the source format, browser-runtime requirements, streaming versus buffering, and the amount of existing PDF content you must preserve.

Prerequisites and version alignment

Use a current supported Node.js runtime and install package versions that you can reproduce with a lockfile. Express displayed version 5.2.1 on its homepage when checked, while the response reference used below is the Express 4.x API reference. The routing and response concepts apply to both, but verify the exact APIs against the Express major version installed in your project. Puppeteer’s guide search result showed 25.12.0; package versions and browser requirements can change.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB
  • CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  • WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
  • A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents
npm install express pdfkit

Use ES modules if your package.json contains "type": "module". For CommonJS, replace the imports with require() equivalents supported by your installed package versions.

Return a generated PDF from an Express route with PDFKit

Express route methods such as app.get() receive a request and response. A handler must complete that response cycle; a route that neither sends a response nor passes control onward can leave the request hanging. PDFKit documents are readable streams: pipe one to res, write content, and call doc.end() to finalize it.

import express from 'express';
import PDFDocument from 'pdfkit';

const app = express();
const port = process.env.PORT || 3000;

app.get('/report.pdf', (req, res, next) => {
  const doc = new PDFDocument({
    size: 'A4',
    margin: 50,
    info: {
      Title: 'Example report',
      Author: 'Example application'
    }
  });

  res.setHeader('Content-Type', 'application/pdf');
  res.setHeader('Content-Disposition', 'inline; filename="report.pdf"');

  doc.on('error', (error) => {
    if (!res.headersSent) next(error);
    else res.destroy(error);
  });

  res.on('close', () => {
    // The client disconnected. Stop producing more output if appropriate.
    if (!res.writableEnded) doc.destroy();
  });

  doc.pipe(res);
  doc.fontSize(20).text('Example report', { align: 'center' });
  doc.moveDown();
  doc.fontSize(12).text(`Generated at ${new Date().toISOString()}`);
  doc.moveDown();
  doc.text('This paragraph is generated from application data.');
  doc.addPage();
  doc.fontSize(16).text('Second page');
  doc.fontSize(12).text('PDFKit adds a first page automatically; addPage() creates another.');
  doc.end();
});

app.use((error, req, res, next) => {
  if (res.headersSent) return next(error);
  res.status(500).json({ error: 'Unable to generate PDF' });
});

app.listen(port, () => console.log(`Listening on ${port}`));

Run the server and request http://localhost:3000/report.pdf. The browser can display the file because the disposition is inline. Change it to attachment; filename="report.pdf" when you want a download prompt. Keep the filename fixed or sanitize every generated value; never place raw user input into a filesystem path or header.

Rank #2
Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
  • CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
  • SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors

Layout, pages, and assets

  • Call doc.addPage() when you need another page. Plan for long text, table rows, images, and page breaks rather than assuming one page.
  • Register and use fonts deliberately if recipients need consistent typography. Test embedded images and font files in the same runtime and container used in production.
  • PDFKit does not save a file automatically. pipe() chooses the destination, and end() completes the stream.
  • If you are writing to disk instead of the response, use a controlled server directory and expose files only through authorization-aware routes.

How to convert HTML to PDF with Puppeteer

Puppeteer is the better starting point when your invoice, report, or page already exists as HTML and CSS. The handler should create or reuse a browser, load the page, wait for the content you need, call page.pdf(), send the returned bytes, and close page/browser resources in a finally path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import express from 'express';
import puppeteer from 'puppeteer';

const app = express();

app.get('/invoice.pdf', async (req, res, next) => {
  let browser;
  try {
    browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.setContent(`<!doctype html>
      <html><head><style>
        @page { size: A4; margin: 18mm; }
        body { font-family: Arial, sans-serif; }
        h1 { color: #123b63; }
        @media print { .screen-only { display: none; } }
      </style></head>
      <body><h1>Invoice</h1><p>Generated from HTML and CSS.</p></body></html>`,
      { waitUntil: 'networkidle0' }
    );
    await page.evaluate(() => document.fonts.ready);
    const pdf = await page.pdf({
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true
    });
    res.type('application/pdf');
    res.setHeader('Content-Disposition', 'inline; filename="invoice.pdf"');
    res.send(Buffer.from(pdf));
  } catch (error) {
    next(error);
  } finally {
    if (browser) await browser.close();
  }
});

app.listen(3000);

Page.pdf() uses print media by default and returns a Promise<Uint8Array>. If the design intentionally uses screen styles, call await page.emulateMediaType('screen') before generating the PDF. For exact colors, review Chromium’s print-color behavior and the -webkit-print-color-adjust CSS property. Puppeteer’s documented example waits for navigation, creates the PDF, and closes the browser; your route should apply the same cleanup principle.

Browser lifecycle and deployment checks

  • Confirm that the Puppeteer package can install or locate its compatible browser in your build environment.
  • Set navigation and PDF timeouts appropriate to your application, and decide how a slow third-party asset should fail.
  • Limit concurrent browser pages according to measurements from your own document and host. No universal concurrency or memory limit is established by the cited documentation.
  • Check sandbox settings, fonts, local-file access, and outbound network policy on the deployment platform rather than assuming local development behavior will match.
  • Close pages and browsers on success, errors, and client disconnects. A long-lived browser pool can reduce startup work, but it adds capacity and isolation decisions that you must test.

Create or modify PDFs with pdf-lib

Use pdf-lib when the input or output is a PDF document rather than a web page. You can create a document, add a page, draw text, and save bytes; you can also load an existing PDF and modify or embed pages.

Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
  • Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
  • Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
  • In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
  • Ultra-thin bezels: Maximize your viewing experience with thin bezels.
import express from 'express';
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';

const app = express();

app.get('/certificate.pdf', async (req, res, next) => {
  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage([595.28, 841.89]); // A4 points
    const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
    page.drawText('Certificate', {
      x: 72, y: 720, size: 28, font, color: rgb(0.05, 0.2, 0.4)
    });
    page.drawText('Issued by the example application', {
      x: 72, y: 675, size: 14, font
    });

    const bytes = await pdfDoc.save();
    res.type('application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="certificate.pdf"');
    res.send(Buffer.from(bytes));
  } catch (error) {
    next(error);
  }
});

app.listen(3000);

For an existing document, read bytes from a controlled upload or storage location, call PDFDocument.load(bytes), perform the documented edits, then call save(). Validate ownership and authorization before loading any user-selected file.

Express response choices and file safety

res.send(Buffer) is convenient for non-streaming output such as pdf-lib bytes or Puppeteer’s returned bytes. A stream such as PDFKit should be piped to the response. Express also provides res.download() and res.sendFile() for filesystem files, but those APIs access the running filesystem. Constrain paths with the documented root option or resolve them against a fixed directory; do not let a query parameter select arbitrary paths.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Set Content-Type: application/pdf (or use res.type('application/pdf')).
  • Choose inline for browser viewing and attachment for downloading.
  • Use a safe, deterministic filename and quote it correctly.
  • Do not send JSON after PDF bytes have started. Check res.headersSent before passing an error to the error middleware.
  • Authenticate report data and validate query parameters before generating a document.

Common failures and fixes

The request hangs

In an Express route, ensure every branch sends, streams, or calls next(). For PDFKit, confirm both doc.pipe(res) and doc.end() execute. An exception before either call should reach error middleware.

Rank #4
Samsung 27" Essential S3 (S36GD) Series FHD 1800R Curved Computer Monitor
  • CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
  • SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
  • MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
  • KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
  • INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient

The PDF is empty or corrupt

With PDFKit, do not end the document before writing content. With Puppeteer, await page readiness and send the complete bytes only after page.pdf() resolves. With pdf-lib, await save() and convert the resulting bytes to a Node.js Buffer.

CSS looks wrong

Puppeteer prints with print media by default. Move print-specific rules into @media print, set printBackground: true when backgrounds are required, or explicitly emulate screen media. Check @page size, margins, and page-break rules.

Fonts or images are missing

Wait for network resources and fonts, use reachable URLs or embedded assets, and verify that the production container contains the required files. A local browser cache or developer font can hide deployment problems.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
  • 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
  • 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.

Chromium fails to launch

Inspect the installed Puppeteer/browser version, operating-system libraries, sandbox policy, and executable path on the host. These are environment-specific checks; do not assume a configuration that works on one provider works everywhere.

A user can read another file

Never concatenate untrusted input into a path. Map an authenticated document identifier to a server-side record, resolve it under a fixed root, and reject traversal or unexpected extensions before calling file-transfer helpers.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your source is a URL and you want a rendered capture or PDF without managing Chromium in your Express process, ScreenshotNeo provides a website screenshot API and MCP server. A GET request returns a clean PNG, JPEG, WebP, or PDF; see the API documentation for the current output options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());

It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Every plan includes the features; 1,000 shots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Operational and cost considerations

  • PDFKit streams output, which can avoid holding the entire document in memory, but layout complexity and asset handling still need measurement.
  • Puppeteer adds browser startup, page isolation, rendering time, and host-level resource requirements. Measure your own templates and concurrency.
  • pdf-lib and Puppeteer examples buffer bytes before res.send(); impose sensible document and request limits for untrusted input.
  • Cache only when the underlying data and authorization permit it. Do not cache a personalized PDF under a shared URL.
  • Log generation failures, duration, document identifiers, and response status without logging sensitive document contents.

Frequently Asked Questions

Can I generate a PDF without saving it to disk?

Yes. Pipe a PDFKit document to the Express response, or send the bytes returned by Puppeteer or pdf-lib directly with a PDF content type.

Should I use PDFKit or Puppeteer for an invoice?

Use PDFKit when the invoice is structured data that you are willing to lay out in PDF primitives. Use Puppeteer when an HTML invoice already matches the design you need to print.

Can Express return a PDF from a POST request?

Yes. Validate the posted data, generate the document in the handler, and use the same response headers and stream or buffer technique shown for GET routes.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.