13 KiB
PptxGenJS Tutorial
Setup & Basic Structure
const pptxgen = require('pptxgenjs');
let pres = new pptxgen();
pres.layout = 'LAYOUT_16x9'; // or 'LAYOUT_16x10', 'LAYOUT_4x3', 'LAYOUT_WIDE'
pres.author = 'Your Name';
pres.title = 'Presentation Title';
let slide = pres.addSlide();
slide.addText('Hello World!', { x: 0.5, y: 0.5, fontSize: 36, color: '363636' });
pres.writeFile({ fileName: 'Presentation.pptx' });
Layout Dimensions
Slide dimensions (coordinates in inches):
LAYOUT_16x9: 10" × 5.625" (default)LAYOUT_16x10: 10" × 6.25"LAYOUT_4x3: 10" × 7.5"LAYOUT_WIDE: 13.3" × 7.5"
Text & Formatting
// Basic text
slide.addText('Simple Text', {
x: 1,
y: 1,
w: 8,
h: 2,
fontSize: 24,
fontFace: 'Arial',
color: '363636',
bold: true,
align: 'center',
valign: 'middle',
});
// Character spacing (use charSpacing, not letterSpacing which is silently ignored)
slide.addText('SPACED TEXT', { x: 1, y: 1, w: 8, h: 1, charSpacing: 6 });
// Rich text arrays
slide.addText(
[
{ text: 'Bold ', options: { bold: true } },
{ text: 'Italic ', options: { italic: true } },
],
{ x: 1, y: 3, w: 8, h: 1 },
);
// Multi-line text (requires breakLine: true)
slide.addText(
[
{ text: 'Line 1', options: { breakLine: true } },
{ text: 'Line 2', options: { breakLine: true } },
{ text: 'Line 3' }, // Last item doesn't need breakLine
],
{ x: 0.5, y: 0.5, w: 8, h: 2 },
);
// Text box margin (internal padding)
slide.addText('Title', {
x: 0.5,
y: 0.3,
w: 9,
h: 0.6,
margin: 0, // Use 0 when aligning text with other elements like shapes or icons
});
Tip: Text boxes have internal margin by default. Set margin: 0 when you need text to align precisely with shapes, lines, or icons at the same x-position.
Lists & Bullets
// ✅ CORRECT: Multiple bullets
slide.addText([
{ text: "First item", options: { bullet: true, breakLine: true } },
{ text: "Second item", options: { bullet: true, breakLine: true } },
{ text: "Third item", options: { bullet: true } }
], { x: 0.5, y: 0.5, w: 8, h: 3 });
// ❌ WRONG: Never use unicode bullets
slide.addText("• First item", { ... }); // Creates double bullets
// Sub-items and numbered lists
{ text: "Sub-item", options: { bullet: true, indentLevel: 1 } }
{ text: "First", options: { bullet: { type: "number" }, breakLine: true } }
Shapes
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.5,
y: 0.8,
w: 1.5,
h: 3.0,
fill: { color: 'FF0000' },
line: { color: '000000', width: 2 },
});
slide.addShape(pres.shapes.OVAL, { x: 4, y: 1, w: 2, h: 2, fill: { color: '0000FF' } });
slide.addShape(pres.shapes.LINE, {
x: 1,
y: 3,
w: 5,
h: 0,
line: { color: 'FF0000', width: 3, dashType: 'dash' },
});
// With transparency
slide.addShape(pres.shapes.RECTANGLE, {
x: 1,
y: 1,
w: 3,
h: 2,
fill: { color: '0088CC', transparency: 50 },
});
// Rounded rectangle (rectRadius only works with ROUNDED_RECTANGLE, not RECTANGLE)
// ⚠️ Don't pair with rectangular accent overlays — they won't cover rounded corners. Use RECTANGLE instead.
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
x: 1,
y: 1,
w: 3,
h: 2,
fill: { color: 'FFFFFF' },
rectRadius: 0.1,
});
// With shadow
slide.addShape(pres.shapes.RECTANGLE, {
x: 1,
y: 1,
w: 3,
h: 2,
fill: { color: 'FFFFFF' },
shadow: { type: 'outer', color: '000000', blur: 6, offset: 2, angle: 135, opacity: 0.15 },
});
Shadow options:
| Property | Type | Range | Notes |
|---|---|---|---|
type |
string | "outer", "inner" |
|
color |
string | 6-char hex (e.g. "000000") |
No # prefix, no 8-char hex — see Common Pitfalls |
blur |
number | 0-100 pt | |
offset |
number | 0-200 pt | Must be non-negative — negative values corrupt the file |
angle |
number | 0-359 degrees | Direction the shadow falls (135 = bottom-right, 270 = upward) |
opacity |
number | 0.0-1.0 | Use this for transparency, never encode in color string |
To cast a shadow upward (e.g. on a footer bar), use angle: 270 with a positive offset — do not use a negative offset.
Note: Gradient fills are not natively supported. Use a gradient image as a background instead.
Images
Image Sources
// From file path
slide.addImage({ path: 'images/chart.png', x: 1, y: 1, w: 5, h: 3 });
// From URL
slide.addImage({ path: 'https://example.com/image.jpg', x: 1, y: 1, w: 5, h: 3 });
// From base64 (faster, no file I/O)
slide.addImage({ data: 'image/png;base64,iVBORw0KGgo...', x: 1, y: 1, w: 5, h: 3 });
Image Options
slide.addImage({
path: 'image.png',
x: 1,
y: 1,
w: 5,
h: 3,
rotate: 45, // 0-359 degrees
rounding: true, // Circular crop
transparency: 50, // 0-100
flipH: true, // Horizontal flip
flipV: false, // Vertical flip
altText: 'Description', // Accessibility
hyperlink: { url: 'https://example.com' },
});
Image Sizing Modes
// Contain - fit inside, preserve ratio
{ sizing: { type: 'contain', w: 4, h: 3 } }
// Cover - fill area, preserve ratio (may crop)
{ sizing: { type: 'cover', w: 4, h: 3 } }
// Crop - cut specific portion
{ sizing: { type: 'crop', x: 0.5, y: 0.5, w: 2, h: 2 } }
Calculate Dimensions (preserve aspect ratio)
const origWidth = 1978,
origHeight = 923,
maxHeight = 3.0;
const calcWidth = maxHeight * (origWidth / origHeight);
const centerX = (10 - calcWidth) / 2;
slide.addImage({ path: 'image.png', x: centerX, y: 1.2, w: calcWidth, h: maxHeight });
Supported Formats
- Standard: PNG, JPG, GIF (animated GIFs work in Microsoft 365)
- SVG: Works in modern PowerPoint/Microsoft 365
Icons
Use react-icons to generate SVG icons, then rasterize to PNG for universal compatibility.
Setup
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const sharp = require('sharp');
const { FaCheckCircle, FaChartLine } = require('react-icons/fa');
function renderIconSvg(IconComponent, color = '#000000', size = 256) {
return ReactDOMServer.renderToStaticMarkup(
React.createElement(IconComponent, { color, size: String(size) }),
);
}
async function iconToBase64Png(IconComponent, color, size = 256) {
const svg = renderIconSvg(IconComponent, color, size);
const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer();
return 'image/png;base64,' + pngBuffer.toString('base64');
}
Add Icon to Slide
const iconData = await iconToBase64Png(FaCheckCircle, '#4472C4', 256);
slide.addImage({
data: iconData,
x: 1,
y: 1,
w: 0.5,
h: 0.5, // Size in inches
});
Note: Use size 256 or higher for crisp icons. The size parameter controls the rasterization resolution, not the display size on the slide (which is set by w and h in inches).
Icon Libraries
Install: npm install -g react-icons react react-dom sharp
Popular icon sets in react-icons:
react-icons/fa- Font Awesomereact-icons/md- Material Designreact-icons/hi- Heroiconsreact-icons/bi- Bootstrap Icons
Slide Backgrounds
// Solid color
slide.background = { color: 'F1F1F1' };
// Color with transparency
slide.background = { color: 'FF3399', transparency: 50 };
// Image from URL
slide.background = { path: 'https://example.com/bg.jpg' };
// Image from base64
slide.background = { data: 'image/png;base64,iVBORw0KGgo...' };
Tables
slide.addTable(
[
['Header 1', 'Header 2'],
['Cell 1', 'Cell 2'],
],
{
x: 1,
y: 1,
w: 8,
h: 2,
border: { pt: 1, color: '999999' },
fill: { color: 'F1F1F1' },
},
);
// Advanced with merged cells
let tableData = [
[{ text: 'Header', options: { fill: { color: '6699CC' }, color: 'FFFFFF', bold: true } }, 'Cell'],
[{ text: 'Merged', options: { colspan: 2 } }],
];
slide.addTable(tableData, { x: 1, y: 3.5, w: 8, colW: [4, 4] });
Charts
// Bar chart
slide.addChart(
pres.charts.BAR,
[
{
name: 'Sales',
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [4500, 5500, 6200, 7100],
},
],
{
x: 0.5,
y: 0.6,
w: 6,
h: 3,
barDir: 'col',
showTitle: true,
title: 'Quarterly Sales',
},
);
// Line chart
slide.addChart(
pres.charts.LINE,
[
{
name: 'Temp',
labels: ['Jan', 'Feb', 'Mar'],
values: [32, 35, 42],
},
],
{ x: 0.5, y: 4, w: 6, h: 3, lineSize: 3, lineSmooth: true },
);
// Pie chart
slide.addChart(
pres.charts.PIE,
[
{
name: 'Share',
labels: ['A', 'B', 'Other'],
values: [35, 45, 20],
},
],
{ x: 7, y: 1, w: 5, h: 4, showPercent: true },
);
Better-Looking Charts
Default charts look dated. Apply these options for a modern, clean appearance:
slide.addChart(pres.charts.BAR, chartData, {
x: 0.5,
y: 1,
w: 9,
h: 4,
barDir: 'col',
// Custom colors (match your presentation palette)
chartColors: ['0D9488', '14B8A6', '5EEAD4'],
// Clean background
chartArea: { fill: { color: 'FFFFFF' }, roundedCorners: true },
// Muted axis labels
catAxisLabelColor: '64748B',
valAxisLabelColor: '64748B',
// Subtle grid (value axis only)
valGridLine: { color: 'E2E8F0', size: 0.5 },
catGridLine: { style: 'none' },
// Data labels on bars
showValue: true,
dataLabelPosition: 'outEnd',
dataLabelColor: '1E293B',
// Hide legend for single series
showLegend: false,
});
Key styling options:
chartColors: [...]- hex colors for series/segmentschartArea: { fill, border, roundedCorners }- chart backgroundcatGridLine/valGridLine: { color, style, size }- grid lines (style: "none"to hide)lineSmooth: true- curved lines (line charts)legendPos: "r"- legend position: "b", "t", "l", "r", "tr"
Slide Masters
pres.defineSlideMaster({
title: 'TITLE_SLIDE',
background: { color: '283A5E' },
objects: [
{
placeholder: { options: { name: 'title', type: 'title', x: 1, y: 2, w: 8, h: 2 } },
},
],
});
let titleSlide = pres.addSlide({ masterName: 'TITLE_SLIDE' });
titleSlide.addText('My Title', { placeholder: 'title' });
Common Pitfalls
⚠️ These issues cause file corruption, visual bugs, or broken output. Avoid them.
-
NEVER use "#" with hex colors - causes file corruption
color: 'FF0000'; // ✅ CORRECT color: '#FF0000'; // ❌ WRONG -
NEVER encode opacity in hex color strings - 8-char colors (e.g.,
"00000020") corrupt the file. Use theopacityproperty instead.shadow: { type: "outer", blur: 6, offset: 2, color: "00000020" } // ❌ CORRUPTS FILE shadow: { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.12 } // ✅ CORRECT -
Use
bullet: true- NEVER unicode symbols like "•" (creates double bullets) -
Use
breakLine: truebetween array items or text runs together -
Avoid
lineSpacingwith bullets - causes excessive gaps; useparaSpaceAfterinstead -
Each presentation needs fresh instance - don't reuse
pptxgen()objects -
NEVER reuse option objects across calls - PptxGenJS mutates objects in-place (e.g. converting shadow values to EMU). Sharing one object between multiple calls corrupts the second shape.
const shadow = { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 }; slide.addShape(pres.shapes.RECTANGLE, { shadow, ... }); // ❌ second call gets already-converted values slide.addShape(pres.shapes.RECTANGLE, { shadow, ... }); const makeShadow = () => ({ type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 }); slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); // ✅ fresh object each time slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); -
Don't use
ROUNDED_RECTANGLEwith accent borders - rectangular overlay bars won't cover rounded corners. UseRECTANGLEinstead.// ❌ WRONG: Accent bar doesn't cover rounded corners slide.addShape(pres.shapes.ROUNDED_RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: 'FFFFFF' }, }); slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: '0891B2' }, }); // ✅ CORRECT: Use RECTANGLE for clean alignment slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: 'FFFFFF' } }); slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: '0891B2' }, });
Quick Reference
- Shapes: RECTANGLE, OVAL, LINE, ROUNDED_RECTANGLE
- Charts: BAR, LINE, PIE, DOUGHNUT, SCATTER, BUBBLE, RADAR
- Layouts: LAYOUT_16x9 (10"×5.625"), LAYOUT_16x10, LAYOUT_4x3, LAYOUT_WIDE
- Alignment: "left", "center", "right"
- Chart data labels: "outEnd", "inEnd", "center"