npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pptx-kit

v0.8.0

Published

Generate and edit .pptx (OOXML PresentationML) files from TypeScript, in Node and the browser.

Readme

pptx-kit

Generate and edit .pptx (PowerPoint / Office Open XML Presentation) files from TypeScript — in Node.js or the browser, from a single ESM bundle.

Status: 1.0 — public API stabilized. Every capability in the table below works end-to-end against real PPTX fixtures, with every emitted XML part validated against the ECMA-376 schemas via xmllint in CI. Future 1.x releases are SemVer-compatible.

Why

The JavaScript ecosystem has several PPTX libraries, but they typically pick one trade-off:

  • Node-only with a Buffer-shaped API → does not work in the browser.
  • Browser-only wrapping a fixed template → cannot author from scratch.
  • Loose XML strings that "usually open" → break in Keynote / Google Slides / the Open XML SDK validator.

pptx-kit is built around a different stance:

  • One ESM bundle that runs in Node and the browser.
  • A typed object model that mirrors the OOXML PresentationML spec (ECMA-376 Part 1, §19). When the spec says something is a choice, our types say it is a discriminated union.
  • Output that passes Microsoft's Open XML SDK Productivity Tool validator, not just PowerPoint's "open and pray."
  • Two complementary paths: author from scratch or edit a template.

Scope

The work is split into four levels of completeness. The v1.0 release targets levels 1-3 in full and level 4 in part:

| Level | Capability | v1.0 | | ----- | ------------------------------------------------------------------- | ------------------------------- | | L1 | Read an existing PPTX, save it back without corruption | ✅ | | L2 | Template edit — text replacement, image swap, add slide from layout | ✅ | | L3 | Authoring — shapes, text, tables, fills, effects, transforms | ✅ | | L3 | Authoring on top of existing themes / masters / layouts | ✅ | | L3 | Constructing new themes / masters / layouts from scratch | ❌ post-1.0 | | L3 | Charts (all common types) with embedded data | ✅ | | L4 | Notes, comments, transitions | ✅ | | L4 | Simple animations (entrance / exit / emphasis presets) | ✅ | | L4 | SmartArt authoring | ❌ post-1.0 (read pass-through) | | L4 | Complex animation timing trees | ❌ post-1.0 | | L4 | OLE / ActiveX authoring | ❌ post-1.0 (read pass-through) | | L4 | Document encryption (read + write) | ❌ post-1.0 |

Out-of-scope content is still preserved on round-trippptx-kit will never silently strip parts it doesn't model. That's the L1 contract.

When NOT to use this:

  • You need a pixel-perfect PPTX rendering (print, archival). The companion pptx-kit-preview package renders slides to SVG in the browser and to PNG on the server — its closeness to LibreOffice is measured per slide and gated in CI (site/fidelity) — but it is a high-fidelity preview, not a spec-complete paint engine. For pixel-authoritative output, use PowerPoint itself or LibreOffice headless.
  • You need a thin DSL for one-off "report" slides and do not care about schema validity. A simpler library will be lighter.
  • You want to convert PPTX to another format (Keynote, ODP). Out of scope forever — that's a renderer's job.

Install

npm install pptx-kit
# or
pnpm add pptx-kit
# or
yarn add pptx-kit

One API

pptx-kit exposes a single tree-shakeable free-function API. Every capability is a named export — loadPresentation, savePresentation, addSlideTextBox, setShapeFill, etc. Bundlers drop every entry you don't import, so the minimal load → save bundle is ~60 KB.

import {
  findSlidePlaceholder,
  getSlides,
  loadPresentation,
  savePresentation,
  setShapeText,
} from 'pptx-kit';

const pres = await loadPresentation(bytes);
const title = findSlidePlaceholder(getSlides(pres)[0]!, 'title');
if (title) setShapeText(title, 'Hello');
const out = await savePresentation(pres);

CI enforces the tree-shake bound in test/tree-shake.test.ts.

Usage

Edit a template

