§ 02 · Recipes
Common scenarios, type-checked snippets.
Every snippet below lives under site/src/lib/examples/ and is type-checked
against the live @office-kit/pptx surface on every build — an API rename breaks
this page before anything ships. Need a one-line lookup? See the Cheatsheet. Looking up a specific function? Jump to
the API reference.
Basics
# Open a presentation and walk every slide
Load an existing .pptx, iterate slides, and read title + text content.
where: site/src/lib/examples/recipes/open-and-iterate.ts
// Load a presentation, walk every slide, print titles and text length.
import { readFile } from 'node:fs/promises';
import {
getSlideText,
getSlideTextLength,
getSlideTitle,
getSlides,
loadPresentation,
} from '@office-kit/pptx';
const pres = await loadPresentation(await readFile('input.pptx'));
for (const slide of getSlides(pres)) {
console.log(`${getSlideTitle(slide) ?? '(no title)'} — ${getSlideTextLength(slide)} chars`);
console.log(getSlideText(slide));
}
- getSlideTitle returns the title placeholder text or null if the slide has no title placeholder.
- getSlideText concatenates every text body on the slide; getSlideOutline gives a structured outline if you need paragraph hierarchy.
# Token-based template fill
The canonical L2 workflow: open a designer-authored template, swap `{{tokens}}`, save.
where: site/src/lib/examples/template-fill.ts
// 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));
- Tokens are exact-match: {{name}} replaces only the literal `{{name}}` string in any text run.
- replaceTokensInPresentation also walks speaker notes; use replaceTokensInSlide for a single slide.
# Build a deck on a blank template
Start from a tiny blank.pptx, add slides on its layouts, and save.
where: site/src/lib/examples/recipes/build-from-scratch.ts
// Build a deck on top of a blank template: add a title slide and a content
// slide, then save.
import { readFile, writeFile } from 'node:fs/promises';
import {
addSlide,
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) {
const cover = addSlide(pres, { layout: titleLayout });
const t = findSlidePlaceholder(cover, 'ctrTitle') ?? findSlidePlaceholder(cover, 'title');
if (t) setShapeText(t, '@office-kit/pptx demo');
const sub = findSlidePlaceholder(cover, 'subTitle');
if (sub) setShapeText(sub, 'an OOXML library for TypeScript');
}
const blank = findSlideLayout(pres, 'Blank') ?? titleLayout;
if (blank) {
const body = addSlide(pres, { layout: blank });
addSlideTextBox(body, {
x: inches(1),
y: inches(1),
w: inches(8),
h: inches(1),
text: 'Free-form text box',
});
}
await writeFile('out.pptx', await savePresentation(pres));
# Brand a deck’s color scheme + fonts
setPresentationTheme / setPresentationFonts patch the theme’s color and font scheme — no template required.
where: site/src/lib/examples/recipes/theme-branding.ts
// Brand a from-scratch deck's color scheme and typography — no template
// required. setPresentationTheme / setPresentationFonts patch only the
// slots you name; every other theme slot keeps its default.
import {
createPresentation,
setPresentationFonts,
setPresentationTheme,
type PresentationData,
} from '@office-kit/pptx';
const pres: PresentationData = createPresentation();
setPresentationTheme(pres, {
name: 'Consulting Navy',
dark1: '#0B1F3A',
accent1: '#00A9E0',
accent2: '#FDB913',
accent3: '#6E7B8B',
});
setPresentationFonts(pres, {
majorLatin: 'Georgia',
minorLatin: 'Calibri',
});
// Every shape added from here on inherits the new scheme via the
// scheme-color tokens (accent1, tx1, ...) it was already using.
- Only the slots you pass are overwritten; every other color/typeface keeps its default.
- Colors are #RRGGBB strings; every theme slot is always a plain srgbClr, never a scheme-color reference.
# Direct fs helpers (Node)
loadPresentationFile / savePresentationToFile skip the manual fs glue.
where: site/src/lib/examples/node-fs.ts
// One-shot read + save direct from / to disk via the @office-kit/pptx/node
// helpers, no manual fs glue needed.
import { loadPresentationFile, savePresentationToFile } from '@office-kit/pptx/node';
const pres = await loadPresentationFile('input.pptx');
// ...mutate pres...
await savePresentationToFile(pres, 'output.pptx');
Authoring shapes
# Per-run text formatting
Address text by (paragraph, run) indices and apply font, size, color, bold / italic / underline.
where: site/src/lib/examples/recipes/text-formatting.ts
// Per-run formatting: bold, size, color, font on a single paragraph.
//
// Index a shape's text by (paragraphIndex, runIndex). Each \n in the
// `text:` argument starts a new paragraph; the first run of each is
// index 0.
import {
addSlideTextBox,
inches,
setParagraphAlignment,
setShapeRunFormat,
type SlideData,
} from '@office-kit/pptx';
declare const slide: SlideData;
const box = addSlideTextBox(slide, {
x: inches(0.7),
y: inches(1.5),
w: inches(9),
h: inches(2.5),
text: 'Default text\nBold red 24pt Calibri\nItalic underline 18pt',
});
setShapeRunFormat(box, 1, 0, {
bold: true,
size: 24,
color: '#C00000',
font: 'Calibri',
});
setShapeRunFormat(box, 2, 0, {
italic: true,
underline: true,
size: 18,
color: '#1F4E79',
});
setParagraphAlignment(box, 2, 'ctr');
- TextFormat.size is in points (number), not EMU. Color accepts #RRGGBB, RRGGBB, or a scheme token like "accent1".
- setShapeTextFormat (no index) applies the same format to every run in the shape.
# Preset shapes (180+ presets)
addSlideShape ships every ECMA-376 preset geometry — pass the token as `preset`.
where: site/src/lib/examples/recipes/preset-shapes.ts
// Drop in a few preset shapes from the 180+ DrawingML preset library.
import { addSlideShape, inches, type SlideData } from '@office-kit/pptx';
declare const slide: SlideData;
addSlideShape(slide, {
preset: 'star5',
x: inches(1),
y: inches(2),
w: inches(2),
h: inches(2),
text: '★',
});
addSlideShape(slide, {
preset: 'rightArrow',
x: inches(4),
y: inches(2.4),
w: inches(2.5),
h: inches(1.2),
text: 'next',
});
addSlideShape(slide, {
preset: 'roundRect',
x: inches(7),
y: inches(2),
w: inches(2.5),
h: inches(2),
text: 'callout',
});
- Use PresetShape from the public types for autocompletion. Unknown strings pass through to <a:prstGeom prst="..."/> verbatim.
# Fills, shadows, glows
Solid / gradient / pattern fills plus the two effect helpers.
where: site/src/lib/examples/recipes/fills-and-effects.ts
// Solid / gradient / pattern fills + a drop shadow and an outer glow.
import {
addSlideShape,
inches,
pt,
setShapeFill,
setShapeGlow,
setShapeGradientFill,
setShapePatternFill,
setShapeShadow,
type SlideData,
} from '@office-kit/pptx';
declare const slide: SlideData;
const solid = addSlideShape(slide, {
preset: 'rect',
x: inches(0.7),
y: inches(1),
w: inches(2.5),
h: inches(2),
text: 'Solid',
});
setShapeFill(solid, '#C00000');
setShapeShadow(solid, {
blurEmu: pt(8),
offsetEmu: pt(4),
angleDeg: 45,
color: '#000000',
opacity: 0.5,
});
const grad = addSlideShape(slide, {
preset: 'rect',
x: inches(3.5),
y: inches(1),
w: inches(2.5),
h: inches(2),
text: 'Gradient',
});
setShapeGradientFill(grad, {
stops: [
{ offset: 0, color: '#FFD966' },
{ offset: 1, color: '#C00000' },
],
angleDeg: 45,
});
const pat = addSlideShape(slide, {
preset: 'rect',
x: inches(6.3),
y: inches(1),
w: inches(2.5),
h: inches(2),
text: 'Pattern',
});
setShapePatternFill(pat, { preset: 'pct50', foreground: '#1F4E79', background: '#FFFFFF' });
setShapeGlow(pat, { radiusEmu: pt(12), color: '#2E75B6' });
# Group shapes into one component
Compose a rectangle + label into a "KPI card"; move/resize the group as one unit.
where: site/src/lib/examples/recipes/group-shapes.ts
// Compose a rectangle + label into a single "KPI card" component, then
// move/resize it as one unit — the members scale with it.
import {
addSlideShape,
addSlideTextBox,
groupShapes,
inches,
setShapeFill,
setShapePosition,
setShapeSize,
type SlideData,
} from '@office-kit/pptx';
declare const slide: SlideData;
const card = addSlideShape(slide, {
preset: 'roundRect',
x: inches(1),
y: inches(1),
w: inches(2.5),
h: inches(1.2),
});
setShapeFill(card, '#0B1F3A');
const label = addSlideTextBox(slide, {
x: inches(1.2),
y: inches(1.3),
w: inches(2.1),
h: inches(0.6),
text: 'Revenue +12%',
});
const kpiCard = groupShapes([card, label], { name: 'KPI Card' });
// Move + resize the group; the rectangle and label scale together.
setShapePosition(kpiCard, inches(5), inches(2));
setShapeSize(kpiCard, inches(3.75), inches(1.8));
- groupShapes needs every member to have an explicit position/size — placeholders that inherit geometry from the layout can't be grouped directly.
- ungroupShapes reverses it, rescaling each member if the group itself was moved/resized in between.
Tables, charts, images
# Add a table
A 2D array of cell strings, plus firstRow / bandRow flags for header styling.
where: site/src/lib/examples/recipes/add-table.ts
// Add a 5×4 table with a header row and banded styling.
import { addSlideTable, inches, type SlideData } from '@office-kit/pptx';
declare const slide: SlideData;
addSlideTable(slide, {
x: inches(0.7),
y: inches(1.5),
w: inches(9),
h: inches(3),
rows: [
['Quarter', 'Revenue', 'Cost', 'Margin'],
['Q1', '$1.2M', '$0.8M', '33%'],
['Q2', '$1.8M', '$0.9M', '50%'],
['Q3', '$2.4M', '$1.3M', '46%'],
['Q4', '$3.0M', '$1.6M', '47%'],
],
firstRow: true,
bandRow: true,
});
- Per-cell control after the fact: getTableCell + setTableCellText / setTableCellFill / setTableCellAlignment.
- insertTableRow / removeTableRow / insertTableColumn / removeTableColumn for structural changes.
# Add a chart
addSlideChart writes the chart XML, the drawing rels, and an embedded xlsx so "Edit data" works in PowerPoint.
where: site/src/lib/examples/recipes/add-chart.ts
// Column chart with two series. addSlideChart writes the chart XML, the
// drawing rels, and an embedded xlsx so PowerPoint's "Edit data" works.
import { addSlideChart, inches, type SlideData } from '@office-kit/pptx';
declare const slide: SlideData;
addSlideChart(slide, {
x: inches(0.5),
y: inches(1.5),
w: inches(9),
h: inches(4.5),
spec: {
kind: 'column',
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Revenue', values: [120, 180, 240, 300] },
{ name: 'Cost', values: [80, 90, 130, 160] },
],
title: 'FY26',
},
});
- Chart kinds: bar, column, line, pie, doughnut, area. Each takes the same { categories, series } shape.
- setChartSpec updates an existing chart in place — useful for re-rendering a template chart with new data.
# Insert an image
Format auto-detected from magic bytes. Reuse setShapeImage to swap an existing picture.
where: site/src/lib/examples/recipes/add-image.ts
// Insert an image. setShapeImage / addSlideImage detect the format
// (PNG / JPEG / GIF / SVG / BMP / TIFF) from magic bytes.
import { readFile } from 'node:fs/promises';
import { addSlideImage, inches, type SlideData } from '@office-kit/pptx';
declare const slide: SlideData;
const logo = await readFile('logo.png');
addSlideImage(slide, logo, {
x: inches(8),
y: inches(0.4),
w: inches(1.5),
h: inches(1),
});
# Replace a template image in place
Find a placeholder picture by name and swap its bytes — geometry / crop preserved.
where: site/src/lib/examples/image-replace.ts
// Swap an image in a template, preserving its crop / transform / sizing.
// The format is auto-detected from the new bytes.
import { readFile, writeFile } from 'node:fs/promises';
import {
getShapeKind,
getShapeName,
getSlideShapes,
getSlides,
loadPresentation,
savePresentation,
setShapeImage,
} from '@office-kit/pptx';
const pres = await loadPresentation(await readFile('template.pptx'));
const newLogo = await readFile('new-logo.png');
for (const slide of getSlides(pres)) {
for (const shape of getSlideShapes(slide)) {
if (getShapeKind(shape) === 'picture' && getShapeName(shape) === 'Logo') {
setShapeImage(shape, newLogo);
}
}
}
await writeFile('out.pptx', await savePresentation(pres));
L4: notes, comments, transitions, animations
# Speaker notes + review comments
Notes live on the slide; comments are separate parts with author metadata and an EMU position.
where: site/src/lib/examples/recipes/notes-and-comments.ts
// Add speaker notes and two review comments to a slide.
import { addSlideComment, cm, setSlideNotes, type SlideData } from '@office-kit/pptx';
declare const slide: SlideData;
setSlideNotes(slide, 'Open the comments pane to see the review thread.');
addSlideComment(slide, {
author: { name: 'Reviewer A', initials: 'RA' },
text: 'Tighten the headline.',
position: { x: cm(2), y: cm(2) },
});
addSlideComment(slide, {
author: { name: 'Reviewer B', initials: 'RB' },
text: 'Numbers look strong.',
position: { x: cm(10), y: cm(6) },
});
# Slide transitions + shape animations
Per-slide transition effect plus entrance / exit animations on individual shapes.
where: site/src/lib/examples/recipes/transitions-animations.ts
// Slide transitions + shape entrance / exit animations.
import {
addSlideShape,
inches,
setShapeAnimation,
setShapeFill,
setSlideTransition,
type SlideData,
} from '@office-kit/pptx';
declare const slide: SlideData;
setSlideTransition(slide, { effect: 'fade', speed: 'med' });
const a = addSlideShape(slide, {
preset: 'roundRect',
x: inches(1),
y: inches(2),
w: inches(3),
h: inches(1.5),
text: 'fadeIn',
});
setShapeFill(a, '#2E75B6');
setShapeAnimation(a, { effect: 'fadeIn', durationMs: 700 });
const b = addSlideShape(slide, {
preset: 'roundRect',
x: inches(5),
y: inches(2),
w: inches(3),
h: inches(1.5),
text: 'fadeOut',
});
setShapeFill(b, '#C00000');
setShapeAnimation(b, { effect: 'fadeOut', durationMs: 700 });
- Animation effects: fadeIn, fadeOut, appear, disappear. Deeper timing-tree authoring is post-1.0.
# Hyperlinks + click-to-slide
External URL on a text run, and a click action that jumps to another slide.
where: site/src/lib/examples/recipes/hyperlinks.ts
// External URL hyperlink + an in-deck click action (jump to slide 2).
import {
addSlideTextBox,
getSlides,
inches,
setShapeClickAction,
setShapeHyperlink,
setShapeTextFormat,
type PresentationData,
type SlideData,
} from '@office-kit/pptx';
declare const pres: PresentationData;
declare const slide: SlideData;
const link = addSlideTextBox(slide, {
x: inches(1),
y: inches(2),
w: inches(6),
h: inches(0.6),
text: 'Open the docs',
});
setShapeHyperlink(link, 'https://github.com/office-kit/pptx');
setShapeTextFormat(link, { color: '#0563C1', underline: true });
const nav = addSlideTextBox(slide, {
x: inches(1),
y: inches(3),
w: inches(6),
h: inches(0.6),
text: 'Jump to slide 2',
});
const slide2 = getSlides(pres)[1];
if (slide2) setShapeClickAction(nav, { kind: 'slide', slide: slide2 });
Diagnostics
# Validate the package
validatePresentation returns invariant violations: dangling rels, missing parts, off-spec IDs.
where: site/src/lib/examples/recipes/validate.ts
// Run the invariant checker before saving — useful in CI to catch
// dangling rels / missing parts / off-spec IDs.
import { type PresentationData, validatePresentation } from '@office-kit/pptx';
declare const pres: PresentationData;
const issues: ReturnType<typeof validatePresentation> = validatePresentation(pres);
for (const issue of issues) {
console.error(`[${issue.severity}] ${issue.message}`);
}
if (issues.some((i) => i.severity === 'error')) {
throw new Error(`@office-kit/pptx: ${issues.length} validation issue(s)`);
}
# Inspect raw OPC parts
listPackageParts + readPackagePart let you peek at the underlying XML without dropping to the internal class.
where: site/src/lib/examples/recipes/inspect-parts.ts
// Inspect the raw OPC parts in a package without dropping to the
// internal OpcPackage class.
import { type PresentationData, listPackageParts, readPackagePart } from '@office-kit/pptx';
declare const pres: PresentationData;
for (const part of listPackageParts(pres)) {
console.log(`${part.name} (${part.contentType}, ${part.byteLength} bytes)`);
}
const themeBytes = readPackagePart(pres, '/ppt/theme/theme1.xml');
if (themeBytes) console.log(new TextDecoder().decode(themeBytes));
Browser
# Browser: load via fetch
loadPresentation accepts ArrayBuffer / Uint8Array / Blob — pipe a fetch Response straight in.
where: site/src/lib/examples/browser-fetch.ts
// Browser: fetch a .pptx and load it. loadPresentation accepts any of
// Uint8Array / ArrayBuffer / Blob — `await response.arrayBuffer()` is the
// most browser-portable form.
import { getSlides, getSlideTitle, loadPresentation } from '@office-kit/pptx';
const response = await fetch('/template.pptx');
const pres = await loadPresentation(await response.arrayBuffer());
for (const slide of getSlides(pres)) {
console.log(getSlideTitle(slide));
}
Want to drive a deck end-to-end? Walk the Getting started page, then open the playground to inspect a real file.