Compile JavaScript slide files into PowerPoint decks. Author slides as plain .js files using a design-system API — no PowerPoint, no drag-and-drop.
Try it live — no install required.
Each workspace is a folder under workspaces/. You write slide files in workspaces/<slug>/slides/, optionally define a color scheme in theme.js, and run forge to get a .pptx.
workspaces/my-deck/
├── theme.js # colors, header text, footer text
├── slides/
│ ├── 01-overview.js
│ └── 02-approach.js
└── out/
└── my-deck.pptx # generated
- Node.js 18+
chromium-headless-shell(only needed forforge --images) — install withnpx playwright install chromium-headless-shell
npm installnpm install does not download any browser binaries. forge --images needs chromium-headless-shell installed separately (see above); nothing else in this project requires it.
If you're developing pptx-forge itself and plan to run the e2e test suite (npm run test:e2e, using @playwright/test), npx playwright install (no arguments) installs both full Chromium and chromium-headless-shell in one step — after that, forge --images also works with no further setup.
npm run create
# Workspace name: my-deckCopies the starter template into workspaces/my-deck/ and prints next steps.
npm run forge my-deck
npm run forge workspaces/my-deck # relative path — useful for tab completion
npm run forge workspaces/my-deck/slides/overview.js # single slide file — only that slide is includedAliases: npm run build my-deck, npm run generate my-deck
Output: workspaces/my-deck/out/my-deck.pptx
Pass a path to a single slide .js file instead of a workspace to compile just that slide — the rest of slides/ is skipped, but theme.js and the output path still resolve from the enclosing workspace.
| Flag | Short | Description |
|---|---|---|
--open |
-o |
Open the generated file in the default app after compiling |
--preview |
-v |
Preview the generated file in QuickLook — macOS only |
--snapshot |
-t |
Write to a timestamped file (my-deck_2026-06-29_14-30-00.pptx) instead of overwriting |
--images |
-i |
Export every slide as a PNG next to the generated file |
--help |
-h |
Show usage and exit |
Note: Due to how npm parses arguments, flags must be separated from the script name with
--.
npm run forge my-deck -- --open # compile and open
npm run forge my-deck -- --preview # quick preview via QuickLook (macOS)
npm run forge my-deck -- --snapshot # timestamped output
npm run forge my-deck -- --open --snapshot # both
npm run forge my-deck -- --images # export my-deck-01.png, my-deck-02.png, ...
npm run forge my-deck -- --images --snapshot # export my-deck_2026-06-29_14-30-00-01.png, ...
npm run forge -- --help # show help--images writes one PNG per slide to workspaces/<slug>/out/, overwriting any file already at that path. Filenames follow <slug>[_<timestamp>]-NN.png — the timestamp is included only when --snapshot is also passed, using the exact same timestamp as the .pptx written in that run.
Rendering is done in a headless browser (chromium-headless-shell), reusing the same renderer the browser tool's live preview uses. If it isn't installed yet, --images fails with an error telling you to run:
npx playwright install chromium-headless-shellIf you've already run npx playwright install for the e2e test suite (see below), --images works immediately — that command installs chromium-headless-shell alongside full Chromium by default.
npm run backup my-deck
npm run backup workspaces/my-deck # relative path — useful for tab completionZips slide and theme files into workspaces/my-deck/backups/my-deck_<timestamp>.zip.
npm run watch -- my-deck # quiet rebuild on every change, no flags forced
npm run watch -- my-deck --snapshot # timestamped file on every change
npm run watch -- my-deck --snapshot --open # timestamped file, opened after every change
npm run watch -- my-deck --images # re-export slide PNGs on every change
npm run watch -- workspaces/my-deck --snapshot # relative path — useful for tab completionWatches workspace source files and re-runs forge when they change, forwarding whatever forge options you pass — --open, --preview, --snapshot, --images, any combination. Nothing is forced: pass exactly what you'd pass to npm run forge directly.
- Debounces rapid edit bursts into a single generation cycle.
- Ignores generated
out/andbackups/paths to prevent feedback loops. - Runs until stopped with Ctrl+C.
A no-install alternative to the CLI: pptx-forge.html is a single self-contained HTML file that runs the same compile pipeline entirely client-side. Double-click it to open — no Node, no server, no network access required.
- Use it online: pptx-forge.html — hosted version of the latest release, opens directly in your browser, nothing to download
Or download a pre-built copy from GitHub Releases — no build step required:
- Latest version: pptx-forge.html — always resolves to the newest release
- A specific version:
https://github.com/awaragi/pptx-forge/releases/download/<version>/pptx-forge-<version>.html(e.g. 1.1.0)
Or build it yourself from source:
npm run build:browserThis bundles pptxgenjs, jszip, and the src/ rendering library into pptx-forge.html at the repo root (gitignored — rebuild it locally when you need it).
Open the generated pptx-forge.html in a browser and:
- Drag and drop, click "Load files…", or click "New slide" to add
theme.js(optional — a placeholder is preloaded) and one or more slide.jsfiles. - Click a file in the sidebar to edit it. Dropping a file with a name that already exists replaces its content in place.
- Click the filename above the editor (or its pencil icon) to rename it in place — edit the name directly (the
.jsextension is fixed and shown separately, not editable), Enter to confirm or Escape to cancel. Use the toolbar's Discard/Download icons to remove or download the file, or click "⚡ Forge" to compile everything currently loaded into a downloadable.pptx. Rename and Discard aren't available fortheme.js.
A live preview pane sits below the editor, rendering the slide file you're currently editing — it recompiles and re-renders automatically a moment after you stop typing, with no manual refresh needed. While theme.js or masters.js is open instead of a slide, it keeps showing whichever slide you last viewed, updated live with your in-progress theme/master edits. Drag the divider above it to resize, or use the toolbar's collapse button to hide it entirely — both the size and collapsed state are remembered across reloads. The toolbar also has buttons to copy the current preview to the clipboard as a PNG or download it as one.
A slide file can call pptx.addSlide() more than once — the preview pane handles this by showing numbered slide buttons in the toolbar (or, when there isn't room for all of them, compact ‹ N / M › arrows instead), letting you step through every slide the file produces. Left/Right arrow keys also work once the preview has focus. Downloading a multi-slide file's preview names the file after the sub-slide currently shown (e.g. deck-2.png).
Only .js files are accepted, and Forge requires at least one slide file besides theme.js. This tool has no special sandboxing around the code it runs — it's meant for the same trusted, local use case as the CLI, just without requiring Node.
Import/export workspace zips follow a strict layout: theme.js at the root, slide files under slides/. Import requires theme.js at the root or it fails with an error; any other entries in the zip (stray files, .js files outside slides/, nested folders) are silently ignored.
Each slide file exports a default function:
export default function Slide01_Title(pptx, lib) {
const { theme, prim, layout, frame } = lib;
const slide = pptx.addSlide();
slide.background = { color: theme.color.surface };
frame.border(slide, undefined, {}, 's01-border');
frame.slideHeader(slide, undefined, {}, 's01');
frame.slideFooter(slide, undefined, {}, 's01');
layout.sectionTitle(slide, null, 'Hello World', {}, 's01-title');
}For AI-assisted authoring, share INSTRUCTIONS.md and lib.d.ts with your model. These two files contain everything needed to generate correct slide files.
pptx-forge makes no backward compatibility guarantees between versions. The tool's purpose is to generate a .pptx and move on — a workspace is a point-in-time artifact, not a long-lived application. When the library changes, existing slide files may need updating. New projects should start from the current version.
GPL-3.0-or-later — see LICENSE. You are free to use, modify, and distribute this software under the terms of the GNU General Public License v3 or any later version; you may not incorporate it verbatim into proprietary software without releasing your changes under the same license.
All components are available via lib.comp. Destructure the ones you need at the top of each slide function.
| Component | Content | Description |
|---|---|---|
infoCard |
{ title, body } |
Standard card with title + body |
accentCard |
{ title, body } |
Card with a thin top accent bar |
challengeCard |
{ title, body } |
Card with a left accent bar — for challenges or risks |
overlayCard |
{ title, body } |
Semi-transparent variant for dark backgrounds |
fileCard |
{ filename, purpose, step } |
Named file/artifact with purpose + next step |
numberedStep |
{ num, title, body } |
Circle number badge + title + body |
stepBox |
{ label, steps: string[] } |
Phase/step container; steps joined with · |
imageCard |
{ image?, title, body?, imageH? } |
Card with an image placeholder band at top |
iconBox |
{ icon?, title, body? } |
Centered large icon + title + optional body |
teamCard |
{ name, role, bio? } |
Circular avatar placeholder + name + role + optional bio |
| Component | Content | Description |
|---|---|---|
iconStat |
{ value, label, icon? } |
Large KPI value + label, optional icon above |
darkStat |
{ value, label, source? } |
Dark-background KPI tile with optional citation line |
| Component | Content | Description |
|---|---|---|
flowBox |
{ label, highlight? } |
Flow diagram box; highlight: true applies accent fill |
flowArrow |
(none) | Arrow connector; opts.vertical: true for ↓ |
stepFlow |
{ label, highlight? }[] |
Auto-lays out flow boxes + arrows across the box width |
phaseLabel |
label (string) |
Accent badge + horizontal rule — section divider |
| Component | Content | Description |
|---|---|---|
calloutQuote |
{ label?, quote } |
Left accent bar + optional label + insight/quote text |
| Component | Content | Description |
|---|---|---|
bulletIconList |
{ icon, text }[] |
Icon-prefixed bullet lines |
twoColumnRow |
{ label, content } |
Left label / right content row — stack to build key-value tables |
| Component | Content | Description |
|---|---|---|
progressBar |
{ value, label?, showPct? } |
Horizontal fill bar; value is 0–1 |
tagBadge |
{ label } |
Small filled pill badge |
imageHolder |
{ icon?, label? } |
Dashed placeholder box — swap with a real image in PowerPoint |
If the built-in comp components don't meet your design needs, it is entirely acceptable to build new ones directly in your slide files using prim primitives. There is no requirement to stay within the existing component set.
It is also acceptable to use pptxgenjs directly (e.g. slide.addText(), slide.addShape(), slide.addImage()) when lib doesn't cover what you need. Prefer lib where it applies — fall back to raw pptxgenjs only when necessary. See the pptxgenjs docs for reference.
If you create something reusable and well-tested, sharing it back with the forge is highly appreciated — open a PR with the implementation or file an issue and paste the function code. Contributions help grow the shared component library for everyone.
Planned features and improvements:
- Syntax highlighting — Add JavaScript syntax coloring to the code editor for better readability
- Version compatibility —
theme.jsand slide.jsfiles should export a version number that must match the library version; mismatch causes runtime errors or bad output - Version history — Store version numbers with workspaces to display changelog since last compilation
- Master slide support — Allow defining and using master slides to maintain consistent layout and styling across multiple slides
- Responsive design — Improve layout adaptability for different screen sizes and resolutions
- Settings icon — Add a settings icon with options such as including components in AI prompts and generating file names with timestamps
- Support for other files — Support for other files (.md, .txt) in workspaces but not included in the generated deck and only for complete packaging of workspace zips
- Update numbering of slides — Update numbering of slides to be increment of 10 to allow insertion of different slides in between existing slides without having to rename all slides after the insertion point
- Trash can for deleted workspaces — Deleting a workspace should move it to an app-level trash (e.g. a picker reachable from the workspace switcher) so it can be restored later.