import {
  findSlidePlaceholder,
  getSlides,
  loadPresentation,
  savePresentation,
  setShapeText,
} from 'pptx-kit';

const pres = await loadPresentation(existingPptxBytes);
const cover = getSlides(pres)[0]!;
const title = findSlidePlaceholder(cover, 'title');
if (title) setShapeText(title, 'Q3 Review');
const body = findSlidePlaceholder(cover, 'body');
if (body) setShapeText(body, 'Numbers up and to the right.');

const out: Uint8Array = await savePresentation(pres);
// Node:    fs.writeFile('out.pptx', out)
// Browser: new Blob([out], { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' })

Token-based template fill

import { loadPresentation, replaceTokensInPresentation, savePresentation } from 'pptx-kit';

const pres = await loadPresentation(templateBytes);
// Replaces `{{name}}`, `{{event}}`, `{{date}}` across every slide.
replaceTokensInPresentation(pres, { name: 'Alice', event: 'Re:Invent', date: '2026-12-01' });
const out = await savePresentation(pres);

Build a deck from scratch (no template file)

createPresentation() returns an immediately-authorable deck — a slide master, the Office theme, and three layouts (Blank, Title Slide, Title and Content) — with no slides yet. No .pptx template needed.

import {
  addContentSlide,
  addTitleSlide,
  createPresentation,
  findSlideLayoutByType,
  addSlide,
  findSlidePlaceholder,
  savePresentation,
  setShapeText,
} from 'pptx-kit';

// Defaults to 16:9; pass { size: '4:3' } for the classic ratio.
const pres = createPresentation();

// Sugar helpers pick the right layout by its locale-stable type token.
addTitleSlide(pres, 'Q3 Business Review');
addContentSlide(pres, { title: 'Agenda', body: 'Highlights and risks' });

// Or bind a layout explicitly. Prefer findSlideLayoutByType — it matches
// the `type` token (`'title'`, `'obj'`, `'blank'`), which is stable
// across PowerPoint UI languages. findSlideLayout(pres, 'Blank') matches
// the user-visible name, which is case-sensitive and localized.
const titleLayout = findSlideLayoutByType(pres, 'title')!;
const slide = addSlide(pres, { layout: titleLayout });
setShapeText(findSlidePlaceholder(slide, 'ctrTitle')!, 'Authored with pptx-kit');

const out: Uint8Array = await savePresentation(pres);

Build a deck from a blank template

import {
  addSlide,
  addSlideImage,
  addSlideTextBox,
  duplicateSlide,
  findSlideLayout,
  findSlidePlaceholder,
  inches,
  loadPresentation,
  moveSlide,
  savePresentation,
  setShapeText,
} from 'pptx-kit';

const pres = await loadPresentation(await fetch('/blank.pptx').then((r) => r.arrayBuffer()));

const titleLayout = findSlideLayout(pres, 'Title Slide')!;
const slide1 = addSlide(pres, { layout: titleLayout });
setShapeText(findSlidePlaceholder(slide1, 'ctrTitle')!, 'pptx-kit demo');
setShapeText(findSlidePlaceholder(slide1, 'subTitle')!, 'an OOXML library for TypeScript');

const blank = findSlideLayout(pres, 'Blank')!;
const slide2 = addSlide(pres, { layout: blank });
addSlideTextBox(slide2, {
  x: inches(1),
  y: inches(1),
  w: inches(8),
  h: inches(1),
  text: 'Free-form text box',
});
addSlideImage(slide2, imageBytes, { x: inches(1), y: inches(3), w: inches(3), h: inches(3) });

const dup = duplicateSlide(pres, slide2);
moveSlide(pres, dup, 0);

const out: Uint8Array = await savePresentation(pres);

Replace an image in place

import {
  getShapeKind,
  getShapeName,
  getSlideShapes,
  getSlides,
  loadPresentation,
  savePresentation,
  setShapeImage,
} from 'pptx-kit';

