PowerPoint Generation (pptx)
Overview
Generate .pptx files programmatically using python-pptx. Convert Claude output, data analysis results, research reports, and structured data into polished PowerPoint presentations.
When to Invoke
Skill({ skill: 'pptx' }) when:
- User asks to "create a presentation", "generate slides", or "make a PowerPoint"
- Converting a report, analysis, or structured document into slide format
- Generating recurring report slides from data
- Creating template-based presentations programmatically
Installation
pip install python-pptx
# or
uv add python-pptx
Quick Start
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
# Create presentation
prs = Presentation()
# Set slide dimensions (widescreen 16:9)
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)
# Add title slide using layout 0
title_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(title_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = "Q1 2026 Business Review"
subtitle.text = "Prepared by Claude Agent"
# Add content slide using layout 1 (title + content)
content_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(content_layout)
slide.shapes.title.text = "Key Findings"
tf = slide.placeholders[1].text_frame
tf.text = "Revenue grew 23% YoY"
p = tf.add_paragraph()
p.text = "Customer retention at 94%"
p.level = 1 # Indent level
prs.save("presentation.pptx")
Slide Layouts Reference
Standard layouts available in default template:
| Index | Name | Use For | | ----- | --------------------- | --------------------------- | | 0 | Title Slide | Opening slide | | 1 | Title and Content | Main content slides | | 2 | Title and Two Content | Side-by-side comparison | | 5 | Title Only | Charts or images with title | | 6 | Blank | Custom layouts |
# List all layouts in a template
for i, layout in enumerate(prs.slide_layouts):
print(f"{i}: {layout.name}")
Text Formatting
from pptx.util import Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
def format_paragraph(paragraph, text, size=18, bold=False, color=None, align=PP_ALIGN.LEFT):
run = paragraph.add_run()
run.text = text
font = run.font
font.size = Pt(size)
font.bold = bold
if color:
font.color.rgb = RGBColor(*color) # e.g., (0x1F, 0x49, 0x7D) for dark blue
paragraph.alignment = align
return run
# Usage
tf = slide.placeholders[1].text_frame
tf.clear() # Clear existing content
p = tf.paragraphs[0]
format_paragraph(p, "Important Metric: 94%", size=24, bold=True, color=(0x1F, 0x49, 0x7D))
Tables
from pptx.util import Inches
def add_table_slide(prs, title, headers, rows):
"""Add a slide with a formatted table."""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.shapes.title.text = title
# Position: left, top, width, height
left = Inches(0.5)
top = Inches(1.5)
width = Inches(12.3)
height = Inches(0.8 * (len(rows) + 1))
table = slide.shapes.add_table(
len(rows) + 1, len(headers), left, top, width, height
).table
# Header row
for col_idx, header in enumerate(headers):
cell = table.cell(0, col_idx)
cell.text = header
cell.text_frame.paragraphs[0].runs[0].font.bold = True
# Data rows
for row_idx, row in enumerate(rows):
for col_idx, value in enumerate(row):
table.cell(row_idx + 1, col_idx).text = str(value)
return slide
# Usage
headers = ["Product", "Revenue", "Growth"]
rows = [
("Widget A", "$1.2M", "+23%"),
("Widget B", "$890K", "+15%"),
("Widget C", "$450K", "-5%"),
]
add_table_slide(prs, "Revenue by Product", headers, rows)
Charts
from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches
def add_bar_chart(slide, title, categories, series_name, values):
"""Add a bar chart to a slide."""
chart_data = ChartData()
chart_data.categories = categories
chart_data.add_series(series_name, values)
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(1), Inches(1.5), # left, top
Inches(11), Inches(5.5), # width, height
chart_data
).chart
chart.has_title = True
chart.chart_title.text_frame.text = title
chart.has_legend = False
return chart
# Usage
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.title.text = "Monthly Revenue"
add_bar_chart(
slide,
"Revenue by Month (2026)",
["Jan", "Feb", "Mar"],
"Revenue ($K)",
(450, 520, 610)
)
Images
from pptx.util import Inches
def add_image_slide(prs, title, image_path, caption=None):
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.title.text = title
# Center image on slide
pic = slide.shapes.add_picture(
image_path,
left=Inches(1.5),
top=Inches(1.5),
width=Inches(10),
)
if caption:
txBox = slide.shapes.add_textbox(
Inches(1.5), Inches(6.8), Inches(10), Inches(0.5)
)
tf = txBox.text_frame
tf.text = caption
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
return slide
Agent Workflow: Claude Output → Slides
import json
from pptx import Presentation
from pptx.util import Inches, Pt
def claude_output_to_slides(slide_data: list[dict], output_path: str):
"""
Convert Claude structured output to a PowerPoint file.
Expected slide_data format:
[
{"type": "title", "title": "...", "subtitle": "..."},
{"type": "content", "title": "...", "bullets": ["...", "..."]},
{"type": "table", "title": "...", "headers": [...], "rows": [[...], ...]},
]
"""
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)
for slide_spec in slide_data:
if slide_spec["type"] == "title":
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = slide_spec["title"]
if "subtitle" in slide_spec:
slide.placeholders[1].text = slide_spec["subtitle"]
elif slide_spec["type"] == "content":
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = slide_spec["title"]
tf = slide.placeholders[1].text_frame
tf.text = slide_spec["bullets"][0]
for bullet in slide_spec["bullets"][1:]:
p = tf.add_paragraph()
p.text = bullet
p.level = 1
elif slide_spec["type"] == "table":
add_table_slide(
prs,
slide_spec["title"],
slide_spec["headers"],
slide_spec["rows"]
)
prs.save(output_path)
return output_path
# Example: Generate from Claude JSON output
slide_data = [
{"type": "title", "title": "Q1 2026 Report", "subtitle": "Generated by Claude"},
{"type": "content", "title": "Key Highlights", "bullets": [
"Revenue up 23% YoY",
"New customer acquisition: +450",
"NPS score improved to 72",
]},
{"type": "table", "title": "Regional Performance",
"headers": ["Region", "Revenue", "Target", "Variance"],
"rows": [["North", "$2.1M", "$2.0M", "+5%"], ["South", "$1.8M", "$2.0M", "-10%"]]},
]
claude_output_to_slides(slide_data, "q1-report.pptx")
Using Existing Templates
# Load existing branded template
prs = Presentation("company-template.pptx")
# Add slide using template's layouts
# Template layouts preserve branding (fonts, colors, logos)
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = "New Content Slide"
Output Location
Save generated .pptx files to:
- User-specified path, or
.claude/context/artifacts/for agent-generated presentations
Anti-Patterns
- Never use raw integer values for sizes — always
Pt(),Inches(), orEmu() - Never add text directly to slide (use placeholder shapes for layout consistency)
- Never hardcode colors without using
RGBColor— inconsistent on dark/light themes - Never forget
tf.clear()before setting text on a reused placeholder - Never use
slide_layouts[6](blank) for content — placeholder positions will be missing
Related Skills
xlsx— Excel spreadsheet generation (tabular data export)diagram-generator— Mermaid diagrams (for embedding architecture diagrams)markitdown-converter— Convert existing files to Markdown for Claude processing
Memory Protocol (MANDATORY)
Before starting: Read .claude/context/memory/learnings.md
After completing: Record any python-pptx version compatibility issues or slide layout gotchas.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.