PowerPoint Presentation (.pptx)
A .pptx file is a ZIP archive of XML files. You can create, edit, and read them programmatically. Choose the right approach for the task:
| Task | Approach |
|---|---|
| Create a new deck | pptxgenjs (Node) or python-pptx (Python) |
| Edit an existing deck | Unzip → edit slide XML → rezip |
| Read content from a deck | markitdown deck.pptx or unzip and parse XML |
| Convert to PDF | LibreOffice headless |
Presentation Principles
- Every slide has one job. A slide that makes three points makes zero. Split ruthlessly.
- Text-only slides are forgettable. Every slide needs at least one visual element — chart, image, icon, data callout, or diagram.
- The deck must work without the speaker. Async decks (sent via email, read without a presenter) need more text and context than live-presentation decks.
- Visual hierarchy over bullet points. A large number + small label communicates faster than a sentence. A 2x2 grid communicates relationships better than a list.
- Commit to a visual direction and hold it. One font pair. One color palette. One layout style. Inconsistency signals lack of polish.
- AI slop is obvious. Cream backgrounds, accent stripes along card edges, Inter/Roboto with no rationale, blue CTA buttons, centered body text — these are trust killers. Avoid all of them.
Step 0: Before Creating
Ask the user:
- What is the deck for? (investor pitch, all-hands, client presentation, technical review, sales demo)
- Who is the audience? (technical, executive, external client, general team)
- How many slides? Or: what content needs to be covered?
- Brand constraints? (specific colors, fonts, logo to include)
- Output format? (.pptx to edit further, or PDF to send)
- Presenting live or sending async? (changes text density and speaker notes needs)
Creating with pptxgenjs (Node/TypeScript)
Setup
# pptxgenjs is often pre-installed — try require first
node -e "require('pptxgenjs')" 2>/dev/null && echo "installed" || npm install pptxgenjs
Basic Structure
const pptx = require("pptxgenjs");
const pres = new pptx();
// ALWAYS set layout before adding slides
pres.layout = "LAYOUT_16x9"; // 10" × 5.625" — standard widescreen
// Define theme colors (never use # prefix, never 8-digit hex)
const COLORS = {
primary: "1E3A5F", // Deep navy
accent: "2E86AB", // Electric blue
light: "F8F9FA", // Near white
dark: "212529", // Near black
muted: "6C757D", // Gray
};
// Title slide
const titleSlide = pres.addSlide();
titleSlide.background = { color: COLORS.primary };
titleSlide.addText("Your Presentation Title", {
x: 0.5, y: 1.8, w: 9, h: 1.2,
fontSize: 40, bold: true, color: "FFFFFF",
align: "left",
});
titleSlide.addText("Subtitle or date", {
x: 0.5, y: 3.0, w: 9, h: 0.5,
fontSize: 18, color: "A8C6E8",
align: "left",
});
await pres.writeFile({ fileName: "presentation.pptx" });
Common Layout Patterns
Data callout slide (big numbers)
const slide = pres.addSlide();
slide.addText("Key Metrics", {
x: 0.5, y: 0.3, w: 9, h: 0.6,
fontSize: 28, bold: true, color: COLORS.dark
});
// Three metric blocks
const metrics = [
{ value: "810×", label: "Productivity increase" },
{ value: "40+", label: "Features shipped" },
{ value: "3", label: "Products launched" },
];
metrics.forEach((m, i) => {
const x = 0.5 + i * 3.2;
// Big number
slide.addText(m.value, {
x, y: 1.2, w: 2.8, h: 1.4,
fontSize: 60, bold: true, color: COLORS.primary, align: "center"
});
// Label
slide.addText(m.label, {
x, y: 2.7, w: 2.8, h: 0.5,
fontSize: 14, color: COLORS.muted, align: "center"
});
});
Two-column slide
const slide = pres.addSlide();
// Left column — text
slide.addText("Problem", {
x: 0.5, y: 0.3, w: 4.2, h: 0.5,
fontSize: 24, bold: true, color: COLORS.dark
});
slide.addText("The current checkout flow has a 23% drop-off rate on mobile...", {
x: 0.5, y: 1.0, w: 4.2, h: 3.5,
fontSize: 14, color: COLORS.dark, valign: "top"
});
// Right column — image or chart
slide.addImage({ path: "chart.png", x: 5.2, y: 0.5, w: 4.3, h: 4.5 });
Critical gotchas
// ❌ WRONG — # prefix corrupts the file
color: "#FF0000"
// ✅ CORRECT — 6 hex chars, no #
color: "FF0000"
// ❌ WRONG — sharing option objects causes corruption
const shadow = { type: "outer", blur: 3, offset: 2, color: "000000" };
slide1.addShape(pptx.ShapeType.rect, { shadow }); // mutates shadow object
slide2.addShape(pptx.ShapeType.rect, { shadow }); // now uses corrupted values
// ✅ CORRECT — fresh object per call
const makeShadow = () => ({ type: "outer", blur: 3, offset: 2, color: "000000" });
slide1.addShape(pptx.ShapeType.rect, { shadow: makeShadow() });
slide2.addShape(pptx.ShapeType.rect, { shadow: makeShadow() });
// ❌ WRONG — shadow offset must be >= 0
shadow: { offset: -2 } // corrupts file
// ✅ CORRECT
shadow: { offset: 2 }
Creating with python-pptx
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)
# Add a slide using blank layout
blank_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(blank_layout)
# Add title text box
txBox = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(12), Inches(1))
tf = txBox.text_frame
tf.word_wrap = True
p = tf.add_paragraph()
p.text = "Slide Title"
p.font.bold = True
p.font.size = Pt(36)
p.font.color.rgb = RGBColor(0x1E, 0x3A, 0x5F)
p.alignment = PP_ALIGN.LEFT
prs.save("presentation.pptx")
Editing an Existing Deck
# Unzip to inspect/edit
mkdir deck_unpacked && cp deck.pptx deck.zip
unzip deck.zip -d deck_unpacked/
# Slides are at: deck_unpacked/ppt/slides/slide1.xml, slide2.xml, etc.
# Edit XML directly for text replacement, color changes, etc.
# Repack
cd deck_unpacked && zip -r ../deck_edited.pptx . && cd ..
For simple text replacement across all slides:
from pptx import Presentation
prs = Presentation("template.pptx")
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
for para in shape.text_frame.paragraphs:
for run in para.runs:
run.text = run.text.replace("{{COMPANY}}", "Acme Corp")
prs.save("output.pptx")
Reading / Extracting Content
# Extract all text, slide by slide
markitdown deck.pptx
# Check for placeholder text (leftover template text)
markitdown deck.pptx | grep -iE "\bx{3,}\b|lorem|ipsum|\bTODO|\[insert"
from pptx import Presentation
prs = Presentation("deck.pptx")
for i, slide in enumerate(prs.slides, 1):
print(f"\n--- Slide {i} ---")
for shape in slide.shapes:
if shape.has_text_frame:
print(shape.text_frame.text)
Design Standards
Color palettes (use one, stay consistent)
| Palette | Primary | Accent | Background |
|---|---|---|---|
| Midnight Executive | 1E2761 |
CADCFC |
FFFFFF |
| Forest & Moss | 2C5F2D |
97BC62 |
F5F5F5 |
| Coral Energy | F96167 |
F9E795 |
2F3C7E |
| Charcoal Minimal | 36454F |
F2F2F2 |
212121 |
| Teal Trust | 028090 |
00A896 |
FFFFFF |
Typography
| Element | Size | Weight |
|---|---|---|
| Slide title | 36–44pt | Bold |
| Section header | 20–24pt | Bold |
| Body text | 14–16pt | Regular |
| Captions / labels | 10–12pt | Regular |
Safe fonts (render correctly in both PowerPoint and LibreOffice): Arial, Calibri, Cambria, Times New Roman, Courier New
What to never do
- ❌ Accent lines or stripes under titles (AI slop signature)
- ❌ Decorative color bars spanning slide width
- ❌ Centered body text (left-align everything except titles)
- ❌ Cream/beige background when not specified — use white
- ❌ Text-only slides — always add a visual element
- ❌ Same layout repeated on every slide
- ❌
#prefix in color values (pptxgenjs) - ❌ Shared option objects across add calls (pptxgenjs)
QA Before Delivering
# 1. Content check — read all text
markitdown output.pptx
# 2. Check for leftover placeholder text
markitdown output.pptx | grep -iE "\bx{3,}\b|lorem|ipsum|TODO|\[insert"
# 3. Convert to PDF for visual check
libreoffice --headless --convert-to pdf output.pptx
# 4. Visual inspection checklist:
Visual QA checklist (check every slide):
- No text overflowing its box or the slide edge
- No overlapping elements
- Minimum 0.5" margin from all slide edges
- Consistent gaps between content blocks (0.3" or 0.5" — pick one)
- No low-contrast text (light on light, dark on dark)
- No leftover template placeholder text
- Speaker notes added for any slide that needs explanation
Definition of Done — PPTX
- Deck purpose, audience, and slide count confirmed before starting
- Layout set before first slide added (
pres.layout = "LAYOUT_16x9") - Color palette defined — no
#prefix, no 8-digit hex - One font pair used consistently throughout
- Every slide has at least one visual element (no text-only slides)
- No accent stripes, decorative bars, or cream backgrounds
- No AI slop patterns (see list above)
- Content QA run:
markitdown output.pptx - Placeholder text check: no leftover
lorem,TODO,[insert] - Visual QA: every slide inspected for overflow, overlap, contrast
- Speaker notes added where needed
- File opens without errors in PowerPoint / LibreOffice