const pres = await loadPresentation(templateBytes);
for (const slide of getSlides(pres)) {
  for (const shape of getSlideShapes(slide)) {
    if (getShapeKind(shape) === 'picture' && getShapeName(shape) === 'Logo') {
      setShapeImage(shape, newLogoBytes); // format auto-detected; geometry preserved
    }
  }
}
const out = await savePresentation(pres);

Node convenience entry

import { loadPresentationFile, savePresentationToFile } from 'pptx-kit/node';

const pres = await loadPresentationFile('./template.pptx');
await savePresentationToFile(pres, './out.pptx');

Charts

import { addSlideChart, getSlides, loadPresentation, savePresentation, inches } from 'pptx-kit';

const pres = await loadPresentation(templateBytes);
const slide = getSlides(pres)[0];
addSlideChart(slide!, {
  x: inches(0.5),
  y: inches(0.5),
  w: inches(8),
  h: inches(4.5),
  spec: {
    kind: 'column', // bar | column | line | pie | doughnut | area
    categories: ['Q1', 'Q2', 'Q3', 'Q4'],
    series: [
      { name: 'Revenue', values: [120, 180, 240, 300] },
      { name: 'Cost', values: [80, 90, 130, 160] },
    ],
    title: 'FY26 plan',
  },
});

await savePresentation(pres);

The embedded xlsx that PowerPoint requires for "Edit data" is generated automatically. Inline <c:strCache> / <c:numCache> caches mean the chart renders without opening the workbook.

Animations

import { setShapeAnimation, getSlideShapes, getSlides } from 'pptx-kit';

const slide = getSlides(pres)[0]!;
const shape = getSlideShapes(slide)[0]!;
setShapeAnimation(shape, { effect: 'fadeIn', durationMs: 800 });
// effects: 'fadeIn' | 'fadeOut' | 'appear' | 'disappear'

Comments

import { addSlideComment, getSlides } from 'pptx-kit';

const slide = getSlides(pres)[0]!;
addSlideComment(slide, {
  author: { name: 'Reviewer A' },
  text: 'Punch up the numbers here.',
  position: { x: 1_000_000, y: 1_000_000 }, // optional EMU coords
});

Gradient fills

import { setShapeGradientFill } from 'pptx-kit';

setShapeGradientFill(shape, {
  stops: [
    { offset: 0, color: '#FF0000' },
    { offset: 1, color: '#0000FF' },
  ],
  angleDeg: 90, // top → bottom
});

Validation

import { validatePresentation } from 'pptx-kit';

const issues = validatePresentation(pres);
for (const i of issues) console.error(i.severity, i.message);
// Catches missing rels, dangling slide ids, layouts without masters, etc.

API surface (current state)

Each row lists the free-function entry points. Read/write pairs are shown together.

