# electron-pdf renders a URL or a Markdown file to PDF with Electron

> Electron-PDF is a command line tool and a Node library that produces PDF files from URLs, HTML or Markdown by driving a real Chromium window, which means the output matches what a browser renders rather than what a PDF library approximates. It is also a tool with a trap in it, because the in-memory mode needs closeWindow set to false or the process dies on a segmentation fault.

**fraserxu/electron-pdf** — 📄 A command line tool to generate PDF from URL, HTML or Markdown files.

- Repository: https://github.com/fraserxu/electron-pdf
- Stars: 1,293 · Forks: 140
- Language: JavaScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/fraserxu-electron-pdf

## The output is a browser screenshot turned into a page

Electron-PDF is a command line tool that leverages Electron to generate PDF files from URLs, HTML or Markdown files, and the mechanism explains its behaviour better than any feature list.

It opens a real browser window through Electron, loads the content, and asks Chromium to print it. That means the PDF is produced by a rendering engine that is the same one your users' browsers are, so the layout is the browser's layout, with the browser's font handling, its CSS support and its JavaScript execution. If your HTML relies on a flexbox edge case or a web font, the PDF matches the browser rather than a PDF library's approximation of one.

The tool is described as a powerful command line tool, and the readme also frames it as something that can be integrated into your application or used as a rendering engine for a PDF service, so there are two entry points. The CLI takes a source and a target path. The Node API exposes an ElectronPDF object that you start, and then create jobs on.

The audience is anyone who has a web page and needs it as a document: a developer generating invoices from a template, a team archiving a site as PDF, a documentation pipeline that turns Markdown into a printable manual. The Markdown path is worth calling out, because the package depends on marked, highlight.js and github-markdown-css, so Markdown input is rendered and styled rather than being treated as plain text.

The keywords in the package metadata list png and export as well as pdf, and the environment variables include a PNG capture delay, so the tool also captures images of the page, not only printable documents.

## The version number is the Electron version

The compatibility section is the most unusual part of the readme, and it is the first thing to read before anything else.

From version 4.0.x onwards, the master branch of Electron-PDF always aligns with the latest Electron version, and semantic versioning is followed, with the version numbers corresponding to Electron versions. The current line, electron-pdf 40.1.x, is pinned to electron 40.1.0, node 24.11.1 and chrome 144.0.7559.96, and the package metadata confirms it, with the version at 40.1.1 and the electron dependency at 40.1.0.

The table then walks back through the older lines, each with its own Electron, Node and Chrome versions: 25.0.x on electron 25.4.0 with node 16.15.0 and chrome 114, 20.0.x on electron 20.0.2, 15.0.x on electron 15.1.1, 10.0.x on electron 10.1.3, 7.0.x on electron 7, 4.0.x on electron 4, and the 1.3.x and 1.2.x lines on the very first Electron releases.

The readme states plainly that the choice of Chromium version affects the functionality you can utilise, and that you should choose the version that aligns with your needs. That is the practical consequence of the design: your PDF output is a function of the Chromium version you ship, and a browser engine upgrade can change the rendering of your documents without any change to your own code.

There is also a practical consequence for installation. Because Electron is a runtime dependency, the install pulls a large binary, and the package pins the Electron version exactly rather than with a caret range.

## Installing it, and the xvfb recipe for a headless Linux box

The normal installation is one npm command:

```bash
npm install electron-pdf
```

If you are installing as root with a system level npm rather than a user level install, the readme gives a different command and points at the npm documentation about the permission setting:

```bash
sudo npm install electron-pdf -g --unsafe-perm
```

The interesting part is the headless case. On GNU and Linux installations without a graphical environment, you need xvfb and a virtual display, and the readme gives the exact sequence:

```bash
sudo apt-get install xvfb # or equivalent
export DISPLAY=':99.0'
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
electron-pdf ...
```

That is a real operational cost and it is worth being explicit about it. Electron is a desktop application runtime, so on a server with no display it needs a virtual framebuffer to run at all, and the readme's own note says the Docker machine example lives in a separate repository, docker-tape-run.

The screen size in the example, 1024x768x24, is also more than a hint. Electron-PDF calculates the browser height and width from the dimensions of the PDF page size multiplied by the HTML standard of 96 pixels per inch, so the virtual display has to be large enough for the page you are asking for, and on a big format page you may need to raise it.

The alternative to all of this is to drive a headless browser you install and control yourself, where the browser has its own headless mode and needs no display server. The trade-off is the opposite of the one Electron-PDF makes: you give up the bundled, version matched runtime and take on the job of installing and keeping a browser current.

