decktape: turning HTML slide decks into PDFs with headless Chrome
PDF exporter for HTML presentations
At a glance
- What is it?
- A Node.js command line tool that drives Puppeteer over a dozen presentation frameworks and writes a PDF, with a generic mode for the ones that expose no API.
- Who is it for?
- decktape is a good fit when the deck already lives in the browser as HTML, because it reuses the framework and the fonts you already ship instead of asking a design tool to rebuild the layout. It is a poor fit when the deck is a PowerPoint file, or when the slides load data asynchronously and you would rather set a fixed wait than tune timeouts.
- 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 85 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What decktape does with a headless Chrome instance
decktape is not a rendering library. It is a script that loads a presentation URL into a headless Chrome instance, walks the slides one at a time, and assembles what Chrome renders into a PDF. The README is explicit about the dependency chain: DeckTape is built on top of Puppeteer, which relies on Google Chrome for laying out and rendering web pages and provides a scriptable headless instance through a JavaScript API. That single decision explains most of the tool's behaviour, including its strengths and its sharp edges.
The strength is fidelity. The deck is rendered by the same engine that renders it live, so CSS transforms, web fonts, SVG and canvas output all land in the PDF the way a browser audience would see them. The project describes itself as a high-quality PDF exporter for HTML presentation frameworks, and the README renders that claim in the only way that matters: there is a gallery of exported decks linked from the documentation.
The sharp edge is that everything a browser can get wrong, a headless browser can get wrong too. If a slide animates in over 400 ms and the exporter advances before the animation settles, the PDF captures a half-finished frame. Almost every option in the tool is a knob for that class of problem.
Installing the command globally or per project
There are two documented install paths, and the README shows both. The global one puts a `decktape` binary on your path:
npm install -g decktape
decktapeThe local one keeps the dependency in a project's `package.json` and runs it through `npm exec`:
npm install decktape
npm exec decktapeThe local form is the one to prefer for CI, since it pins the version through the lockfile and a given commit of your build always gets the same exporter. The package declares `"engines": { "node": ">=18" }`, so an older runtime will install with a warning rather than a clean failure. The declared version in `package.json` is 3.16.1, and the dependency list is short and tells you a lot: Puppeteer and Puppeteer Core at 24.40.0, `pdf-lib` for the final document assembly, `chalk` for terminal output, `fonteditor-core` and `urijs` for font and URL handling.
One detail worth planning around: the entry point is `decktape.js`, declared as an ES module, and the npm scripts call it directly with `node decktape.js`. There is no build step to run before use.
The plugin list and why the automatic command usually works
DeckTape handles a dozen frameworks out of the box: Bespoke.js, deck.js, DZSlides, Flowtime.js, impress.js, Inspire.js, NueDeck, remark, reveal.js, RISE, Shower, Slidy and WebSlides. Each one is a plugin, and the default `automatic` command iterates over the available plugins, picks the compatible one for the presentation at the given URL, and uses it to write the PDF.
That design is more useful than it first sounds. Each framework exposes its slide list in a different way, some through a documented API and some through internal globals, and DeckTape hides that difference behind a single set of command line options. The README describes the plugin architecture as exposing an extension API, which is what makes it possible to add a framework the project does not ship, or to modify an existing plugin to fit a custom deck.
The alternative worth naming is not another exporter but the browser print dialogue. Chrome can already print any page to PDF, and the `--chrome-arg` option passes arguments straight to the Chrome instance, so a print-to-PDF flag is reachable in principle. What you do not get is per-slide pagination, a defined viewport size, or a known slide count. DeckTape's reason to exist is that a slide deck is a sequence, not a page.
Generic mode for decks that expose no API
The `generic` command exists for frameworks that expose neither an API nor accessible state. Instead of reading the deck's internals, it emulates the end user: it presses the key named by the `--key` option and iterates the presentation as the DOM changes. The README describes the loop as continuing while any change to the DOM is detected by observing mutation events targeting the body element and its subtree, and while the number of slides exported has not reached the configured maximum.
Mutation observation is the interesting half. It means DeckTape does not have to know which element holds the slide: any change anywhere under `<body>` counts as evidence that the framework moved on. That is a genuinely portable signal, and it is also the source of the tool's flakiness. A slide with an animated background, a live clock, or a randomly ordered image will keep mutating and can stall or advance early.
The README recommends generic mode for HTML presentation frameworks that do not expose an API, and separately lists it as a way to convert presentations from virtually any kind of framework. Treat that as a fallback with a known failure mode rather than a first choice. A deck with live content is the case where you will be tuning `--pause` by hand.
Viewport size, slide ranges, screenshots and timeouts
The option list is where this tool shows its age and its seriousness at the same time. The viewport is set with `--size`, for example a 16:9 value such as 1280x720. Individual slides can be exported with `--slides`, which accepts a combination of indexes and ranges in the form `'1-3,5,8'`. `--screenshots` captures each slide as an image into `--screenshots-directory`, at resolutions set by the repeatable `--screenshots-size`, in `jpg` or `png`.
The screenshot mode is listed as useful for checking that a presentation is responsive, or for producing handouts, and it is a reasonable substitute for pageres when you want to stay inside one tool. The remaining options are timeouts, and their defaults are worth internalising: `--pause` at 1000 ms before each slide is exported, `--url-load-timeout` at 60000 ms for the initial URL, `--page-load-timeout` at 20000 ms for the deck page, `--buffer-timeout` at 30000 ms while a slide finishes buffering, and `--load-pause` at 0 ms between page load and export starting. `--headless` accepts `new`, `true` or `false`.
Two options are more commonly needed than they look. `--headers` takes a comma-separated list of `header,value` pairs, which is how you reach a deck behind a token. `--chrome-path` points DeckTape at your own Chromium or Chrome executable instead of the bundled build, and `--chrome-arg` adds arguments to that instance.
The container build and what the release history shows
The repository ships a Dockerfile, so CI does not need a browser download at all. It is a two-stage build: a Node builder installs production dependencies, then the runtime stage is Alpine 3.22 with Chromium, CA certificates, FreeType, HarfBuzz, Noto emoji and a Chinese font package installed from apk, and the Node binary plus the decktape directory copied in from the builder.
docker build -t decktape .
docker run --no-sandbox decktapeThe comment at the bottom of the Dockerfile explains the `--no-sandbox` requirement and points at the Puppeteer troubleshooting documentation, which is the usual Chrome-inside-a-container story. The runtime image pins Chromium through a build argument, currently 147.0.7727.101-r0, which is also why the release notes read the way they do. The v3.16.1 release from 2026-04-20 upgraded Chromium to that version and set video current time before rendering so video slides render deterministically. The v3.16.0 release a week earlier moved the image to Alpine 3.22, upgraded Puppeteer twice, and worked around a race condition involving iframes in the end-to-end tests.
The project is not archived and the last push was on 2026-07-13. The README is the document to read; beyond it, the tree shows `plugins/`, `libs/`, `docs/` and a Playwright based `test/` directory, which tells you the framework adapters are the maintained part and the exporter around them changes far less often.
Editorial conclusion
decktape is a good fit when the deck already lives in the browser as HTML, because it reuses the framework and the fonts you already ship instead of asking a design tool to rebuild the layout. It is a poor fit when the deck is a PowerPoint file, or when the slides load data asynchronously and you would rather set a fixed wait than tune timeouts. The README documents the option surface well and stops there, so the things worth verifying first are the plugin that matches your framework and the pause and timeout defaults, which sit at 1000 ms and 20000 ms respectively. Start with the automatic command against a URL you control, then pin down the version of Chromium your build ends up with.
Frequently asked questions
Which HTML presentation frameworks does decktape support?
Bespoke.js, deck.js, DZSlides, Flowtime.js, impress.js, Inspire.js, NueDeck, remark, reveal.js, RISE, Shower, Slidy and WebSlides work out of the box, each through its own plugin. The default automatic command picks the matching plugin for the URL you pass. For anything else, the generic command drives the deck by emulating a key press.
Does decktape need Google Chrome installed on the machine?
It relies on Chrome through Puppeteer, which normally downloads a bundled Chromium as part of the Puppeteer install. If you already have a Chrome or Chromium build you prefer, the --chrome-path option points decktape at that executable instead. In Docker, the bundled route is avoided entirely because the image installs Chromium from Alpine's package repository.
How do I export only some slides to a PDF?
Use the --slides option, which takes a combination of indexes and ranges such as '1-3,5,8'. Slide size is a separate concern and is set with --size, for example 1280x720 for a 16:9 deck. Command specific options for a named plugin can be listed with decktape followed by the command name and -h.
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/astefanutti-decktape)