| Capability | API | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Load / save | loadPresentation(input), savePresentation(pres), loadPresentationFile(path) (node), savePresentationToFile(pres, path) (node) | | Create | createPresentation({ size?: '16:9' \| '4:3' }) — blank deck with master + theme + Blank / Title Slide / Title and Content layouts | | Slide CRUD | getSlides, getSlideAt, getSlideIndex, addSlide, removeSlide, moveSlide, duplicateSlide, clearSlideShapes | | Slide layout | getSlideLayouts, findSlideLayout (by name — case-sensitive, exact; pass a RegExp for case-insensitive), findSlideLayoutByType (by locale-stable type token — preferred), getSlideLayout(slide), setSlideLayout(slide, layout), getSlideLayoutName, getSlideLayoutType | | Slide metadata | getSlideTitle / setSlideTitle, getSlideSize / setSlideSize, isSlideHidden / setSlideHidden, getSlideText | | Slide sections | getSlideSections, setSlideSections (p14 sectionLst) | | Placeholders | findSlidePlaceholder(slide, 'title' \| 'body' \| ...) | | Token / text replace | replaceTokensInPresentation, replaceTokensInSlide, replaceTextInPresentation, replaceTextInSlide | | Background | getSlideBackground / setSlideBackground / clearSlideBackground | | Notes | getSlideNotes / setSlideNotes | | Transitions | getSlideTransition / setSlideTransition / clearSlideTransition | | Animations | getShapeAnimation / setShapeAnimation (fadeIn / fadeOut / appear / disappear), clearSlideAnimations | | Comments | addSlideComment, getSlideComments, removeSlideComment, getCommentAuthors, getCommentText / getCommentAuthor / getCommentPosition | | Shape authoring | addSlideTextBox, addSlideShape, addSlideLine, addSlideTable, addSlideImage, addSlideChart | | Shape lookup | findShapeByName, findShapesByName, findShapesByKind, findShapeInPresentation, getAllShapes, getSlideShapes | | Shape text | setShapeText, setShapeBullets, setShapeAlignment, setShapeTextFormat, setShapeHyperlink / getShapeHyperlink | | Per-paragraph | setParagraphAlignment / getParagraphAlignment, setParagraphLevel / getParagraphLevel, setParagraphBullet / getParagraphBullet | | Per-run text | setShapeRunText / getShapeRunText, setShapeRunFormat / getShapeRunFormat, getShapeParagraphCount, getShapeRunCount | | Text frame | setShapeTextAnchor / getShapeTextAnchor, setShapeTextMargins / getShapeTextMargins | | Fill | setShapeFill / getShapeFill, setShapeGradientFill, setShapePatternFill, setShapeImageFill, setShapeNoFill, clearShapeFill | | Stroke | setShapeStroke / getShapeStroke, setShapeStrokeDash / getShapeStrokeDash, setShapeStrokeArrow / getShapeStrokeArrow, …NoStroke | | Effects | setShapeShadow / setShapeGlow / getShapeEffect, clearShapeEffects | | Geometry | setShapePosition, setShapeSize, setShapeRotation, setShapeFlip, setShapeBounds / getShapeBounds | | Pictures | setShapeImage, setShapeImageCrop / getShapeImageCrop, setShapeImageOpacity / getShapeImageOpacity, setShapeImageBrightness, …Contrast | | Z-order | bringShapeToFront, sendShapeToBack, bringShapeForward, sendShapeBackward | | Click actions | setShapeClickAction / getShapeClickAction (url / slide / nextSlide / prevSlide / firstSlide / lastSlide) | | Shape removal | removeShape | | Tables | getTableCell / getTableCells, setTableCellText / getTableCellText, setTableCellFill / clearTableCellFill, setTableCellAlignment, setTableCellTextFormat, insertTableRow / removeTableRow, insertTableColumn / removeTableColumn | | Charts | addSlideChart, getSlideCharts, setChartSpec — kinds: bar, column, line, pie, doughnut, area | | Theme | getPresentationTheme — color scheme (accent1..accent6, dark1, light1, hyperlink, ...) | | Validation | validatePresentation(pres) — invariant checks, returns ValidationIssue[] | | Units | inches(n), cm(n), mm(n), pt(n), emu(n) — return branded Emu numbers |

Compatibility

  • Node: >= 20.
  • Browsers: current and current-1 of Chrome, Firefox, Safari, Edge.
  • TypeScript: >= 5.4 (for strict satisfies and const type parameters).
  • Output: PPTX files validated against ECMA-376 schemas, smoke-tested against PowerPoint (current), Keynote (current), Google Slides, and LibreOffice Impress.

Development

git clone --recurse-submodules [email protected]:baseballyama/pptx-kit.git
cd pptx-kit
pnpm install
pnpm test

If you already cloned without submodules:

git submodule update --init --recursive --depth 1

references/ holds reference implementations and spec material we read while building this library. See references/README.md.

Contributing

Before opening an issue or PR, please read CLAUDE.md — it documents the project's design rules, the "one way to do one thing" policy, and what counts as a real bug report vs. a low-effort AI-generated one.

PRs are expected to:

  • Follow the template (.github/pull_request_template.md).
  • Include a failing test in the same PR that the change makes pass.
  • Add a changeset (pnpm changeset) for user-visible changes.
  • Pass pnpm typecheck, pnpm lint, and pnpm test.

License

MIT