Round-trips real decks
Load a .pptx from PowerPoint, Keynote, Google Slides, or LibreOffice and save it back without corruption. Unknown parts — SmartArt, OLE, custom extensions — are preserved verbatim so the original consumer still opens them.
.pptx from Node and the browser.Full presentation model — slides, shapes, text, tables, charts, themes, notes, comments, transitions, animations — built around round-trip fidelity. Output validates against the ECMA-376 XSDs and opens cleanly in PowerPoint, Keynote, Google Slides, and LibreOffice. No native modules.
pnpm add @office-kit/pptx / npm i @office-kit/pptxLoad a .pptx from PowerPoint, Keynote, Google Slides, or LibreOffice and save it back without corruption. Unknown parts — SmartArt, OLE, custom extensions — are preserved verbatim so the original consumer still opens them.
180+ preset shapes, custom text formatting, tables, embedded charts (column / line / bar / pie / doughnut / area) with auto-generated xlsx, solid / gradient / pattern / image fills, shadows, glows, rotation, z-order.
Token replace (`{{name}}` → `Yamashita`) across slides and notes, image swap with geometry preserved, slide CRUD, placeholder inheritance from layout/master, hyperlinks, comments, transitions, animations.
Minimal load → save bundle ~61 KB unminified, full fn-API ~122 KB. One ESM build runs in Node and the browser. Every export is side-effect-free; bundlers drop what you do not import.
Both files below live under site/src/lib/examples/ and are type-checked
against the real library on every build — if an API renames, the site build fails.
Replace `{{tokens}}` across every slide and the speaker notes — the canonical full-library round-trip.
// Open a template .pptx, replace `{{tokens}}` everywhere, save it back.
//
// This file is imported as ?raw into the docs site so the snippet shown
// to readers is exactly what svelte-check / tsc compiled — if an API
// rename breaks this import, the docs build fails before deploy.
import { readFile, writeFile } from 'node:fs/promises';
import { loadPresentation, replaceTokensInPresentation, savePresentation } from '@office-kit/pptx';
const pres = await loadPresentation(await readFile('template.pptx'));
replaceTokensInPresentation(pres, {
name: 'Yamashita',
event: 'Re:Invent',
date: '2026-12-01',
});
await writeFile('out.pptx', await savePresentation(pres));
Add slides on a layout, drop in a text box, an image, and a chart, then save.
// Build a deck from a blank template: add slides on a layout, drop in a
// text box, an image, and a chart, then save.
import { readFile, writeFile } from 'node:fs/promises';
import {
addSlide,
addSlideChart,
addSlideImage,
addSlideTextBox,
findSlideLayout,
findSlidePlaceholder,
inches,
loadPresentation,
savePresentation,
setShapeText,
} from '@office-kit/pptx';
const pres = await loadPresentation(await readFile('blank.pptx'));
const titleLayout = findSlideLayout(pres, 'Title Slide');
if (!titleLayout) throw new Error('no Title Slide layout');
const cover = addSlide(pres, { layout: titleLayout });
const title = findSlidePlaceholder(cover, 'ctrTitle') ?? findSlidePlaceholder(cover, 'title');
if (title) setShapeText(title, 'Q3 review');
const blank = findSlideLayout(pres, 'Blank') ?? titleLayout;
const slide = addSlide(pres, { layout: blank });
addSlideTextBox(slide, {
x: inches(0.7),
y: inches(0.5),
w: inches(9),
h: inches(0.7),
text: 'Numbers up and to the right',
});
addSlideChart(slide, {
x: inches(0.7),
y: inches(1.5),
w: inches(8),
h: inches(4.5),
spec: {
kind: 'column',
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [{ name: 'Revenue', values: [120, 180, 240, 300] }],
title: 'FY26',
},
});
addSlideImage(slide, await readFile('logo.png'), {
x: inches(8),
y: inches(0.4),
w: inches(1.5),
h: inches(1),
});
await writeFile('out.pptx', await savePresentation(pres));
More in Getting started & Recipes.