## The CLI takes a source, a target and a few options

The command line interface is the simplest way to see what the tool does, and the readme gives four examples that cover the source types.

An HTML file to a PDF:

```bash
$ electron-pdf index.html ~/Desktop/index.pdf
```

A Markdown file:

```bash
$ electron-pdf index.md ~/Desktop/index.pdf
```

A URL:

```bash
$ electron-pdf https://fraserxu.me ~/Desktop/fraserxu.pdf
```

And a file with your own stylesheet applied, using the -c flag:

```bash
$ electron-pdf index.html ~/Desktop/index.pdf -c my-awesome-css.css
```

The source and the target are positional, and the stylesheet is an option, so the shape of every invocation is the same. The tool also accepts a --browserConfig option that takes a JSON object, and the readme points at the Electron BrowserWindow documentation for the full list of options it forwards.

Two of the forwarded options are called out because they are the ones people get wrong. The height and width are computed by Electron-PDF from the PDF page size and the 96 pixels per inch standard, so you should only set them if you need to override that behaviour. And show controls whether the browser window is displayed during generation, which is a debugging switch rather than a production one.

The rendering section is described as giving you complete control of how the BrowserWindow should be configured and when the window contents should be captured, which is the right way to think about the tool: it is a Chromium window with a print command attached, and the options are the window's options.

## Embedding it: an exporter, jobs and events

The Node API exists for the case where the PDF is produced inside an application or behind a service endpoint, and the readme shows it with Express.

The setup is an ElectronPDF instance that you listen to and start:

```javascript
const ElectronPDF = require('electron-pdf')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json())

const exporter = new ElectronPDF()
exporter.on('charged', () => {
  app.listen(port, hostname, () => {
    console.log(`Export Server running at http://${hostname}:${port}`)
  })
})
exporter.start()
```

The charged event is the signal that the exporter is ready, and the example deliberately does not start the HTTP server before it fires. That ordering is what you want in a service: do not accept a request until the renderer is up.

Work is then submitted as a job, with the source, the target, the rendering options and the job options, and the job object emits its own events:

```javascript
app.post('/pdfexport', (req, res) => {
  const jobOptions = {
    inMemory: false // Set inMemory to true for Buffer export
  }
  const options = {
    pageSize: "A4"
  }
  
  exporter.createJob(source, target, options, jobOptions).then(job => {
    job.on('job-complete', r => {
      console.log('pdf files:', r.results)
      // Process the PDF file(s) here
    })
    job.render()
  })
})
```

The job completes asynchronously, so the response cannot be sent from the handler. The result object carries an array of results, and the comment in the example explains what is inside it. With inMemory false, the entry is the fully qualified path to a PDF file on disk, and with inMemory true it is the Buffer object as returned by Electron. The default is false, and the comment notes that this default cannot be set using the CLI.

The rendering options are where a document gets its shape, with pageSize set to A4 in the example.

## The inMemory trap: a segmentation fault if you forget closeWindow

The in-memory mode is the most useful feature for a service, because it lets you return the PDF in the HTTP response without writing a temporary file, and it is also the one with a documented crash.

The readme states it directly. If you set the inMemory setting to true, you must also set closeWindow=false, or you will get a segmentation fault anytime the window is closed before the buffer is sent on the response. The window has to outlive the buffer, which means you have to close it yourself afterwards:

```javascript
const jobOptions = { inMemory: true, closeWindow: false }
exporter.createJob(source, target, options, jobOptions).then(job => {
  job.on('job-complete', r => {
    // Send the Buffer here
    process.nextTick(() => { job.destroy() })
  })
})
```

The destroy call in the next tick is the other half of the contract. The window stays open because the buffer is still being read, and the job is destroyed once the response has been sent.

This is the failure mode to design around. A segmentation fault does not throw an exception you can catch, so a service that gets this wrong does not return a 500, it takes the whole process down. Two environment variables exist to limit the damage of a window that is never closed, ELECTRONPDF_WINDOW_CLEANUP_INTERVAL for the interval at which hung windows are checked, which defaults to 30 seconds, and ELECTRONPDF_WINDOW_LIFE_THRESHOLD for how long a window may stay open before it is terminated, which defaults to 5 minutes. And ELECTRONPDF_RENDERER_MAX_MEMORY sets the max-old-space-size option for each renderer process, defaulting to 75 percent of total system memory up to 8GB.

Those three variables are the operational safety net for a service, and the defaults are the specification of what the tool considers reasonable.

## A package on npm at 40.1.1, and a release tag from 2017

The maintenance picture has a wrinkle, and it is the kind that catches people out.

The recent releases listed for the repository are v1.1.5, v1.1.6 and v1.1.7, all dated in February 2017, while the package metadata declares version 40.1.1 and the compatibility table describes the 40.1.x line as the current master. The repository is not archived and the last push was on 2026-10-13, so the code is being worked on. What is not being maintained is the release tagging, and the readme explains the reason: from version 4.0.x the master branch always aligns with the latest Electron version, so the tags stopped following the package version.

The practical effect is that the GitHub release list tells you nothing about what npm will install. Pin by the package version in your lock file, not by a tag.

The licence is MIT, in the LICENSE file at the root, and the package metadata says the same.

The dependency set shows the age of the code more clearly than the release dates do. The Electron version is pinned exactly at 40.1.0, which is right for this project, but alongside it sit uuid at 2.0.1, marked at 0.3.5, highlight.js at 9.0.0, object-assign and eventemitter2, plus @sentry/electron for crash reporting, async, debug, lodash and minimist. Several of those are long superseded, and object-assign in particular is a polyfill for a language feature that has been in JavaScript for years.

The test setup is the other legacy detail. The scripts run ava for the unit tests and electron-ava for the integration tests, both piped through tap-diff, with linting by standard and a fix script that runs standard with --fix. The repository has a test directory, a cli.js entry point, the library in lib, a usage.txt and a .travis.yml, and a .nvmrc for the Node version. There is also a documented Sentry integration, so the tool reports its own crashes to a sentry project, and a debugging section covering the CLI and other output formats.

## Conclusion

electron-pdf is worth adopting when you need a PDF that looks like the page a browser would show, from a URL, a local HTML file or a Markdown document, and you would rather not maintain a headless browser of your own. It is the wrong choice for a server with no display and no ability to install one, since the headless Linux path needs xvfb, and it is the wrong choice if you need a headless browser you can pin and upgrade independently, because Electron-PDF's version number is its Electron version. Before you adopt it, read the inMemory section of the documentation carefully, because the documented segmentation fault is the one failure that will take down your process, and note that the package on npm is at 40.1.1 while the last tagged GitHub release is v1.1.7 from 2017, so the release list is not a guide to the current code.

## FAQ

### How do I install electron-pdf?

Run npm install electron-pdf. If you are installing as root with a system level npm rather than a user level install, the documentation gives sudo npm install electron-pdf -g --unsafe-perm instead and points at the npm documentation for the permission setting.

### Can electron-pdf run on a Linux server without a display?

You need xvfb and a virtual display. The documented sequence is to install xvfb, export DISPLAY=':99.0', start Xvfb on :99 with a 1024x768x24 screen, and then run electron-pdf. A Docker machine example is provided in a separate repository.

### What does the electron-pdf version number mean?

From version 4.0.x the master branch always aligns with the latest Electron version, so the package version tracks the Electron release. The current line, electron-pdf 40.1.x, corresponds to electron 40.1.0 with node 24.11.1 and chrome 144.0.7559.96, and the choice of Chromium version affects the functionality you can use.

### How do I use the in-memory export in electron-pdf?

Set the job options to inMemory true and closeWindow false, otherwise you get a segmentation fault when the window closes before the buffer is sent on the response. You then send the Buffer in the job-complete handler and call job.destroy afterwards, as the documentation example does inside process.nextTick.

### What are the environment variables in electron-pdf?

There are four. ELECTRONPDF_RENDERER_MAX_MEMORY sets the max-old-space-size option for each renderer process and defaults to 75 percent of total system memory up to 8GB, ELECTRONPDF_WINDOW_CLEANUP_INTERVAL defaults to 30 seconds, ELECTRONPDF_WINDOW_LIFE_THRESHOLD defaults to 5 minutes, and ELECTRONPDF_PNG_CAPTURE_DELAY defaults to 100ms before invoking WebContents.capturePage for PNG exports.

## Sources

- [fraserxu/electron-pdf on GitHub](https://github.com/fraserxu/electron-pdf)
- [Issues](https://github.com/fraserxu/electron-pdf/issues)
- [License: MIT](https://github.com/fraserxu/electron-pdf/blob/master/LICENSE)
- [README](https://github.com/fraserxu/electron-pdf/blob/master/README.md)
- [Releases](https://github.com/fraserxu/electron-pdf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fraserxu-electron-pdf
