html-pdf-chrome: HTML to PDF by attaching to a Chrome you already run
HTML to PDF or image (jpeg, png, webp) converter via Chrome/Chromium
At a glance
- What is it?
- html-pdf-chrome is a TypeScript library that renders HTML to PDF or image files by talking to a headless Chrome over the DevTools protocol. It is quick to adopt and blunt about its limits: the HTML must come from someone you trust.
- Who is it for?
- html-pdf-chrome suits a Node service that has to turn known, trusted markup into a PDF or an image and would rather reuse a Chrome it already keeps alive than manage a second rendering stack. It does not suit a pipeline that renders whatever a visitor submits, since the documentation rules that out and warns about server side request forgery.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The library attaches to a running Chrome instead of starting one
html-pdf-chrome renders HTML to PDF or to an image by driving Chrome or Chromium through the DevTools protocol. The architecture matters more than the feature list, because the library does not own the browser process. Its runtime dependencies are chrome-remote-interface, which speaks the protocol, and chrome-launcher, which can start a browser when needed, and the connection is made against a port that the caller names in the options. The documentation is explicit that keeping Chrome running next to Node is the recommended arrangement, because starting a browser for each PDF carries a lot of overhead. The result object returned by create exposes the output in four forms: a file on disk, a base64 string, a buffer and a stream. That shape is what makes it usable inside a web service, where the output usually has to become an HTTP response rather than a file on a disk.
Chrome startup cost, not rendering cost, is what you pay
The performance argument in the README is about process startup rather than page rendering. The text states that there is significant overhead in starting Chrome for each PDF generation and that this can be avoided by keeping Chrome alive. It points at pm2 as a way to ensure Chrome keeps running and restarts if it crashes, and it gives a memory figure of about 65mb of RAM for headless Chrome while idle. The example launches Chrome under pm2 with a set of flags that disable GPU use, translation, extensions, background networking, sync and audio, plus a remote debugging port that the Node side then targets. The port in that command is a placeholder, and the same port value has to appear in the options object passed to the library, so the two pieces have to agree. That arrangement also means Chrome is a long lived service that someone has to monitor, which is a different operational profile from a library call that starts and stops a browser for you.
A first render takes one npm install and one port number
The prerequisites are a current Chrome or Chromium, Windows, macOS or Linux, and a currently supported Node.js release. Installation is a single npm command.
npm install --save html-pdf-chromeThe simplest script passes an HTML string and a port, then writes the result to a file.
const htmlPdf = require('html-pdf-chrome');
const html = '<p>Hello, world!</p>';
const options = {
port: 9222, // port Chrome is listening on
};
htmlPdf.create(html, options).then((pdf) => pdf.toFile('test.pdf'));
htmlPdf.create(html, options).then((pdf) => pdf.toBase64());
htmlPdf.create(html, options).then((pdf) => pdf.toBuffer());
htmlPdf.create(html, options).then((pdf) => pdf.toStream());In TypeScript the same call is available as an async function and the options object is typed as CreateOptions. The port value is the only piece of configuration in this minimal example, and it is where the library meets the browser you started earlier. Nothing in the documentation describes a timeout or a retry for the case where that browser is not up, so a missing Chrome shows up as a failed call rather than a friendly error.
The same call produces PNG, JPEG and WebP when screenshotOptions is set
By default a page is saved as a PDF. Supplying screenshotOptions switches the output to an image, and the format field accepts png, jpeg or webp and defaults to png. A clip object limits the capture to a region, and the documentation notes it defaults to the entire window. Alongside it sits a deviceMetrics object that maps onto the DevTools method for overriding device metrics, which is what lets you emulate a particular viewport width, height and scale factor when you want the output to look like a phone or a specific screen size rather than like your own monitor. Since the underlying DevTools protocol exposes the full set of capture options, the library passes them through rather than wrapping them in a narrower API. That is convenient and also a maintenance consideration, because a change in the browser's protocol behaviour reaches you through this library rather than through your own code.
Untrusted HTML is explicitly out of scope
The security section is unusually direct for a package README. The library is not meant to accept untrusted user input, and doing so may bring serious risks such as server side request forgery. The mechanism is easy to see in the API: create accepts either an HTML string or a URL, and when it receives a URL, Chrome goes and fetches it, which turns the library into a request proxy for whatever the renderer can reach inside your network. There is a second sharp edge around CORS. The documentation suggests the --disable-web-security Chrome flag, either when starting Chrome externally or through options.chromeFlags, and warns that it should only be used if you fully trust the code being executed during the print job. Read that as: this flag turns off the browser's protection against a page reaching other origins. The documentation also notes that images must be inlined with base64 encoding at this time, so remote assets do not travel with the document.
Extra headers and print templates cover the boring half of a report
Two features earn their keep in real reporting work. The first is extraHTTPHeaders in CreateOptions, which adds headers to the request the renderer makes, so a page behind a token check can be captured without embedding the credential in the HTML.
const options: HtmlPdf.CreateOptions = {
port: 9222, // port Chrome is listening on
extraHTTPHeaders: {
'Authorization': 'Bearer 123',
'X-Custom-Test-Header': 'This is great!',
},
};The second is custom headers and footers, which need Chrome 65 or later and are given as an HTML template. A small set of classes are available for injecting printing values: date for the formatted print date, title for the document title, url for the document location, pageNumber for the current page and totalPages for the page count. Margins are adjusted through printOptions with marginTop, marginBottom, marginLeft and marginRight. The underlying difference from a template engine approach is that the caller decides who renders what: markdown can be converted with marked and Pug can be compiled with pug, and only the resulting HTML string is handed to the library.
MIT licensed, but the published package is older than the repository
The package is MIT licensed and the manifest declares Node 22.13.0 or newer as the supported engine range, which rules out a good deal of older CI imagery without changes. Development tooling is ordinary TypeScript: tsc for the build, eslint for linting and nyc with mocha for coverage. The gap worth noticing is between the repository and the registry. The manifest still carries version 0.8.4, and the newest published release, v0.8.4, dates from 2023-03-22, while the last push to the repository was on 2026-10-12. The repository has moved since the last npm release without a new published version, so anyone installing from npm is running code that is well behind the branch. The dependencies give a second signal: chrome-remote-interface and chrome-launcher are held at caret ranges, so a fresh install will pull newer browser protocol code than the pinned lockfile in the repository.
Editorial conclusion
html-pdf-chrome suits a Node service that has to turn known, trusted markup into a PDF or an image and would rather reuse a Chrome it already keeps alive than manage a second rendering stack. It does not suit a pipeline that renders whatever a visitor submits, since the documentation rules that out and warns about server side request forgery. Before adopting it, confirm your Node version satisfies the declared engine range of 22.13.0 or newer, keep Chrome on a dedicated debugging port that is not exposed to the network, and check whether the version you install from npm is the same code the repository is currently pushing.
Frequently asked questions
How do I install html-pdf-chrome?
Run npm install --save html-pdf-chrome. The prerequisites are a current Chrome or Chromium, a Windows, macOS or Linux host, and a currently supported Node.js release.
Does html-pdf-chrome need a running Chrome instance?
The recommended setup keeps Chrome running alongside Node.js, because starting Chrome for each PDF carries significant overhead. The documentation suggests pm2 to keep it alive and to restart it if it crashes, and to pass the Chrome debugging port through to the library options.
Can html-pdf-chrome produce PNG or JPEG instead of PDF?
Yes. By default pages are saved as a PDF, and supplying screenshotOptions switches the output to an image, with the format field accepting png, jpeg or webp and defaulting to png.
Is it safe to render user submitted HTML with html-pdf-chrome?
The documentation says the library is not meant to accept untrusted user input and warns that doing so carries risks such as server side request forgery. It also suggests the --disable-web-security Chrome flag only when you fully trust the code being executed during the print job.
How do I add headers to the rendered page request?
Use CreateOptions.extraHTTPHeaders to specify additional headers for the request, for example an Authorization header with a Bearer token or a custom header of your own.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/westy92-html-pdf-chrome)Was this useful?