
Document Pptx
- 237 installs
- 73 repo stars
- Updated July 13, 2026
- vasilyu1983/ai-agents-public
Helps with documentation tasks.
About
document-pptx is a Claude Code skill for documentation. It helps solo builders move faster with AI-assisted development.
- document-pptx
- Documentation
- AI-coding skill
Document Pptx by the numbers
- 237 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #487 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vasilyu1983/ai-agents-public --skill document-pptxAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 237 |
|---|---|
| repo stars | ★ 73 |
| Last updated | July 13, 2026 |
| Repository | vasilyu1983/ai-agents-public ↗ |
What it does
Helps with documentation tasks.
Files
Document PPTX Skill - Quick Reference
This skill enables creation and editing of PowerPoint presentations programmatically. Claude should apply these patterns when users need to generate pitch decks, reports, training materials, or automate presentation workflows.
Modern Best Practices (Jan 2026):
- One slide = one takeaway; design the deck around a decision or audience goal.
- Cite numbers (definition + timeframe + source) and keep a single source of truth for charts.
- Accessibility: slide titles, reading order, contrast, and meaningful alt text; follow your org's standard (often WCAG 2.2 AA / EN 301 549).
- Version decks and enforce review loops (avoid "final_final_v7.pptx").
---
Quick Reference
| Task | Tool/Library | Language | When to Use |
|---|---|---|---|
| Create PPTX | python-pptx | Python | Presentations, slide decks |
| Create PPTX | PptxGenJS | Node.js | Server-side generation |
| Template-driven | PPTX-Automizer | Node.js | Corporate branding, template injection |
| Templates | python-pptx | Python | Master slides, themes |
| Charts | python-pptx | Python | Data visualizations |
| Extract content | python-pptx | Python | Parse existing decks |
Selection guide
- Prefer PPTX-Automizer when you have a branded .pptx template and need to "inject data into slides".
- Prefer python-pptx in Python-heavy pipelines (reporting, notebooks, ETL).
- Prefer PptxGenJS in Node.js pipelines (server-side generation, web apps).
---
Core Operations
Create Presentation (Python)
from pptx import Presentation
prs = Presentation()
# Title slide
title_layout = prs.slide_layouts[0] # Title Slide layout
slide = prs.slides.add_slide(title_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = "Q4 2025 Business Review"
subtitle.text = "Presented by Product Team"
# Content slide with bullets
bullet_layout = prs.slide_layouts[1] # Title and Content
slide = prs.slides.add_slide(bullet_layout)
slide.shapes.title.text = "Key Highlights"
body = slide.placeholders[1]
tf = body.text_frame
tf.text = "Revenue grew 25% YoY"
p = tf.add_paragraph()
p.text = "Customer base expanded to 10,000+"
p.level = 0
p = tf.add_paragraph()
p.text = "New enterprise tier launched"
p.level = 1 # Indented bullet
# Add speaker notes
notes_slide = slide.notes_slide
notes_slide.notes_text_frame.text = "Emphasize the enterprise growth story here."
prs.save('presentation.pptx')Create Presentation (Node.js)
import pptxgen from 'pptxgenjs';
async function main() {
const pptx = new pptxgen();
pptx.author = 'Product Team';
pptx.title = 'Q4 Business Review';
// Title slide
let slide = pptx.addSlide();
slide.addText('Q4 2025 Business Review', {
x: 1, y: 2, w: '80%',
fontSize: 36, bold: true, color: '363636',
align: 'center',
});
slide.addText('Presented by Product Team', {
x: 1, y: 3.5, w: '80%',
fontSize: 18, color: '666666',
align: 'center',
});
// Content slide with bullets
slide = pptx.addSlide();
slide.addText('Key Highlights', {
x: 0.5, y: 0.5, w: '90%',
fontSize: 28, bold: true,
});
slide.addText([
{ text: 'Revenue grew 25% YoY', options: { bullet: true } },
{ text: 'Customer base expanded to 10,000+', options: { bullet: true } },
{ text: 'New enterprise tier launched', options: { bullet: true, indentLevel: 1 } },
], { x: 0.5, y: 1.5, w: '90%', fontSize: 18 });
// Add chart
slide = pptx.addSlide();
slide.addChart(pptx.ChartType.bar, [
{ name: 'Sales', labels: ['Q1', 'Q2', 'Q3', 'Q4'], values: [100, 150, 180, 225] },
], { x: 1, y: 1.5, w: 8, h: 4 });
await pptx.writeFile({ fileName: 'presentation.pptx' });
}
main();Add Charts (Python)
from pptx import Presentation
from pptx.util import Inches
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
# Chart data
chart_data = CategoryChartData()
chart_data.categories = ['Q1', 'Q2', 'Q3', 'Q4']
chart_data.add_series('Revenue', (100, 150, 180, 225))
chart_data.add_series('Expenses', (80, 90, 100, 110))
# Add chart
x, y, cx, cy = Inches(1), Inches(1.5), Inches(8), Inches(5)
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
x, y, cx, cy,
chart_data
).chart
chart.has_legend = True
chart.legend.include_in_layout = False
prs.save('charts.pptx')Add Images and Tables
from pptx import Presentation
from pptx.util import Inches
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
# Add image
slide.shapes.add_picture('logo.png', Inches(0.5), Inches(0.5), width=Inches(2))
# Add table
rows, cols = 4, 3
table = slide.shapes.add_table(rows, cols, Inches(1), Inches(2), Inches(8), Inches(3)).table
# Set column headers
table.cell(0, 0).text = 'Product'
table.cell(0, 1).text = 'Sales'
table.cell(0, 2).text = 'Growth'
# Fill data
data = [
('Widget A', '$1.2M', '+25%'),
('Widget B', '$800K', '+15%'),
('Widget C', '$500K', '+40%'),
]
for row_idx, (product, sales, growth) in enumerate(data, 1):
table.cell(row_idx, 0).text = product
table.cell(row_idx, 1).text = sales
table.cell(row_idx, 2).text = growth
prs.save('images_and_tables.pptx')Extract Content
from pptx import Presentation
prs = Presentation('existing.pptx')
for slide_num, slide in enumerate(prs.slides, 1):
print(f"\n--- Slide {slide_num} ---")
for shape in slide.shapes:
if shape.has_text_frame:
for paragraph in shape.text_frame.paragraphs:
print(paragraph.text)
if shape.has_table:
table = shape.table
for row in table.rows:
row_text = [cell.text for cell in row.cells]
print(row_text)---
Slide Layout Reference
| Layout Index | Name | Use Case |
|---|---|---|
| 0 | Title Slide | Opening, section dividers |
| 1 | Title and Content | Standard bullet slides |
| 2 | Section Header | Section transitions |
| 3 | Two Content | Side-by-side comparison |
| 4 | Comparison | Pros/cons, before/after |
| 5 | Title Only | Custom content placement |
| 6 | Blank | Full creative control |
| 7 | Content with Caption | Image + description |
---
Presentation Structure Patterns
Pitch Deck (10 slides)
PITCH DECK STRUCTURE
1. Title (company, tagline)
2. Problem (pain point)
3. Solution (your product)
4. Market Size (TAM/SAM/SOM)
5. Business Model (how you make money)
6. Traction (metrics, growth)
7. Team (founders, advisors)
8. Competition (landscape)
9. Financials (projections)
10. Ask (funding, next steps)Quarterly Review (8 slides)
QUARTERLY REVIEW STRUCTURE
1. Title + Agenda
2. Executive Summary (KPIs dashboard)
3. Revenue & Growth
4. Product Updates
5. Customer Highlights
6. Challenges & Learnings
7. Next Quarter Goals
8. Q&A---
Do / Avoid (Dec 2025)
Do
- Use a slide narrative plan (title + 1-sentence takeaway + supporting visual).
- Put the executive summary up front for decision decks.
- Keep speaker notes aligned with slide takeaways.
Avoid
- Dense slides with multiple messages.
- Uncited numbers or charts without definitions.
- Pixelated screenshots and unreadable tables.
What Good Looks Like
- Narrative: each slide has a 1-sentence takeaway and supports a single decision or insight.
- Structure: opening executive summary + clear arc (problem -> insight -> recommendation -> next steps).
- Data hygiene: charts show units, timeframes, sources, and consistent axes.
- Design: consistent typography, spacing, and contrast; no "wall of text" slides.
- Accessibility: reading order set and meaningful alt text where needed.
Optional: AI / Automation
Use only when explicitly requested and policy-compliant.
- Draft slide headlines and speaker notes; humans verify accuracy and tone.
- Generate chart code from data; humans verify labels, units, and sources.
Navigation
Resources
- references/pptx-layouts.md - Master slides, themes, templates
- references/pptx-charts.md - Chart types, data visualization
- references/pptx-animations-transitions.md - Slide transitions, build animations, timing
- references/pptx-speaker-notes-delivery.md - Speaker notes, presenter mode, delivery prep
- references/pptx-template-branding.md - Corporate templates, multi-brand support
- data/sources.json - Library documentation links
Templates
- assets/pitch-deck.md - Startup pitch structure
- assets/quarterly-review.md - Business review template
- assets/slide-narrative-template.md - 1-sentence takeaway per slide
Related Skills
- ../document-pdf/SKILL.md - Export presentations to PDF
- ../document-xlsx/SKILL.md - Data source for charts
- ../product-management/SKILL.md - Product strategy decks
Fact-Checking
- Use web search/web fetch to verify current external facts, versions, pricing, deadlines, regulations, or platform behavior before final answers.
- Prefer primary sources; report source links and dates for volatile information.
- If web access is unavailable, state the limitation and mark guidance as unverified.
Pitch Deck Template — 10-Slide Startup Presentation
Complete, copy-paste Python code for generating a startup pitch deck.
---
Quick Start
pip install python-pptxpython pitch_deck.py
# Output: pitch_deck.pptx---
Full Template Code
"""
Pitch Deck Generator
Creates a 10-slide startup pitch deck following YC format.
"""
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.chart import XL_CHART_TYPE
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RgbColor
# ============================================================
# CONFIGURATION - Edit these values
# ============================================================
COMPANY_NAME = "TechCorp"
TAGLINE = "AI-powered solutions for enterprise"
FOUNDER_NAMES = "Jane Doe & John Smith"
CONTACT_EMAIL = "founders@techcorp.com"
# Brand colors
PRIMARY_COLOR = RgbColor(0x00, 0x66, 0xCC)
SECONDARY_COLOR = RgbColor(0x00, 0x99, 0xFF)
DARK_COLOR = RgbColor(0x33, 0x33, 0x33)
LIGHT_COLOR = RgbColor(0xF5, 0xF5, 0xF5)
# ============================================================
# HELPER FUNCTIONS
# ============================================================
def add_title_slide(prs, title: str, subtitle: str):
"""Slide 1: Title"""
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(2), Inches(9), Inches(1))
tf = title_box.text_frame
tf.paragraphs[0].text = title
tf.paragraphs[0].font.size = Pt(44)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = DARK_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Subtitle
sub_box = slide.shapes.add_textbox(Inches(0.5), Inches(3.2), Inches(9), Inches(0.5))
tf = sub_box.text_frame
tf.paragraphs[0].text = subtitle
tf.paragraphs[0].font.size = Pt(24)
tf.paragraphs[0].font.color.rgb = PRIMARY_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
def add_content_slide(prs, title: str, bullets: list):
"""Standard content slide with bullets"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = title
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = DARK_COLOR
# Bullets
content_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.3), Inches(9), Inches(4.5))
tf = content_box.text_frame
tf.word_wrap = True
for i, bullet in enumerate(bullets):
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.text = f"• {bullet}"
p.font.size = Pt(20)
p.font.color.rgb = DARK_COLOR
p.space_before = Pt(12)
return slide
def add_metric_slide(prs, title: str, metrics: list):
"""Slide with 3-4 big metrics"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = title
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
# Metrics in row
num_metrics = len(metrics)
box_width = 9 / num_metrics
for i, (value, label) in enumerate(metrics):
x = Inches(0.5 + i * box_width)
# Value
val_box = slide.shapes.add_textbox(x, Inches(2), Inches(box_width - 0.2), Inches(1))
tf = val_box.text_frame
tf.paragraphs[0].text = value
tf.paragraphs[0].font.size = Pt(48)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = PRIMARY_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Label
lbl_box = slide.shapes.add_textbox(x, Inches(3), Inches(box_width - 0.2), Inches(0.5))
tf = lbl_box.text_frame
tf.paragraphs[0].text = label
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.color.rgb = DARK_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
# ============================================================
# BUILD PRESENTATION
# ============================================================
def create_pitch_deck():
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(5.625) # 16:9
# Slide 1: Title
add_title_slide(prs, COMPANY_NAME, TAGLINE)
# Slide 2: Problem
add_content_slide(prs, "The Problem", [
"Enterprises waste 40% of time on manual data processing",
"Current solutions are expensive and require technical expertise",
"No single platform integrates with existing workflows",
"Compliance and security concerns block adoption"
])
# Slide 3: Solution
add_content_slide(prs, "Our Solution", [
"AI-powered automation platform for enterprise data",
"No-code interface accessible to business users",
"Seamless integration with 100+ enterprise systems",
"SOC2 and GDPR compliant from day one"
])
# Slide 4: Market Size
add_metric_slide(prs, "Market Opportunity", [
("$50B", "TAM"),
("$12B", "SAM"),
("$2B", "SOM"),
])
# Slide 5: Business Model
add_content_slide(prs, "Business Model", [
"SaaS subscription: $500-5,000/month per enterprise",
"Usage-based pricing for API calls",
"Professional services for custom integrations",
"90% gross margin at scale"
])
# Slide 6: Traction
add_metric_slide(prs, "Traction", [
("$1.2M", "ARR"),
("150%", "YoY Growth"),
("45", "Enterprise Clients"),
("95%", "Retention"),
])
# Slide 7: Team (simple version)
add_content_slide(prs, "Team", [
"Jane Doe, CEO - 10 years enterprise SaaS (ex-Salesforce)",
"John Smith, CTO - ML PhD, ex-Google AI",
"Sarah Lee, VP Sales - Built $50M ARR at Datadog",
"15 engineers from FAANG companies"
])
# Slide 8: Competition
add_content_slide(prs, "Competitive Landscape", [
"Legacy vendors (SAP, Oracle): Expensive, slow, complex",
"Point solutions (Zapier, Workato): Limited AI, no enterprise features",
"Our advantage: AI-native + enterprise-grade + simple UX",
"First mover in AI-powered enterprise automation"
])
# Slide 9: Financials (chart)
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Financial Projections"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
chart_data = CategoryChartData()
chart_data.categories = ['2024', '2025', '2026', '2027', '2028']
chart_data.add_series('ARR ($M)', (1.2, 4, 12, 30, 75))
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(1), Inches(1.3), Inches(8), Inches(4),
chart_data
).chart
chart.has_legend = False
# Slide 10: Ask
slide = prs.slides.add_slide(prs.slide_layouts[6])
ask_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.5), Inches(9), Inches(1))
tf = ask_box.text_frame
tf.paragraphs[0].text = "Raising $5M Series A"
tf.paragraphs[0].font.size = Pt(40)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = PRIMARY_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
use_box = slide.shapes.add_textbox(Inches(0.5), Inches(2.8), Inches(9), Inches(1.5))
tf = use_box.text_frame
tf.paragraphs[0].text = "Use of Funds:"
tf.paragraphs[0].font.size = Pt(20)
tf.paragraphs[0].font.bold = True
for item in ["50% Engineering & Product", "30% Sales & Marketing", "20% Operations"]:
p = tf.add_paragraph()
p.text = f"• {item}"
p.font.size = Pt(18)
contact_box = slide.shapes.add_textbox(Inches(0.5), Inches(4.5), Inches(9), Inches(0.5))
tf = contact_box.text_frame
tf.paragraphs[0].text = f"{FOUNDER_NAMES} | {CONTACT_EMAIL}"
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.color.rgb = DARK_COLOR
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Save
prs.save('pitch_deck.pptx')
print("Created: pitch_deck.pptx")
if __name__ == "__main__":
create_pitch_deck()---
Slide Structure Reference
| # | Slide | Purpose | Key Elements |
|---|---|---|---|
| 1 | Title | First impression | Company name, tagline |
| 2 | Problem | Pain point | 3-4 bullets, quantified impact |
| 3 | Solution | Your product | Features tied to problems |
| 4 | Market | Opportunity size | TAM/SAM/SOM metrics |
| 5 | Business Model | Revenue strategy | Pricing, margins |
| 6 | Traction | Proof | ARR, growth, customers |
| 7 | Team | Credibility | Relevant experience |
| 8 | Competition | Positioning | Differentiation matrix |
| 9 | Financials | Projections | 5-year chart |
| 10 | Ask | Call to action | Amount, use of funds |
---
Customization Tips
Add Logo
slide.shapes.add_picture('logo.png', Inches(0.3), Inches(0.2), width=Inches(1.5))Add Footer to All Slides
for slide in prs.slides:
footer = slide.shapes.add_textbox(Inches(0.3), Inches(5.3), Inches(9.4), Inches(0.3))
tf = footer.text_frame
tf.paragraphs[0].text = "Confidential | TechCorp 2025"
tf.paragraphs[0].font.size = Pt(10)
tf.paragraphs[0].font.color.rgb = RgbColor(0x99, 0x99, 0x99)Export to PDF
# Requires LibreOffice or unoconv
import subprocess
subprocess.run(['unoconv', '-f', 'pdf', 'pitch_deck.pptx'])---
Related Resources
- ../references/pptx-layouts.md — Custom themes
- ../references/pptx-charts.md — Financial charts
- quarterly-review.md — Business review template
Quarterly Review Template — 8-Slide Business Presentation
Complete, copy-paste Python code for generating a quarterly business review deck.
---
Quick Start
pip install python-pptx pandaspython quarterly_review.py
# Output: q4_2025_review.pptx---
Full Template Code
"""
Quarterly Business Review Generator
Creates an 8-slide QBR deck with dynamic data.
"""
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.enum.chart import XL_CHART_TYPE
from pptx.chart.data import CategoryChartData
from pptx.dml.color import RgbColor
import pandas as pd
from datetime import datetime
# ============================================================
# CONFIGURATION
# ============================================================
QUARTER = "Q4"
YEAR = "2025"
COMPANY_NAME = "TechCorp"
PRESENTER = "Product Team"
# Brand colors
PRIMARY = RgbColor(0x00, 0x66, 0xCC)
SECONDARY = RgbColor(0x00, 0x99, 0xFF)
SUCCESS = RgbColor(0x28, 0xA7, 0x45)
WARNING = RgbColor(0xFF, 0xC1, 0x07)
DANGER = RgbColor(0xDC, 0x35, 0x45)
DARK = RgbColor(0x33, 0x33, 0x33)
# ============================================================
# SAMPLE DATA (Replace with real data)
# ============================================================
kpis = {
'revenue': {'value': '$4.2M', 'change': '+25%', 'status': 'success'},
'customers': {'value': '1,250', 'change': '+180', 'status': 'success'},
'nps': {'value': '72', 'change': '+5', 'status': 'success'},
'churn': {'value': '2.1%', 'change': '-0.3%', 'status': 'success'},
}
monthly_revenue = pd.DataFrame({
'Month': ['Oct', 'Nov', 'Dec'],
'Revenue': [1.3, 1.4, 1.5],
'Target': [1.2, 1.3, 1.4]
})
product_updates = [
"Launched AI-powered analytics dashboard",
"Reduced API latency by 40%",
"Added SSO support for enterprise clients",
"Released mobile app v2.0",
]
customer_wins = [
("Acme Corp", "$500K ARR", "Enterprise"),
("GlobalTech", "$250K ARR", "Mid-Market"),
("StartupXYZ", "$50K ARR", "Growth"),
]
challenges = [
"Enterprise sales cycle longer than expected (avg 6 months)",
"Technical debt slowing feature velocity",
"Hiring for senior engineering roles",
]
next_quarter_goals = [
"Launch v3.0 with workflow automation",
"Achieve $5M ARR milestone",
"Expand sales team by 5 reps",
"Complete SOC2 Type II certification",
]
# ============================================================
# HELPER FUNCTIONS
# ============================================================
def add_title_slide(prs):
"""Slide 1: Title + Agenda"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.5), Inches(9), Inches(1))
tf = title_box.text_frame
tf.paragraphs[0].text = f"{QUARTER} {YEAR} Business Review"
tf.paragraphs[0].font.size = Pt(40)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = DARK
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Presenter
sub_box = slide.shapes.add_textbox(Inches(0.5), Inches(2.8), Inches(9), Inches(0.5))
tf = sub_box.text_frame
tf.paragraphs[0].text = f"Presented by {PRESENTER}"
tf.paragraphs[0].font.size = Pt(20)
tf.paragraphs[0].font.color.rgb = PRIMARY
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Date
date_box = slide.shapes.add_textbox(Inches(0.5), Inches(3.5), Inches(9), Inches(0.5))
tf = date_box.text_frame
tf.paragraphs[0].text = datetime.now().strftime("%B %d, %Y")
tf.paragraphs[0].font.size = Pt(14)
tf.paragraphs[0].font.color.rgb = DARK
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
def add_kpi_dashboard(prs):
"""Slide 2: Executive Summary / KPIs"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Executive Summary"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
# KPI cards
metrics = [
('Revenue', kpis['revenue']),
('Customers', kpis['customers']),
('NPS', kpis['nps']),
('Churn', kpis['churn']),
]
for i, (label, data) in enumerate(metrics):
x = Inches(0.5 + i * 2.4)
y = Inches(1.5)
# Value
val_box = slide.shapes.add_textbox(x, y, Inches(2.2), Inches(1))
tf = val_box.text_frame
tf.paragraphs[0].text = data['value']
tf.paragraphs[0].font.size = Pt(36)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = PRIMARY
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Label
lbl_box = slide.shapes.add_textbox(x, Inches(2.5), Inches(2.2), Inches(0.4))
tf = lbl_box.text_frame
tf.paragraphs[0].text = label
tf.paragraphs[0].font.size = Pt(14)
tf.paragraphs[0].font.color.rgb = DARK
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Change indicator
status_color = SUCCESS if data['status'] == 'success' else (WARNING if data['status'] == 'warning' else DANGER)
chg_box = slide.shapes.add_textbox(x, Inches(2.9), Inches(2.2), Inches(0.4))
tf = chg_box.text_frame
tf.paragraphs[0].text = data['change']
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = status_color
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
def add_revenue_slide(prs):
"""Slide 3: Revenue & Growth"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Revenue & Growth"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
# Chart
chart_data = CategoryChartData()
chart_data.categories = monthly_revenue['Month'].tolist()
chart_data.add_series('Actual', monthly_revenue['Revenue'].tolist())
chart_data.add_series('Target', monthly_revenue['Target'].tolist())
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(0.5), Inches(1.3), Inches(6), Inches(4),
chart_data
).chart
chart.has_legend = True
chart.legend.include_in_layout = False
# Key insight
insight_box = slide.shapes.add_textbox(Inches(6.8), Inches(1.5), Inches(2.7), Inches(3))
tf = insight_box.text_frame
tf.word_wrap = True
tf.paragraphs[0].text = "Key Insight"
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.bold = True
p = tf.add_paragraph()
p.text = "Exceeded target by 7% in Q4. Enterprise deals drove growth."
p.font.size = Pt(14)
p.space_before = Pt(8)
return slide
def add_product_slide(prs):
"""Slide 4: Product Updates"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Product Updates"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
content_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.3), Inches(9), Inches(4))
tf = content_box.text_frame
for i, update in enumerate(product_updates):
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.text = f"[check] {update}"
p.font.size = Pt(20)
p.font.color.rgb = DARK
p.space_before = Pt(16)
return slide
def add_customers_slide(prs):
"""Slide 5: Customer Highlights"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Customer Highlights"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
# Table
rows = len(customer_wins) + 1
cols = 3
table = slide.shapes.add_table(rows, cols, Inches(0.5), Inches(1.5), Inches(9), Inches(2.5)).table
# Headers
headers = ['Customer', 'Contract Value', 'Segment']
for col, header in enumerate(headers):
cell = table.cell(0, col)
cell.text = header
cell.text_frame.paragraphs[0].font.bold = True
cell.text_frame.paragraphs[0].font.size = Pt(14)
# Data
for row, (customer, value, segment) in enumerate(customer_wins, 1):
table.cell(row, 0).text = customer
table.cell(row, 1).text = value
table.cell(row, 2).text = segment
for col in range(3):
table.cell(row, col).text_frame.paragraphs[0].font.size = Pt(14)
return slide
def add_challenges_slide(prs):
"""Slide 6: Challenges & Learnings"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = "Challenges & Learnings"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
content_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.3), Inches(9), Inches(4))
tf = content_box.text_frame
for i, challenge in enumerate(challenges):
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.text = f"• {challenge}"
p.font.size = Pt(20)
p.font.color.rgb = DARK
p.space_before = Pt(16)
return slide
def add_goals_slide(prs):
"""Slide 7: Next Quarter Goals"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
tf = title_box.text_frame
tf.paragraphs[0].text = f"Q1 {int(YEAR)+1 if QUARTER == 'Q4' else YEAR} Goals"
tf.paragraphs[0].font.size = Pt(32)
tf.paragraphs[0].font.bold = True
content_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.3), Inches(9), Inches(4))
tf = content_box.text_frame
for i, goal in enumerate(next_quarter_goals):
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.text = f"→ {goal}"
p.font.size = Pt(20)
p.font.color.rgb = PRIMARY
p.space_before = Pt(16)
return slide
def add_qa_slide(prs):
"""Slide 8: Q&A"""
slide = prs.slides.add_slide(prs.slide_layouts[6])
qa_box = slide.shapes.add_textbox(Inches(0.5), Inches(2), Inches(9), Inches(1.5))
tf = qa_box.text_frame
tf.paragraphs[0].text = "Questions?"
tf.paragraphs[0].font.size = Pt(48)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = PRIMARY
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
contact_box = slide.shapes.add_textbox(Inches(0.5), Inches(3.8), Inches(9), Inches(0.5))
tf = contact_box.text_frame
tf.paragraphs[0].text = f"{COMPANY_NAME} | {QUARTER} {YEAR}"
tf.paragraphs[0].font.size = Pt(16)
tf.paragraphs[0].font.color.rgb = DARK
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
# ============================================================
# BUILD PRESENTATION
# ============================================================
def create_quarterly_review():
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(5.625)
add_title_slide(prs)
add_kpi_dashboard(prs)
add_revenue_slide(prs)
add_product_slide(prs)
add_customers_slide(prs)
add_challenges_slide(prs)
add_goals_slide(prs)
add_qa_slide(prs)
filename = f'{QUARTER.lower()}_{YEAR}_review.pptx'
prs.save(filename)
print(f"Created: {filename}")
if __name__ == "__main__":
create_quarterly_review()---
Slide Structure Reference
| # | Slide | Purpose | Data Source |
|---|---|---|---|
| 1 | Title | Set context | Config vars |
| 2 | Executive Summary | KPIs at a glance | kpis dict |
| 3 | Revenue & Growth | Financial trends | monthly_revenue DataFrame |
| 4 | Product Updates | What shipped | product_updates list |
| 5 | Customer Highlights | Wins & logos | customer_wins list |
| 6 | Challenges | Honest retrospective | challenges list |
| 7 | Next Quarter | Forward-looking | next_quarter_goals list |
| 8 | Q&A | Discussion | None |
---
Integration with Real Data
From Database
import sqlite3
conn = sqlite3.connect('metrics.db')
monthly_revenue = pd.read_sql('''
SELECT strftime('%b', date) as Month,
SUM(revenue) as Revenue,
SUM(target) as Target
FROM sales
WHERE quarter = 'Q4' AND year = 2025
GROUP BY Month
''', conn)From API
import requests
response = requests.get('https://api.company.com/kpis', headers={'Authorization': 'Bearer ...'})
kpis = response.json()From CSV
monthly_revenue = pd.read_csv('revenue.csv')
customer_wins = pd.read_csv('wins.csv').values.tolist()---
Related Resources
- ../references/pptx-charts.md — Advanced chart styling
- ../references/pptx-layouts.md — Custom themes
- pitch-deck.md — External presentation format
Slide Narrative Template (Core, Non-AI)
Purpose: create a slide deck where every slide has a single takeaway and a clear narrative flow.
Inputs
- Audience + meeting goal (inform/decide/sell)
- Decision needed (what you want the audience to do)
- Source data (links + definitions)
Outputs
- Slide-by-slide narrative with one-sentence takeaway per slide
- Speaker notes and a "so what" for the decision
Core
0) Deck Metadata
- Deck title: {{TITLE}}
- Audience: {{AUDIENCE}}
- Decision/ask: {{DECISION}}
- Owner: {{OWNER}}
- Last updated: {{DATE}}
1) Narrative Spine (1 page)
- Context: {{CONTEXT}}
- Tension/problem: {{PROBLEM}}
- Insight: {{INSIGHT}}
- Proposal: {{PROPOSAL}}
- Proof: {{PROOF}}
- Decision: {{DECISION}}
2) Slide Plan
Use this table; keep "Takeaway" to one sentence.
| # | Slide title | Takeaway (1 sentence) | Visual | Source(s) | Speaker notes (bullets) |
|---|---|---|---|---|---|
| 1 | {{TITLE}} | {{TAKEAWAY}} | {{VISUAL}} | {{LINKS}} | {{NOTES}} |
| 2 | {{TITLE}} | {{TAKEAWAY}} | {{VISUAL}} | {{LINKS}} | {{NOTES}} |
| 3 | {{TITLE}} | {{TAKEAWAY}} | {{VISUAL}} | {{LINKS}} | {{NOTES}} |
3) Executive Summary (required for decision decks)
- What we recommend: {{RECOMMENDATION}}
- Why: {{WHY}}
- Risks: {{RISKS}}
- What we need from you: {{ASK}}
Decision Rules
- One slide = one point; if a slide has two points, split it.
- Every number needs a label (definition + timeframe + source).
- If the slide doesn’t support the decision, remove it.
Risks
- Dense slides that hide the argument
- Uncited numbers reduce trust
- Narrative without decision leads to "nice deck, no action"
Optional: AI / Automation
Use only if allowed by policy and data handling rules.
- Draft slide titles/takeaways; humans verify the argument and tone.
- Generate speaker notes and alternative phrasings; do not invent data.
{
"metadata": {
"skill": "document-pptx",
"updated": "2026-01-17",
"total_sources": 7,
"description": "Official PPTX automation libraries and accessibility guidance for slide decks.",
"version": "2.1"
},
"categories": {
"python_libraries": [
{
"name": "python-pptx Documentation",
"url": "https://python-pptx.readthedocs.io/",
"type": "documentation",
"relevance": "Primary reference for creating and editing PowerPoint files in Python.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true,
"tags": ["python", "pptx"]
}
],
"nodejs_libraries": [
{
"name": "PptxGenJS",
"url": "https://gitbrent.github.io/PptxGenJS/",
"type": "documentation",
"relevance": "Primary reference for generating PowerPoint decks in Node.js.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true,
"tags": ["nodejs", "pptx"]
},
{
"name": "PPTX-Automizer",
"url": "https://github.com/niclasgz/pptx-automizer",
"type": "library",
"relevance": "Template-driven PPTX automation for Node.js. Design in PowerPoint, inject data programmatically.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true,
"tags": ["nodejs", "pptx", "templates"]
}
],
"quality_and_accessibility": [
{
"name": "Make your PowerPoint presentations accessible (Microsoft Support)",
"url": "https://support.microsoft.com/en-us/office/make-your-powerpoint-presentations-accessible-to-people-with-disabilities-6f7772b2-2f33-4bd2-8ca7-dae3b2b3ef25",
"type": "guide",
"relevance": "Baseline accessibility practices for PPTX: reading order, alt text, slide titles, and contrast.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true,
"tags": ["accessibility", "powerpoint"]
},
{
"name": "Web Content Accessibility Guidelines (WCAG) 2.2 (W3C Recommendation)",
"url": "https://www.w3.org/TR/WCAG22/",
"type": "specification",
"relevance": "Accessibility baseline that affects slide decks when distributed as web/PDF or used in enterprise contexts.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false,
"tags": ["accessibility", "wcag"]
},
{
"name": "Office Open XML (ECMA-376)",
"url": "https://www.ecma-international.org/publications-and-standards/standards/ecma-376/",
"type": "specification",
"relevance": "Underlying standard behind .pptx; useful for interoperability and edge cases.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false,
"tags": ["spec", "ooxml", "pptx"]
},
{
"name": "Microsoft Open Specifications (Office formats)",
"url": "https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oi29500/7d096214-5f67-4e46-a1b8-88f8e9e6cf5b",
"type": "reference",
"relevance": "Reference entry point for Office Open XML and related Office format specifications.",
"update_frequency": "continuous",
"access": "free",
"add_as_web_search": true,
"tags": ["spec", "office"]
}
]
}
}
PPTX Animations & Transitions - Motion in Presentations
Deep-dive resource for slide transitions and build animations across python-pptx, PptxGenJS, and PPTX-Automizer.
---
Contents
- Library Support Matrix
- Slide Transitions
- PptxGenJS Transition Examples
- Build Animations
- python-pptx XML Approach for Animations
- PPTX-Automizer: Preserving Existing Animations
- When to Use Animations
- Accessibility Concerns
- Do / Avoid
- Related Resources
---
Library Support Matrix
| Capability | python-pptx | PptxGenJS | PPTX-Automizer |
|---|---|---|---|
| Slide transitions | No native API; XML manipulation required | Native transition option on slides | Preserves transitions from source slides |
| Build animations (appear, fade) | No API; raw OOXML only | Not supported | Preserves existing animations on copied slides |
| Custom motion paths | Raw OOXML only | Not supported | Preserves if present in template |
| Timing / auto-advance | XML manipulation | advanceAfter option | Preserves source timing |
Key takeaway: PptxGenJS is the simplest path for transitions. For build animations, design them in PowerPoint first and use PPTX-Automizer to inject data while keeping animations intact. python-pptx requires direct XML work.
---
Slide Transitions
Transition Types (OOXML)
| Transition | OOXML Element | Notes |
|---|---|---|
| Fade | <p:fade> | Smooth, safe default |
| Push | <p:push dir="l"> | Directional (l, r, u, d) |
| Wipe | <p:wipe dir="d"> | Directional reveal |
| Cover / Uncover | <p:cover> / <p:uncover> | Overlay motion |
| Split | <p:split orient="horz"> | Horizontal or vertical |
| Cut | <p:cut> | Instant, no animation |
| None | omit <p:transition> | Default behavior |
Timing Options
<!-- Auto-advance after 3 seconds, 500ms transition duration -->
<p:transition spd="med" advTm="3000">
<p:fade />
</p:transition>spd:slow(1000ms),med(750ms),fast(500ms)advTm: auto-advance in milliseconds (omit for click-to-advance)advClick: set to0to disable click advance when using auto-advance
---
PptxGenJS Transition Examples
import pptxgen from 'pptxgenjs';
const pptx = new pptxgen();
// Slide with fade transition
const slide1 = pptx.addSlide();
slide1.addText('Introduction', { x: 1, y: 1, fontSize: 32 });
slide1.transition = {
type: 'fade',
speed: 1.0, // seconds
advanceAfter: 5000 // auto-advance after 5s (omit for manual)
};
// Slide with push transition
const slide2 = pptx.addSlide();
slide2.addText('Key Findings', { x: 1, y: 1, fontSize: 32 });
slide2.transition = {
type: 'push',
speed: 0.5,
dir: 'l' // push from left
};
// Kiosk-style auto-advancing deck
const kioskSlide = pptx.addSlide();
kioskSlide.addText('Auto-play slide', { x: 1, y: 2, fontSize: 24 });
kioskSlide.transition = {
type: 'fade',
speed: 0.75,
advanceAfter: 4000
};
await pptx.writeFile({ fileName: 'transitions.pptx' });Available PptxGenJS Transition Types
fade, push, wipe, zoom, split, cover, uncover, cut, random, none
---
Build Animations
Build animations reveal slide elements sequentially (bullet points appearing one at a time, chart series fading in). PowerPoint uses the <p:timing> tree inside each slide's XML.
Animation Types
| Effect | OOXML Preset | Use Case |
|---|---|---|
| Appear | anim_appear | Instant reveal, no motion |
| Fade | anim_fade | Subtle entrance |
| Fly In | anim_flyIn | Directional entrance (from bottom, left, etc.) |
| Wipe | anim_wipe | Progressive reveal for charts |
| Grow & Turn | anim_growTurn | Emphasis on icons or callouts |
Animation Sequence Concepts
Slide Timing Tree
├── Build sequence 1 (on click)
│ ├── Shape A → Fade In (duration 500ms)
│ └── Shape B → Fade In (delay 200ms after A)
├── Build sequence 2 (on click)
│ └── Chart → Wipe by series
└── Exit sequence (on click)
└── Shape A → Fade Out---
python-pptx XML Approach for Animations
python-pptx has no animation API. Manipulate the slide's <p:timing> element directly.
from pptx import Presentation
from pptx.oxml.ns import qn
from lxml import etree
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[1])
# Add a text shape that we want to animate
title = slide.shapes.title
title.text = "Animated Title"
body = slide.placeholders[1]
body.text = "This appears on click"
# Get the shape's spTree ID for targeting
body_sp = body._element
shape_id = body_sp.attrib.get('id', body_sp.find(qn('p:nvSpPr')).find(qn('p:cNvPr')).attrib['id'])
# Build the timing XML for a fade-in animation
timing_xml = f'''
<p:timing xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<p:tnLst>
<p:par>
<p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">
<p:childTnLst>
<p:seq concurrent="1" nextAc="seek">
<p:cTn id="2" dur="indefinite" nodeType="mainSeq">
<p:childTnLst>
<p:par>
<p:cTn id="3" fill="hold">
<p:stCondLst>
<p:cond delay="0"/>
</p:stCondLst>
<p:childTnLst>
<p:par>
<p:cTn id="4" fill="hold">
<p:stCondLst>
<p:cond delay="0"/>
</p:stCondLst>
<p:childTnLst>
<p:set>
<p:cBhvr>
<p:cTn id="5" dur="1" fill="hold">
<p:stCondLst>
<p:cond delay="0"/>
</p:stCondLst>
</p:cTn>
<p:tgtEl>
<p:spTgt spid="{shape_id}"/>
</p:tgtEl>
<p:attrNameLst>
<p:attrName>style.visibility</p:attrName>
</p:attrNameLst>
</p:cBhvr>
<p:to><p:strVal val="visible"/></p:to>
</p:set>
</p:childTnLst>
</p:cTn>
</p:par>
</p:childTnLst>
</p:cTn>
</p:par>
</p:childTnLst>
</p:cTn>
<p:prevCondLst>
<p:cond evt="onPrev" delay="0">
<p:tgtEl><p:sldTgt/></p:tgtEl>
</p:cond>
</p:prevCondLst>
<p:nextCondLst>
<p:cond evt="onClick" delay="0">
<p:tgtEl><p:sldTgt/></p:tgtEl>
</p:cond>
</p:nextCondLst>
</p:seq>
</p:childTnLst>
</p:cTn>
</p:par>
</p:tnLst>
</p:timing>
'''
timing_element = etree.fromstring(timing_xml)
slide._element.append(timing_element)
prs.save('animated.pptx')Warning: This XML is fragile. Test output in PowerPoint after every change. Consider designing animations in PowerPoint and using PPTX-Automizer to merge content instead.
---
PPTX-Automizer: Preserving Existing Animations
PPTX-Automizer copies slides from template files, keeping all animations and transitions intact.
import Automizer from 'pptx-automizer';
const automizer = new Automizer({
templateDir: './templates',
outputDir: './output',
});
const pptx = automizer
.loadRoot('base.pptx')
.load('animated-template.pptx', 'animated');
// Copy slide 2 from animated template — all animations are preserved
pptx.addSlide('animated', 2, (slide) => {
slide.modifyElement('TitlePlaceholder', { text: 'Updated Title' });
slide.modifyElement('DataTable', { replaceTable: updatedTableData });
});
await pptx.write('output.pptx');Workflow: Design animations in PowerPoint, save as template, use Automizer to swap data. Animations stay intact.
---
When to Use Animations
Good Uses
- Progressive disclosure: Reveal complex diagrams step by step so the audience follows your logic
- Data storytelling: Animate chart series to show growth over time
- Agenda navigation: Highlight the current section in a recurring agenda slide
- Before/after reveals: Show the "after" state on click for impact
When to Skip
- Dense data slides: Animations slow down comprehension when the audience needs to scan
- Printed or exported decks: Animations are invisible in PDF exports
- Kiosk / self-service: Auto-play timing is hard to calibrate for varied reading speeds
- Accessibility-first contexts: Screen readers and reduced-motion users get no benefit
---
Accessibility Concerns
- Reduced motion: Users with vestibular disorders rely on OS-level "reduce motion" preferences. PPTX files do not honor
prefers-reduced-motion. Provide a static version of any animated deck. - Screen readers: Animations are invisible to screen readers. Ensure all content makes sense without animation sequence.
- Flashing content: Avoid rapid flashing (3+ flashes per second). This can trigger photosensitive seizures. WCAG 2.3.1 applies.
- Auto-advance timing: If using auto-advance, set generous timing (5s+ per bullet point) or provide manual override instructions.
- Alt text: Animated elements still need alt text. Animations do not replace descriptive text.
---
Do / Avoid
Do
- Use fade for most transitions — it is unobtrusive and professional
- Apply consistent transition type across the entire deck
- Build bullet points one at a time for persuasive presentations
- Design animations in PowerPoint, then inject data with Automizer
- Test the final file in PowerPoint (not just a viewer) to verify timing
- Provide a non-animated PDF export for distribution
Avoid
- Mixing multiple transition types on adjacent slides
- Using fly-in, bounce, or spin effects in business presentations
- Adding animations purely for decoration
- Relying on auto-advance without a manual fallback
- Using python-pptx XML animation hacks in production without thorough QA
- Assuming animations work in Google Slides or Keynote imports
---
Related Resources
- pptx-layouts.md - Master slides and themes
- pptx-charts.md - Chart styling and data binding
- ../assets/pitch-deck.md - Complete pitch deck template
PPTX Charts - Data Visualization in PowerPoint
Deep-dive resource for charts, graphs, and data visualization with python-pptx and pptxgenjs.
---
Contents
- Chart Type Reference
- Column Chart (Python)
- Line Chart with Markers
- Pie Chart
- Combo Chart (Column + Line)
- Charts in Node.js (pptxgenjs)
- Chart Styling Best Practices
- Dynamic Data from DataFrame
- Chart from Database Query
- Related Resources
---
Chart Type Reference
| Chart Type | Constant (python-pptx) | Use Case |
|---|---|---|
| Column (Clustered) | XL_CHART_TYPE.COLUMN_CLUSTERED | Compare categories |
| Column (Stacked) | XL_CHART_TYPE.COLUMN_STACKED | Part-to-whole by category |
| Bar (Clustered) | XL_CHART_TYPE.BAR_CLUSTERED | Horizontal comparison |
| Line | XL_CHART_TYPE.LINE | Trends over time |
| Line (Markers) | XL_CHART_TYPE.LINE_MARKERS | Trends with data points |
| Pie | XL_CHART_TYPE.PIE | Proportions (single series) |
| Doughnut | XL_CHART_TYPE.DOUGHNUT | Proportions with center |
| Area | XL_CHART_TYPE.AREA | Cumulative trends |
| Scatter | XL_CHART_TYPE.XY_SCATTER | Correlation analysis |
| Bubble | XL_CHART_TYPE.BUBBLE | Three-variable comparison |
| Radar | XL_CHART_TYPE.RADAR | Multi-axis comparison |
---
Column Chart (Python)
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches, Pt
from pptx.dml.color import RgbColor
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
# Chart data
chart_data = CategoryChartData()
chart_data.categories = ['Q1', 'Q2', 'Q3', 'Q4']
chart_data.add_series('2024', (120, 145, 160, 185))
chart_data.add_series('2025', (150, 175, 195, 225))
# Add chart
x, y, cx, cy = Inches(0.5), Inches(1.5), Inches(9), Inches(5)
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
x, y, cx, cy,
chart_data
).chart
# Style the chart
chart.has_legend = True
chart.legend.include_in_layout = False
# Style series colors
series_colors = [
RgbColor(0x00, 0x66, 0xCC), # Blue
RgbColor(0x00, 0x99, 0xFF), # Light blue
]
for idx, series in enumerate(chart.series):
series.format.fill.solid()
series.format.fill.fore_color.rgb = series_colors[idx]
prs.save('column_chart.pptx')---
Line Chart with Markers
# Assumes you already imported `CategoryChartData` and `Inches`, and have a `slide` reference.
from pptx.enum.chart import XL_CHART_TYPE, XL_MARKER_STYLE
chart_data = CategoryChartData()
chart_data.categories = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
chart_data.add_series('Revenue', (100, 110, 125, 140, 155, 180))
chart_data.add_series('Target', (100, 115, 130, 145, 160, 175))
chart = slide.shapes.add_chart(
XL_CHART_TYPE.LINE_MARKERS,
Inches(0.5), Inches(1.5), Inches(9), Inches(5),
chart_data
).chart
# Configure markers
for series in chart.series:
series.marker.style = XL_MARKER_STYLE.CIRCLE
series.marker.size = 8---
Pie Chart
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
chart_data = CategoryChartData()
chart_data.categories = ['Product A', 'Product B', 'Product C', 'Other']
chart_data.add_series('Market Share', (35, 28, 22, 15))
chart = slide.shapes.add_chart(
XL_CHART_TYPE.PIE,
Inches(2), Inches(1.5), Inches(6), Inches(5),
chart_data
).chart
# Add data labels
plot = chart.plots[0]
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.show_percentage = True
data_labels.show_category_name = True
data_labels.show_value = False---
Combo Chart (Column + Line)
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
# Create column chart first
chart_data = CategoryChartData()
chart_data.categories = ['Q1', 'Q2', 'Q3', 'Q4']
chart_data.add_series('Revenue', (100, 150, 180, 225))
chart_data.add_series('Growth %', (10, 15, 12, 25))
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(0.5), Inches(1.5), Inches(9), Inches(5),
chart_data
).chart
# Change second series to line (requires XML manipulation)
# python-pptx doesn't natively support combo charts
# Consider pptxgenjs for native combo chart support---
Charts in Node.js (pptxgenjs)
import pptxgen from 'pptxgenjs';
async function main() {
const pptx = new pptxgen();
// Bar chart
let slide = pptx.addSlide();
slide.addChart(pptx.ChartType.bar, [
{ name: 'Q1', labels: ['Sales', 'Costs', 'Profit'], values: [100, 70, 30] },
{ name: 'Q2', labels: ['Sales', 'Costs', 'Profit'], values: [120, 75, 45] },
{ name: 'Q3', labels: ['Sales', 'Costs', 'Profit'], values: [140, 80, 60] },
{ name: 'Q4', labels: ['Sales', 'Costs', 'Profit'], values: [180, 90, 90] },
], {
x: 0.5, y: 1.5, w: 9, h: 5,
showLegend: true,
legendPos: 'b',
showTitle: true,
title: 'Quarterly Performance',
barDir: 'bar', // horizontal bars
barGrouping: 'clustered',
});
// Line chart
slide = pptx.addSlide();
slide.addChart(pptx.ChartType.line, [
{ name: 'Actual', labels: ['Jan','Feb','Mar','Apr','May','Jun'], values: [10,20,30,40,50,60] },
{ name: 'Target', labels: ['Jan','Feb','Mar','Apr','May','Jun'], values: [15,25,35,45,55,65] },
], {
x: 0.5, y: 1.5, w: 9, h: 5,
showLegend: true,
lineDataSymbol: 'circle',
lineDataSymbolSize: 8,
});
// Pie chart
slide = pptx.addSlide();
slide.addChart(pptx.ChartType.pie, [
{ name: 'Market Share', labels: ['Product A','Product B','Product C'], values: [40, 35, 25] },
], {
x: 2, y: 1.5, w: 6, h: 5,
showLegend: true,
showPercent: true,
showValue: false,
});
// "Combo-like" chart: column series plus line styling options (see PptxGenJS docs for true combos)
slide = pptx.addSlide();
slide.addChart(pptx.ChartType.bar, [
{ name: 'Revenue', labels: ['Q1','Q2','Q3','Q4'], values: [100,150,180,225] },
], {
x: 0.5, y: 1.5, w: 9, h: 5,
chartColors: ['0066CC'],
catAxisTitle: 'Quarter',
valAxisTitle: 'Revenue ($K)',
showValue: true,
lineDataSymbol: 'none',
});
await pptx.writeFile({ fileName: 'charts.pptx' });
}
main();---
Chart Styling Best Practices
Colors
# Use brand colors consistently
from pptx.dml.color import RgbColor
CHART_COLORS = [
RgbColor(0x00, 0x66, 0xCC), # Primary
RgbColor(0x00, 0x99, 0xFF), # Secondary
RgbColor(0xFF, 0x66, 0x00), # Accent
RgbColor(0x66, 0x66, 0x66), # Neutral
]Data Labels
plot = chart.plots[0]
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.font.size = Pt(10)
data_labels.font.color.rgb = RgbColor(0x33, 0x33, 0x33)
data_labels.number_format = '#,##0' # Thousands separatorAxis Formatting
# Value axis
value_axis = chart.value_axis
value_axis.has_major_gridlines = True
value_axis.major_gridlines.format.line.color.rgb = RgbColor(0xE0, 0xE0, 0xE0)
value_axis.tick_labels.font.size = Pt(10)
value_axis.tick_labels.number_format = '$#,##0K'
# Category axis
category_axis = chart.category_axis
category_axis.tick_labels.font.size = Pt(10)
category_axis.tick_labels.font.bold = False---
Dynamic Data from DataFrame
import pandas as pd
from pptx.chart.data import CategoryChartData
# Sample DataFrame
df = pd.DataFrame({
'Quarter': ['Q1', 'Q2', 'Q3', 'Q4'],
'Revenue': [100, 150, 180, 225],
'Costs': [70, 85, 95, 110],
'Profit': [30, 65, 85, 115]
})
# Build chart data from DataFrame
chart_data = CategoryChartData()
chart_data.categories = df['Quarter'].tolist()
for col in ['Revenue', 'Costs', 'Profit']:
chart_data.add_series(col, df[col].tolist())
# Add to slide
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(0.5), Inches(1.5), Inches(9), Inches(5),
chart_data
).chart---
Chart from Database Query
import sqlite3
from pptx.chart.data import CategoryChartData
# Query database
conn = sqlite3.connect('sales.db')
cursor = conn.execute('''
SELECT quarter, SUM(revenue), SUM(costs)
FROM sales
WHERE year = 2025
GROUP BY quarter
ORDER BY quarter
''')
rows = cursor.fetchall()
conn.close()
# Build chart
chart_data = CategoryChartData()
chart_data.categories = [row[0] for row in rows]
chart_data.add_series('Revenue', [row[1] for row in rows])
chart_data.add_series('Costs', [row[2] for row in rows])---
Related Resources
- pptx-layouts.md - Master slides and themes
- ../assets/pitch-deck.md - Charts in pitch context
- ../assets/quarterly-review.md - Business charts
PPTX Layouts - Master Slides, Themes & Templates
Deep-dive resource for PowerPoint layout customization with python-pptx and pptxgenjs.
---
Contents
- Master Slide Architecture
- Access Slide Masters (Python)
- Custom Theme Colors
- Brand Color Palette Pattern
- Use Existing Template
- Custom Placeholder Positions
- Node.js Theme Configuration
- Slide Size Presets
- Background Patterns
- Related Resources
---
Master Slide Architecture
PowerPoint uses a three-level hierarchy:
Slide Master (top level)
├── Slide Layouts (mid level) - Title, Content, Blank, etc.
└── Individual Slides (bottom level) - Your actual contentChanges to the Slide Master cascade down to all layouts and slides.
---
Access Slide Masters (Python)
from pptx import Presentation
prs = Presentation()
# Access the first slide master
slide_master = prs.slide_master
# List all available layouts
for idx, layout in enumerate(slide_master.slide_layouts):
print(f"{idx}: {layout.name}")Default Layout Names:
| Index | Name | Placeholders |
|---|---|---|
| 0 | Title Slide | title, subtitle |
| 1 | Title and Content | title, body |
| 2 | Section Header | title, subtitle |
| 3 | Two Content | title, body (left), body (right) |
| 4 | Comparison | title, body x4 |
| 5 | Title Only | title |
| 6 | Blank | none |
| 7 | Content with Caption | body, title, text |
| 8 | Picture with Caption | picture, title, text |
---
Custom Theme Colors
from pptx import Presentation
from pptx.dml.color import RgbColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.util import Inches, Pt
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
# Access theme colors via shape formatting (fill/line)
shape = slide.shapes.add_shape(
MSO_SHAPE.RECTANGLE,
Inches(1), Inches(1), Inches(2), Inches(1)
)
# Set fill color
fill = shape.fill
fill.solid()
fill.fore_color.rgb = RgbColor(0x00, 0x66, 0xCC) # Brand blue
# Set line color
line = shape.line
line.color.rgb = RgbColor(0x00, 0x33, 0x66)
line.width = Pt(2)---
Brand Color Palette Pattern
from pptx.dml.color import RgbColor
class BrandColors:
PRIMARY = RgbColor(0x00, 0x66, 0xCC) # #0066CC
SECONDARY = RgbColor(0x00, 0x99, 0xFF) # #0099FF
ACCENT = RgbColor(0xFF, 0x66, 0x00) # #FF6600
DARK = RgbColor(0x33, 0x33, 0x33) # #333333
LIGHT = RgbColor(0xF5, 0xF5, 0xF5) # #F5F5F5
SUCCESS = RgbColor(0x28, 0xA7, 0x45) # #28A745
WARNING = RgbColor(0xFF, 0xC1, 0x07) # #FFC107
DANGER = RgbColor(0xDC, 0x35, 0x45) # #DC3545---
Use Existing Template
from pptx import Presentation
# Load template with custom master slides
prs = Presentation('company_template.pptx')
# Use template's layouts
title_layout = prs.slide_layouts[0]
content_layout = prs.slide_layouts[1]
# Add slides using template layouts
slide = prs.slides.add_slide(title_layout)
slide.shapes.title.text = "Uses Template Styling"
prs.save('branded_presentation.pptx')---
Custom Placeholder Positions
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation()
# Blank layout for full control
blank_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(blank_layout)
# Custom title position
title_box = slide.shapes.add_textbox(
Inches(0.5), Inches(0.3), # x, y
Inches(9), Inches(0.8) # width, height
)
title_frame = title_box.text_frame
title_frame.paragraphs[0].text = "Custom Positioned Title"
title_frame.paragraphs[0].font.size = Pt(36)
title_frame.paragraphs[0].font.bold = True
# Custom content area
content_box = slide.shapes.add_textbox(
Inches(0.5), Inches(1.5),
Inches(9), Inches(5)
)---
Node.js Theme Configuration
import pptxgen from 'pptxgenjs';
const pptx = new pptxgen();
// Set presentation metadata
pptx.author = 'Company Name';
pptx.company = 'Company Name';
pptx.subject = 'Quarterly Report';
pptx.title = 'Q4 2025 Business Review';
// Define master slide
pptx.defineSlideMaster({
title: 'BRANDED_SLIDE',
background: { color: 'FFFFFF' },
objects: [
// Header bar
{ rect: { x: 0, y: 0, w: '100%', h: 0.75, fill: { color: '0066CC' } } },
// Logo placeholder
{ image: { x: 0.3, y: 0.1, w: 1.5, h: 0.5, path: 'logo.png' } },
// Footer
{ text: {
text: 'Confidential',
options: { x: 0.3, y: 5.2, w: 2, h: 0.3, fontSize: 8, color: '666666' }
}},
// Page number
{ text: {
text: 'Slide {slideNumber}',
options: { x: 8.5, y: 5.2, w: 1, h: 0.3, fontSize: 8, color: '666666' }
}},
],
slideNumber: { x: 9.0, y: 5.2, fontSize: 8, color: '666666' },
});
// Use custom master
const slide = pptx.addSlide({ masterName: 'BRANDED_SLIDE' });---
Slide Size Presets
from pptx.util import Inches
# Standard 16:9 (default)
# Width: 10 inches, Height: 5.625 inches
# Standard 4:3
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)
# Widescreen 16:10
prs.slide_width = Inches(10)
prs.slide_height = Inches(6.25)
# A4 Portrait (for print)
prs.slide_width = Inches(8.27)
prs.slide_height = Inches(11.69)// pptxgenjs
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9'; // or 'LAYOUT_4x3', 'LAYOUT_16x10', 'LAYOUT_WIDE'---
Background Patterns
from pptx import Presentation
from pptx.dml.color import RgbColor
# Solid color background
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
background = slide.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = RgbColor(0xF0, 0xF0, 0xF0)
# Gradient backgrounds require XML manipulation (python-pptx doesn't have native gradient support).// pptxgenjs gradient background
slide.background = {
color: '0066CC',
// or gradient
// fill: { type: 'solid', color: '0066CC' }
};---
Related Resources
- pptx-charts.md - Chart styling and data binding
- ../assets/pitch-deck.md - Complete pitch deck
- ../assets/quarterly-review.md - Business review template
PPTX Speaker Notes & Delivery - Presenter-Side Content
Deep-dive resource for speaker notes, rehearsal workflow, and delivery preparation with python-pptx and PptxGenJS.
---
Contents
- Adding Speaker Notes (Python)
- Adding Speaker Notes (PptxGenJS)
- Notes Structure Template
- Speaker Notes Best Practices
- Presenter View Setup
- Exporting Notes and Handouts
- Rehearsal and Timing
- Do / Avoid
- Pre-Presentation Delivery Checklist
- Related Resources
---
Adding Speaker Notes (Python)
from pptx import Presentation
from pptx.util import Pt
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = "Q4 Results"
slide.placeholders[1].text = "Revenue grew 22% YoY"
# Add speaker notes
notes_slide = slide.notes_slide
notes_tf = notes_slide.notes_text_frame
notes_tf.text = "Opening: Remind audience of Q3 target.\nKey stat: $4.2M revenue.\nTransition: Move to regional breakdown."
prs.save('with_notes.pptx')Rich-Formatted Notes
from pptx.util import Pt
from pptx.dml.color import RgbColor
notes_slide = slide.notes_slide
notes_tf = notes_slide.notes_text_frame
# Clear default paragraph and build formatted notes
notes_tf.clear()
p = notes_tf.paragraphs[0]
run = p.add_run()
run.text = "KEY POINT: "
run.font.bold = True
run.font.size = Pt(12)
run2 = p.add_run()
run2.text = "Revenue exceeded forecast by 8%."
run2.font.size = Pt(12)
# Add a second paragraph
p2 = notes_tf.add_paragraph()
p2.text = "If asked about margins, defer to slide 7."
p2.font.size = Pt(11)
p2.font.italic = TrueBatch-Add Notes from a Dictionary
from pptx import Presentation
prs = Presentation('existing_deck.pptx')
# Map slide index to notes content
notes_map = {
0: "Welcome the audience. Introduce yourself and the agenda.",
1: "Key metric: 42% conversion rate. Compare to industry avg 28%.",
2: "Transition: 'Now let's look at what's driving this growth.'",
3: "Close with the ask: Series A terms on slide 5.",
}
for idx, notes_text in notes_map.items():
slide = prs.slides[idx]
notes_slide = slide.notes_slide
notes_slide.notes_text_frame.text = notes_text
prs.save('deck_with_notes.pptx')---
Adding Speaker Notes (PptxGenJS)
import pptxgen from 'pptxgenjs';
const pptx = new pptxgen();
const slide = pptx.addSlide();
slide.addText('Market Opportunity', { x: 1, y: 1, fontSize: 32 });
// Add speaker notes as a string
slide.addNotes('TAM is $12B. We target the mid-market segment ($2B SAM).\nPause here for questions.');
// Multi-slide notes pattern
const slidesData = [
{ title: 'Problem', notes: 'Start with the customer pain point story from Acme Corp.' },
{ title: 'Solution', notes: 'Demo video is 90 seconds. Cue it before advancing.' },
{ title: 'Traction', notes: 'Emphasize MoM growth. If asked about churn, see appendix.' },
];
for (const data of slidesData) {
const s = pptx.addSlide();
s.addText(data.title, { x: 1, y: 1, fontSize: 28 });
s.addNotes(data.notes);
}
await pptx.writeFile({ fileName: 'deck_with_notes.pptx' });---
Notes Structure Template
Use a consistent structure for every slide's notes. This keeps delivery smooth and rehearsal predictable.
[OPENING] — Hook or transition from previous slide
"As we saw in the pipeline data..."
[KEY POINTS] — 2-3 bullets, not sentences
• Revenue: $4.2M (+22% YoY)
• Top driver: Enterprise segment
• Risk: APAC slowdown in Q1
[DATA CUE] — Specific numbers to reference
Exact figure: $4,217,000
Comparison: Q3 was $3,460,000
[TRANSITION] — Bridge to next slide
"Let's look at how this breaks down by region."
[TIME CHECK] — Target time at this point
⏱ Should be at ~4:00 of 15:00---
Speaker Notes Best Practices
Write Key Points, Not Scripts
- Notes are a safety net, not a teleprompter
- 3-5 bullet points per slide maximum
- Include exact numbers you might forget under pressure
- Write transition phrases verbatim — these are the hardest to improvise
Include Audience Cues
- "PAUSE for questions here"
- "Check room energy — if low, use the Acme story"
- "This slide is optional — skip if running over 12 min"
- "CLICK to advance build animation before speaking"
Timing Markers
- Add cumulative time targets: "⏱ 5:00 / 20:00"
- Flag slides that tend to run long: "WARNING: This slide eats time. Stay under 2 min."
- Mark optional slides: "SKIP if under 5 min remaining"
---
Presenter View Setup
Presenter View shows notes on the presenter's screen while the audience sees only slides.
Enabling Presenter View
| Platform | How to Enable |
|---|---|
| PowerPoint (Windows) | Slide Show > Use Presenter View (check box) |
| PowerPoint (Mac) | Slide Show > Presenter View |
| Google Slides | Present > Presenter View (dropdown arrow) |
| Keynote | Play > Presenter Display > Customize |
Presenter View Features
- Current slide and next slide preview
- Speaker notes panel (resizable)
- Elapsed time and clock
- Slide navigation thumbnails
- Zoom into current slide
- Black/white screen toggle (B or W key)
Keyboard Shortcuts During Presentation
| Action | Key |
|---|---|
| Next slide | Right arrow, Space, Enter, N |
| Previous slide | Left arrow, Backspace, P |
| Go to slide N | Type number + Enter |
| Black screen | B |
| White screen | W |
| End show | Esc |
| Toggle pointer | Ctrl+P (Windows), Cmd+P (Mac) |
---
Exporting Notes and Handouts
Notes Pages (Python)
PowerPoint's "Notes Page" layout prints one slide per page with notes below. This is controlled via print settings, not python-pptx. To generate a notes-included PDF:
# macOS — use LibreOffice headless to export with notes
libreoffice --headless --convert-to pdf:"impress_pdf_Export:ExportNotesPages=true" deck.pptxExtract Notes to Text
from pptx import Presentation
prs = Presentation('deck.pptx')
for idx, slide in enumerate(prs.slides):
notes_slide = slide.notes_slide
notes_text = notes_slide.notes_text_frame.text
if notes_text.strip():
print(f"--- Slide {idx + 1} ---")
print(notes_text)
print()Generate Speaker Script Markdown
from pptx import Presentation
prs = Presentation('deck.pptx')
lines = ["# Speaker Script\n"]
for idx, slide in enumerate(prs.slides):
title = slide.shapes.title.text if slide.shapes.title else f"Slide {idx + 1}"
notes = slide.notes_slide.notes_text_frame.text.strip()
lines.append(f"## Slide {idx + 1}: {title}\n")
lines.append(f"{notes}\n" if notes else "_No notes._\n")
with open('speaker_script.md', 'w') as f:
f.write('\n'.join(lines))---
Rehearsal and Timing
Rehearsal Workflow
1. First pass: Read notes aloud, slide by slide. Record total time. 2. Trim pass: Cut any note that you naturally remember. Keep only what you forget. 3. Timing pass: Add time markers after measuring your natural pace. 4. Dry run: Present to a colleague using Presenter View. Get feedback on pacing. 5. Final notes edit: Update based on dry run. Remove anything you no longer need.
Timing Rules of Thumb
| Content Type | Time per Slide |
|---|---|
| Title / section divider | 15-30 seconds |
| Key message with build | 1-2 minutes |
| Data-heavy chart | 2-3 minutes |
| Demo or video | Actual runtime + 30s buffer |
| Q&A prompt | 3-5 minutes |
Programmatic Timing Validation
# Estimate presentation duration from notes word count
from pptx import Presentation
prs = Presentation('deck.pptx')
total_words = 0
for slide in prs.slides:
notes = slide.notes_slide.notes_text_frame.text
total_words += len(notes.split())
# Average speaking pace: 130 words per minute
estimated_minutes = total_words / 130
print(f"Estimated speaking time: {estimated_minutes:.1f} minutes")
print(f"Slide count: {len(prs.slides)}")
print(f"Average words per slide: {total_words / len(prs.slides):.0f}")---
Do / Avoid
Do
- Write notes as bullet points, not full paragraphs
- Include exact data points you need to cite verbally
- Add transition phrases between slides
- Put timing markers on every 3rd-4th slide
- Test Presenter View on the actual presentation hardware
- Export a speaker script for backup (phone or printout)
Avoid
- Writing a word-for-word script (you will read it, and the audience will notice)
- Leaving notes empty on data slides where you need exact figures
- Assuming Presenter View will work without testing (dual monitor setup varies)
- Putting confidential information in notes (they export with handouts)
- Using notes as a dumping ground for cut slide content
- Skipping rehearsal because "I know this material"
---
Pre-Presentation Delivery Checklist
- [ ] All slides have speaker notes (no blanks on data slides)
- [ ] Notes contain timing markers at regular intervals
- [ ] Total estimated time fits the allotted slot (with 10% buffer)
- [ ] Presenter View tested on target hardware
- [ ] Backup copy of notes exported as text or PDF
- [ ] Font rendering verified on presentation machine
- [ ] Slide clicker / remote tested
- [ ] Video and audio clips tested for playback
- [ ] Screen resolution matches slide aspect ratio (16:9 vs 4:3)
- [ ] Confidence monitor or podium screen confirmed with AV team
---
Related Resources
- pptx-layouts.md - Master slides and themes
- pptx-animations-transitions.md - Animations and transitions
- ../assets/pitch-deck.md - Complete pitch deck template
- ../assets/quarterly-review.md - Business review template
PPTX Template & Branding - Corporate Identity Management
Deep-dive resource for slide masters, theme configuration, branded templates, and multi-brand workflows with python-pptx, PptxGenJS, and PPTX-Automizer.
---
Contents
- Slide Master and Layout Architecture
- Theme Elements
- Creating Reusable Templates (Python)
- Branded Master Slides (PptxGenJS)
- PPTX-Automizer for Branded Templates
- Template Versioning and Distribution
- Multi-Brand Support
- Brand Consistency Checklist
- Common Pitfalls
- Do / Avoid
- Related Resources
---
Slide Master and Layout Architecture
Presentation (.pptx)
├── Slide Master (slideMaster1.xml)
│ ├── Theme (theme1.xml) — colors, fonts, effects
│ ├── Slide Layout: Title Slide
│ ├── Slide Layout: Title and Content
│ ├── Slide Layout: Section Header
│ ├── Slide Layout: Two Content
│ ├── Slide Layout: Blank
│ └── Slide Layout: Custom Layout ...
└── Slides
└── Each slide references one layoutInheritance chain: Theme → Slide Master → Slide Layout → Individual Slide. Properties set lower in the chain override those set higher. A color defined on the slide master is inherited by all layouts unless explicitly overridden.
Layout Placeholders
Each layout defines placeholder positions, sizes, and types. When you add a slide from a layout, you get that layout's placeholders.
from pptx import Presentation
prs = Presentation('company_template.pptx')
for idx, layout in enumerate(prs.slide_master.slide_layouts):
print(f"Layout {idx}: {layout.name}")
for ph in layout.placeholders:
print(f" Placeholder {ph.placeholder_format.idx}: {ph.name} ({ph.placeholder_format.type})")---
Theme Elements
A PPTX theme (theme1.xml) controls three categories:
Color Scheme
| Slot | Purpose | Typical Mapping |
|---|---|---|
| dk1 | Dark 1 | Primary text (black/dark gray) |
| lt1 | Light 1 | Background (white) |
| dk2 | Dark 2 | Secondary text |
| lt2 | Light 2 | Secondary background |
| accent1-6 | Accent colors | Brand palette, chart colors |
| hlink | Hyperlink | Link color |
| folHlink | Followed hyperlink | Visited link color |
Font Scheme
<a:fontScheme name="Corporate">
<a:majorFont>
<a:latin typeface="Inter"/> <!-- Headings -->
</a:majorFont>
<a:minorFont>
<a:latin typeface="Inter"/> <!-- Body text -->
</a:minorFont>
</a:fontScheme>Effect Scheme
Effects (shadows, reflections, 3D) are defined at the theme level. Keep them minimal for corporate use.
---
Creating Reusable Templates (Python)
Start from an Existing Template
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RgbColor
from pptx.enum.text import PP_ALIGN
# Load a designed template with correct masters
prs = Presentation('brand_template.pptx')
# Verify available layouts
for idx, layout in enumerate(prs.slide_master.slide_layouts):
print(f"{idx}: {layout.name}")
# Use branded layouts
title_slide = prs.slides.add_slide(prs.slide_layouts[0])
title_slide.shapes.title.text = "Q4 Business Review"
title_slide.placeholders[1].text = "December 2025"
content_slide = prs.slides.add_slide(prs.slide_layouts[1])
content_slide.shapes.title.text = "Revenue Summary"
body = content_slide.placeholders[1]
body.text = "Total revenue: $4.2M"
prs.save('branded_output.pptx')Set Theme Colors via XML
python-pptx does not expose theme color modification through its API. Use XML manipulation:
from pptx import Presentation
from lxml import etree
from pptx.oxml.ns import qn
prs = Presentation()
theme = prs.slide_master.element.find(qn('p:cSld')).getparent()
# Access the theme XML (stored in the theme part)
theme_part = prs.slide_master.part.slide_master.theme_part
theme_element = theme_part._element
# Find the color scheme
clrScheme = theme_element.find('.//' + qn('a:clrScheme'))
# Update accent1 to brand blue
accent1 = clrScheme.find(qn('a:accent1'))
srgbClr = accent1.find(qn('a:srgbClr'))
if srgbClr is not None:
srgbClr.set('val', '0066CC')
prs.save('themed.pptx')Set Default Font Sizes
from pptx import Presentation
from pptx.util import Pt
prs = Presentation('brand_template.pptx')
slide = prs.slides.add_slide(prs.slide_layouts[1])
# Title formatting
title = slide.shapes.title
title.text = "Section Title"
for paragraph in title.text_frame.paragraphs:
paragraph.font.size = Pt(28)
paragraph.font.bold = True
paragraph.font.color.rgb = RgbColor(0x1A, 0x1A, 0x2E)
# Body formatting
body = slide.placeholders[1]
for paragraph in body.text_frame.paragraphs:
paragraph.font.size = Pt(16)
paragraph.font.color.rgb = RgbColor(0x33, 0x33, 0x33)---
Branded Master Slides (PptxGenJS)
import pptxgen from 'pptxgenjs';
const pptx = new pptxgen();
// Define brand master with logo, header bar, and footer
pptx.defineSlideMaster({
title: 'BRAND_STANDARD',
background: { color: 'FFFFFF' },
objects: [
// Top brand bar
{ rect: { x: 0, y: 0, w: '100%', h: 0.6, fill: { color: '1A1A2E' } } },
// Logo
{ image: { x: 0.3, y: 0.08, w: 1.2, h: 0.44, path: './assets/logo-white.png' } },
// Bottom rule
{ rect: { x: 0, y: 5.35, w: '100%', h: 0.02, fill: { color: '0066CC' } } },
// Footer text
{ text: {
text: 'Confidential | Company Inc.',
options: { x: 0.3, y: 5.4, w: 5, h: 0.25, fontSize: 8, color: '999999' }
}},
// Slide number
{ text: {
text: '{slideNumber}',
options: { x: 9.2, y: 5.4, w: 0.5, h: 0.25, fontSize: 8, color: '999999', align: 'right' }
}},
],
});
// Section divider master
pptx.defineSlideMaster({
title: 'SECTION_DIVIDER',
background: { color: '1A1A2E' },
objects: [
{ image: { x: 0.3, y: 0.3, w: 1.5, h: 0.55, path: './assets/logo-white.png' } },
{ rect: { x: 2, y: 2.7, w: 6, h: 0.03, fill: { color: '0066CC' } } },
],
});
// Use masters
const slide1 = pptx.addSlide({ masterName: 'SECTION_DIVIDER' });
slide1.addText('Market Analysis', { x: 2, y: 2, w: 6, fontSize: 36, color: 'FFFFFF' });
const slide2 = pptx.addSlide({ masterName: 'BRAND_STANDARD' });
slide2.addText('TAM: $12B', { x: 1, y: 1.2, w: 8, fontSize: 24, bold: true });
await pptx.writeFile({ fileName: 'branded.pptx' });---
PPTX-Automizer for Branded Templates
PPTX-Automizer copies slides from branded template files while preserving all master/layout references.
import Automizer from 'pptx-automizer';
const automizer = new Automizer({
templateDir: './templates',
outputDir: './output',
});
const pptx = automizer
.loadRoot('branded-base.pptx') // Contains master slides and theme
.load('data-slides.pptx', 'data'); // Contains content slides
// Slides inherit the branded-base.pptx masters
pptx.addSlide('data', 1, (slide) => {
slide.modifyElement('RevenueChart', {
replaceChart: updatedChartData,
});
slide.modifyElement('QuarterLabel', { text: 'Q4 2025' });
});
pptx.addSlide('data', 2, (slide) => {
slide.modifyElement('MetricsTable', { replaceTable: metricsData });
});
await pptx.write('quarterly_report.pptx');Key advantage: Designers maintain the template in PowerPoint. Engineers inject data with Automizer. Branding stays pixel-perfect.
---
Template Versioning and Distribution
Version Control Strategy
templates/
├── v2.1/
│ ├── brand-standard.pptx ← Current production template
│ ├── brand-standard.potx ← PowerPoint template format
│ └── CHANGELOG.md
├── v2.0/
│ └── brand-standard.pptx ← Previous version (archived)
└── assets/
├── logo-dark.png
├── logo-white.png
└── brand-colors.jsonDistribution Approaches
| Method | Pros | Cons |
|---|---|---|
| Shared drive / cloud folder | Simple, accessible | No version enforcement |
| Git LFS | Versioned, auditable | Requires dev tooling |
| Template server API | Programmatic access, always current | Requires infrastructure |
| PowerPoint custom template path | Native Office integration | Manual installation per machine |
brand-colors.json for Automation
{
"version": "2.1",
"colors": {
"primary": "1A1A2E",
"secondary": "0066CC",
"accent": "FF6600",
"text_dark": "333333",
"text_light": "FFFFFF",
"background": "FFFFFF",
"surface": "F5F5F5"
},
"fonts": {
"heading": "Inter",
"body": "Inter",
"mono": "JetBrains Mono"
}
}---
Multi-Brand Support
When supporting sub-brands or white-label variants:
import json
from pptx import Presentation
from pptx.dml.color import RgbColor
from pptx.util import Pt
def load_brand(brand_name: str) -> dict:
with open(f'brands/{brand_name}/brand-colors.json') as f:
return json.load(f)
def apply_brand(prs: Presentation, brand: dict):
"""Apply brand colors to all slides in the presentation."""
primary = brand['colors']['primary']
for slide in prs.slides:
if slide.shapes.title:
for p in slide.shapes.title.text_frame.paragraphs:
p.font.color.rgb = RgbColor(
int(primary[0:2], 16),
int(primary[2:4], 16),
int(primary[4:6], 16)
)
# Usage
brand = load_brand('subsidiary-a')
prs = Presentation(f'brands/subsidiary-a/template.pptx')
apply_brand(prs, brand)
prs.save('output.pptx')Multi-Brand File Structure
brands/
├── parent-co/
│ ├── template.pptx
│ ├── brand-colors.json
│ └── assets/
│ ├── logo-dark.png
│ └── logo-white.png
├── subsidiary-a/
│ ├── template.pptx ← Inherits parent layout, different colors/logo
│ ├── brand-colors.json
│ └── assets/
└── white-label/
├── template.pptx ← Neutral template, no branding
└── brand-colors.json---
Brand Consistency Checklist
- [ ] Logo placement is identical on every non-title slide (position, size)
- [ ] Color palette uses only defined brand colors (no ad-hoc hex values)
- [ ] Heading font and body font match the brand font scheme
- [ ] Font sizes follow the hierarchy: title (28-36pt), subtitle (18-24pt), body (14-18pt), caption (10-12pt)
- [ ] Footer contains required legal text (confidential, copyright)
- [ ] Slide numbers are present and consistently positioned
- [ ] Chart colors map to the accent palette in the correct order
- [ ] Background color or gradient matches the brand specification
- [ ] No orphaned layouts from previous template versions
- [ ] Template version is documented in the file properties or a hidden slide
---
Common Pitfalls
Layout Index Assumptions
Layout indices vary between templates. Never hardcode layout index numbers.
# BAD — breaks if template changes
layout = prs.slide_layouts[1]
# GOOD — look up by name
def get_layout(prs, name):
for layout in prs.slide_master.slide_layouts:
if layout.name == name:
return layout
raise ValueError(f"Layout '{name}' not found. Available: "
f"{[l.name for l in prs.slide_master.slide_layouts]}")
layout = get_layout(prs, 'Title and Content')Missing Placeholders
Templates may have fewer placeholders than expected. Always check before accessing.
slide = prs.slides.add_slide(layout)
# BAD — KeyError if placeholder 1 does not exist
body = slide.placeholders[1]
# GOOD — safe access
if 1 in slide.placeholders:
body = slide.placeholders[1]
body.text = "Content here"
else:
# Fall back to adding a text box
from pptx.util import Inches
txBox = slide.shapes.add_textbox(Inches(1), Inches(1.5), Inches(8), Inches(4))
txBox.text_frame.text = "Content here"Font Substitution
If the brand font is not installed on the rendering machine, PowerPoint substitutes a default font. This breaks spacing and alignment.
Mitigations:
- Embed fonts in the PPTX (File > Options > Save > Embed fonts)
- Use widely available fonts (Inter, Open Sans, Roboto) as fallbacks
- Test on the target machine before the presentation
- For PDF export, fonts must be present at export time
Theme Corruption
Editing theme1.xml incorrectly can corrupt the file. Always:
1. Back up the original template before XML edits 2. Validate the output opens cleanly in PowerPoint 3. Check that all 12 theme color slots are populated
---
Do / Avoid
Do
- Look up layouts by name, not by index
- Store brand configuration in a JSON file alongside templates
- Version your templates with clear changelogs
- Test generated files in PowerPoint on the target OS
- Use PPTX-Automizer when designers own the template and engineers own the data
- Validate placeholder existence before accessing
- Keep one canonical template per brand, not per-project copies
Avoid
- Hardcoding layout indices (
slide_layouts[1]) - Defining colors as raw hex strings scattered through code (use a palette class or JSON)
- Editing slide master XML without a backup
- Assuming fonts are available on all machines
- Creating new layouts programmatically when a template layout already exists
- Mixing elements from different template versions in one deck
- Ignoring the theme color scheme and using only direct RGB overrides
---
Related Resources
- pptx-layouts.md - Master slides, themes, and backgrounds
- pptx-charts.md - Chart styling with brand colors
- pptx-animations-transitions.md - Transitions and motion
- ../assets/pitch-deck.md - Complete pitch deck template