python-pptx creates and modifies PowerPoint .pptx files. pip install python-pptx. New: from pptx import Presentation; prs = Presentation(). Open: prs = Presentation("template.pptx"). Slide layout: layout = prs.slide_layouts[1]. Add slide: slide = prs.slides.add_slide(layout). Title: slide.shapes.title.text = "Title". Body: slide.placeholders[1].text = "Content". Text box: from pptx.util import Inches, Pt; txBox = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(4), Inches(1)). Run: tf = txBox.text_frame; p = tf.paragraphs[0]; run = p.add_run(); run.text = "Hello"; run.font.size = Pt(18). Bold: run.font.bold = True. Color: from pptx.dml.color import RGBColor; run.font.color.rgb = RGBColor(0,0,0xFF). Picture: slide.shapes.add_picture("img.png", Inches(1), Inches(1), Inches(3)). Shape: from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE; slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE, Inches(1),Inches(1),Inches(3),Inches(1)). Table: table = slide.shapes.add_table(3,4,Inches(1),Inches(2),Inches(6),Inches(2)).table. Cell: table.cell(0,0).text = "Header". Chart: from pptx.chart.data import ChartData; from pptx.enum.chart import XL_CHART_TYPE; cd = ChartData(); cd.categories=["A","B"]; cd.add_series("S1",[1,2]). Slide size: prs.slide_width = Inches(13.33). Save: prs.save("deck.pptx"). BytesIO: from io import BytesIO; buf=BytesIO(); prs.save(buf). Claude Code generates python-pptx branded decks, data-driven chart slides, and automated business presentations.
CLAUDE.md for python-pptx
## python-pptx Stack
- Version: python-pptx >= 0.6 | pip install python-pptx
- Create: prs = Presentation() | prs = Presentation("template.pptx")
- Slide: slide = prs.slides.add_slide(prs.slide_layouts[N])
- Text: slide.shapes.title.text = "..." | placeholders[1].text = "..."
- Shape/image: slide.shapes.add_picture(path, x, y, w, h)
- Chart: ChartData() → add_series → slide.shapes.add_chart(XL_CHART_TYPE, pos, size, chart_data)
- Save: prs.save("file.pptx") | prs.save(BytesIO())
python-pptx Presentation Generation Pipeline
# app/pptx_gen.py — python-pptx slides, charts, tables, formatting, and report generator
from __future__ import annotations
import io
from dataclasses import dataclass, field
from datetime import date
from pathlib import Path
from typing import Any
from pptx import Presentation
from pptx.chart.data import CategoryChartData, ChartData
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_CHART_TYPE
from pptx.enum.text import PP_ALIGN
from pptx.util import Emu, Inches, Pt
# ─────────────────────────────────────────────────────────────────────────────
# 1. Theme / palette
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Theme:
primary: RGBColor = field(default_factory=lambda: RGBColor(0x1F, 0x49, 0x7D))
secondary: RGBColor = field(default_factory=lambda: RGBColor(0x70, 0xAD, 0x47))
accent: RGBColor = field(default_factory=lambda: RGBColor(0xED, 0x7D, 0x31))
white: RGBColor = field(default_factory=lambda: RGBColor(0xFF, 0xFF, 0xFF))
dark_text: RGBColor = field(default_factory=lambda: RGBColor(0x26, 0x26, 0x26))
light_bg: RGBColor = field(default_factory=lambda: RGBColor(0xDC, 0xE6, 0xF1))
heading_font: str = "Calibri"
body_font: str = "Calibri"
DEFAULT_THEME = Theme()
# ─────────────────────────────────────────────────────────────────────────────
# 2. Low-level helpers
# ─────────────────────────────────────────────────────────────────────────────
def add_text_box(
slide,
text: str,
left: float,
top: float,
width: float,
height: float,
font_size_pt: float = 14,
bold: bool = False,
italic: bool = False,
color: RGBColor | None = None,
alignment: str = "left",
word_wrap: bool = True,
font_name: str = "Calibri",
) -> Any:
"""Add a text box to a slide (dimensions in inches)."""
txBox = slide.shapes.add_textbox(
Inches(left), Inches(top), Inches(width), Inches(height)
)
tf = txBox.text_frame
tf.word_wrap = word_wrap
p = tf.paragraphs[0]
align_map = {
"left": PP_ALIGN.LEFT,
"center": PP_ALIGN.CENTER,
"right": PP_ALIGN.RIGHT,
"justify": PP_ALIGN.JUSTIFY,
}
p.alignment = align_map.get(alignment, PP_ALIGN.LEFT)
run = p.add_run()
run.text = text
run.font.size = Pt(font_size_pt)
run.font.bold = bold
run.font.italic = italic
run.font.name = font_name
if color:
run.font.color.rgb = color
return txBox
def fill_shape(shape, fill_color: RGBColor) -> None:
"""Set a shape's solid fill color."""
shape.fill.solid()
shape.fill.fore_color.rgb = fill_color
def set_cell_text(
cell,
text: str,
bold: bool = False,
font_size_pt: float = 11,
color: RGBColor | None = None,
bg_color: RGBColor | None = None,
alignment: str = "left",
) -> None:
"""Set text and formatting for a table cell."""
cell.text = text
tf = cell.text_frame
align_map = {"left": PP_ALIGN.LEFT, "center": PP_ALIGN.CENTER, "right": PP_ALIGN.RIGHT}
for para in tf.paragraphs:
para.alignment = align_map.get(alignment, PP_ALIGN.LEFT)
for run in para.runs:
run.font.size = Pt(font_size_pt)
run.font.bold = bold
if color:
run.font.color.rgb = color
if bg_color:
fill = cell.fill
fill.solid()
fill.fore_color.rgb = bg_color
# ─────────────────────────────────────────────────────────────────────────────
# 3. Slide builders
# ─────────────────────────────────────────────────────────────────────────────
def add_title_slide(
prs: Presentation,
title: str,
subtitle: str = "",
company: str = "",
date_str: str | None = None,
theme: Theme = DEFAULT_THEME,
) -> Any:
"""
Add a title slide (layout 0).
Returns the slide object.
"""
layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(layout)
if slide.shapes.title:
slide.shapes.title.text = title
slide.shapes.title.text_frame.paragraphs[0].runs[0].font.color.rgb = theme.white
if len(slide.placeholders) > 1:
ph = slide.placeholders[1]
ph.text = subtitle or (f"{company} · {date_str or date.today().strftime('%B %Y')}")
return slide
def add_content_slide(
prs: Presentation,
title: str,
bullets: list[str] | None = None,
theme: Theme = DEFAULT_THEME,
) -> Any:
"""
Add a content slide with title and bullet points (layout 1).
Returns the slide object.
"""
layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(layout)
if slide.shapes.title:
slide.shapes.title.text = title
if bullets and len(slide.placeholders) > 1:
body_ph = slide.placeholders[1]
tf = body_ph.text_frame
for i, bullet in enumerate(bullets):
if i == 0:
tf.paragraphs[0].text = bullet
else:
p = tf.add_paragraph()
p.text = bullet
p.level = 0
return slide
def add_data_table_slide(
prs: Presentation,
title: str,
headers: list[str],
rows: list[list[Any]],
left: float = 0.5,
top: float = 1.5,
width: float = 9.0,
theme: Theme = DEFAULT_THEME,
) -> Any:
"""
Add a slide with a data table.
Returns the slide object.
"""
layout = prs.slide_layouts[5] # blank
slide = prs.slides.add_slide(layout)
add_text_box(slide, title, 0.3, 0.1, 9.3, 0.8,
font_size_pt=24, bold=True, color=theme.primary)
n_cols = len(headers)
n_rows = 1 + len(rows)
col_w = width / n_cols
row_h = min(0.4, (7.0 - top) / n_rows)
table_shape = slide.shapes.add_table(
n_rows, n_cols, Inches(left), Inches(top),
Inches(width), Inches(row_h * n_rows),
)
table = table_shape.table
# Set uniform column widths
for i in range(n_cols):
table.columns[i].width = Inches(col_w)
# Headers
for ci, header in enumerate(headers):
set_cell_text(table.cell(0, ci), str(header),
bold=True, font_size_pt=11,
color=theme.white, bg_color=theme.primary,
alignment="center")
# Data rows
for ri, row in enumerate(rows):
bg = theme.light_bg if ri % 2 == 1 else None
for ci, val in enumerate(row):
set_cell_text(table.cell(1 + ri, ci), str(val),
font_size_pt=10, bg_color=bg)
return slide
def add_chart_slide(
prs: Presentation,
title: str,
categories: list[str],
series: dict[str, list[float]],
chart_type: XL_CHART_TYPE = XL_CHART_TYPE.COLUMN_CLUSTERED,
left: float = 0.5,
top: float = 1.3,
width: float = 9.0,
height: float = 5.5,
theme: Theme = DEFAULT_THEME,
) -> Any:
"""
Add a slide with a chart.
series: {"Series Name": [val1, val2, ...], ...}
Example:
add_chart_slide(prs, "Q1 Revenue by Region",
categories=["North", "South", "East", "West"],
series={"2023": [120, 90, 150, 80], "2024": [145, 105, 165, 95]})
"""
layout = prs.slide_layouts[5] # blank
slide = prs.slides.add_slide(layout)
add_text_box(slide, title, 0.3, 0.1, 9.3, 0.8,
font_size_pt=24, bold=True, color=theme.primary)
chart_data = CategoryChartData()
chart_data.categories = categories
for name, vals in series.items():
chart_data.add_series(name, vals)
slide.shapes.add_chart(
chart_type,
Inches(left), Inches(top), Inches(width), Inches(height),
chart_data,
)
return slide
def add_kpi_slide(
prs: Presentation,
title: str,
kpis: list[dict],
theme: Theme = DEFAULT_THEME,
) -> Any:
"""
Add a KPI / metrics slide with colored tiles.
kpis: list of {"label": str, "value": str, "delta": str (optional), "up": bool}
Example:
add_kpi_slide(prs, "Key Metrics", [
{"label": "Revenue", "value": "$1.2M", "delta": "+20%", "up": True},
{"label": "Churn", "value": "3.8%", "delta": "-1.2pp", "up": True},
{"label": "NPS", "value": "52", "delta": "+7", "up": True},
])
"""
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
layout = prs.slide_layouts[5]
slide = prs.slides.add_slide(layout)
add_text_box(slide, title, 0.3, 0.1, 9.3, 0.6,
font_size_pt=24, bold=True, color=theme.primary)
n = len(kpis)
w = min(2.8, 9.5 / n)
gap = (10.0 - n * w) / (n + 1)
top = 1.2
height = 4.5
for i, kpi in enumerate(kpis):
left = gap + i * (w + gap)
color = theme.secondary if kpi.get("up", True) else theme.accent
box = slide.shapes.add_shape(
MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE,
Inches(left), Inches(top), Inches(w), Inches(height),
)
fill_shape(box, color)
box.line.fill.background()
add_text_box(slide, kpi.get("label", ""), left + 0.1, top + 0.3,
w - 0.2, 0.5, font_size_pt=13, color=theme.white,
alignment="center")
add_text_box(slide, kpi.get("value", ""), left + 0.1, top + 1.0,
w - 0.2, 1.2, font_size_pt=32, bold=True,
color=theme.white, alignment="center")
if "delta" in kpi:
add_text_box(slide, kpi["delta"], left + 0.1, top + 2.5,
w - 0.2, 0.6, font_size_pt=16, color=theme.white,
alignment="center")
return slide
# ─────────────────────────────────────────────────────────────────────────────
# 4. Report generator
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class DeckSlide:
slide_type: str # "title" | "bullets" | "table" | "chart" | "kpi"
title: str
# type-specific fields:
subtitle: str = ""
bullets: list[str] = field(default_factory=list)
headers: list[str] = field(default_factory=list)
rows: list[list[Any]] = field(default_factory=list)
categories: list[str] = field(default_factory=list)
series: dict[str, list[float]] = field(default_factory=dict)
kpis: list[dict] = field(default_factory=list)
def generate_presentation(
slides: list[DeckSlide],
company: str = "My Company",
template_path: str | Path | None = None,
theme: Theme | None = None,
slide_width_inches: float = 13.33,
slide_height_inches: float = 7.5,
) -> bytes:
"""
Generate a .pptx presentation from a slide list.
Returns bytes.
Example:
deck = [
DeckSlide("title", "Q1 2024 Review", subtitle="Board Presentation"),
DeckSlide("kpi", "Key Metrics", kpis=[
{"label": "Revenue", "value": "$1.2M", "delta": "+20%", "up": True},
]),
DeckSlide("table", "Sales by Region",
headers=["Region", "Q1", "Q4", "YoY"],
rows=[["North", "$420K", "$350K", "+20%"]]),
DeckSlide("chart", "Revenue Trend",
categories=["Jan", "Feb", "Mar"],
series={"2024": [380, 410, 450]}),
]
pptx_bytes = generate_presentation(deck, company="Acme Corp")
"""
t = theme or DEFAULT_THEME
if template_path and Path(template_path).exists():
prs = Presentation(str(template_path))
else:
prs = Presentation()
prs.slide_width = Inches(slide_width_inches)
prs.slide_height = Inches(slide_height_inches)
for ds in slides:
if ds.slide_type == "title":
add_title_slide(prs, ds.title, ds.subtitle, company, theme=t)
elif ds.slide_type == "bullets":
add_content_slide(prs, ds.title, ds.bullets, theme=t)
elif ds.slide_type == "table":
add_data_table_slide(prs, ds.title, ds.headers, ds.rows, theme=t)
elif ds.slide_type == "chart":
add_chart_slide(prs, ds.title, ds.categories, ds.series, theme=t)
elif ds.slide_type == "kpi":
add_kpi_slide(prs, ds.title, ds.kpis, theme=t)
buf = io.BytesIO()
prs.save(buf)
return buf.getvalue()
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
slides = [
DeckSlide("title", "Q1 2024 Business Review",
subtitle="Confidential · Board Presentation"),
DeckSlide("kpi", "Executive Metrics", kpis=[
{"label": "Revenue", "value": "$1.2M", "delta": "+20%", "up": True},
{"label": "Churn", "value": "3.8%", "delta": "-1.2pp","up": True},
{"label": "NPS", "value": "52", "delta": "+7", "up": True},
{"label": "CAC", "value": "$124", "delta": "-$18", "up": True},
]),
DeckSlide("chart", "Monthly Revenue (Q1 2024)",
categories=["January", "February", "March"],
series={"2023": [380, 390, 410], "2024": [420, 445, 480]}),
DeckSlide("table", "Top Accounts by Revenue",
headers=["Account", "Tier", "Q1 Revenue", "Growth"],
rows=[
["Acme Corp", "Enterprise", "$180K", "+22%"],
["Globex Inc", "Enterprise", "$140K", "+18%"],
["Initech", "Mid-Market", "$85K", "+31%"],
["Umbrella Co", "Mid-Market", "$74K", "+8%"],
]),
DeckSlide("bullets", "Q2 Priorities", bullets=[
"Launch API v2 — targeting April 15",
"Expand to EU market — compliance review complete",
"Mobile app redesign — beta users provide 4.6/5 rating",
"Hire 3 senior engineers per open headcount plan",
]),
]
pptx_bytes = generate_presentation(slides, company="Acme Corp")
Path("/tmp/q1_review.pptx").write_bytes(pptx_bytes)
print(f"Generated /tmp/q1_review.pptx ({len(pptx_bytes):,} bytes, {len(slides)} slides)")
For the reportlab / WeasyPrint alternative — reportlab and WeasyPrint produce PDFs; python-pptx produces editable .pptx files where presenters can add speaker notes, animate objects, and modify slides before presenting — use python-pptx when the output is a slide deck for presenting, use PDF generators when you need a fixed-layout printable document. For the python-docx alternative — python-docx targets flowing Word documents (reports, invoices, letters with paragraphs and headings); python-pptx targets slide decks with positioned shapes, charts, and visual KPI tiles — use python-docx for text-heavy reports, python-pptx for executive presentations and dashboards that will be presented in slide show mode. The Claude Skills 360 bundle includes python-pptx skill sets covering Theme dataclass with branded colors, add_text_box() with font/color/alignment, fill_shape() solid fill, set_cell_text() with background color, add_title_slide()/add_content_slide() with layout access, add_data_table_slide() with header shading and alternating rows, add_chart_slide() CategoryChartData with multiple series, add_kpi_slide() colored metric tiles, DeckSlide dataclass and generate_presentation() report builder, and BytesIO in-memory output. Start with the free tier to try PowerPoint presentation generation code.