PPTX Generation
Structured guidance for building systems that generate professional PowerPoint presentations programmatically. Covers library selection, slide layout design, chart integration, master slide management, template-based generation, batch processing, and quality assurance strategies for automated presentation pipelines.
When to Use This Skill
Use this skill for:
- Building automated presentation generators from structured data
- Creating report decks (financial summaries, analytics dashboards, project status updates)
- Designing reusable slide templates with consistent corporate branding
- Adding charts, tables, and data visualizations to slides programmatically
- Populating existing PowerPoint templates with dynamic content
- Batch-generating personalized slide decks from datasets (mail merge pattern)
- Integrating presentation generation into CI/CD pipelines or reporting workflows
- Converting Markdown, JSON, or database records into formatted slide decks
Trigger phrases: "pptx", "PowerPoint generation", "slide deck", "presentation builder", "python-pptx", "PptxGenJS", "slide template", "chart slides", "automated reports", "batch presentations", "slide layouts", "master slides", "branding deck", "report generator", "slide automation"
What This Skill Does
Provides presentation generation patterns including:
- Library Selection: Decision matrix for python-pptx, PptxGenJS, Apache POI, and LibreOffice approaches
- Slide Design: Layout patterns for title, content, two-column, section divider, and closing slides
- Charts and Data: Bar, line, pie, scatter, and combo charts with data-driven generation
- Master Slides: Theme management, color schemes, font families, and brand consistency
- Templates: Loading existing .pptx templates, populating placeholders, and extending layouts
- Batch Generation: Mail merge patterns, data-driven deck creation, and parallel processing
- Quality Assurance: Slide count verification, content extraction, visual validation, and file size optimization
Instructions
Step 1: Library Selection
Choose a PPTX generation library based on your runtime environment, feature requirements, and integration constraints. The following decision matrix compares the four primary options.
Library Comparison Matrix:
| Criteria | python-pptx | PptxGenJS | Apache POI | LibreOffice (CLI) |
|---|---|---|---|---|
| Language | Python | JavaScript/TypeScript | Java/Kotlin | Any (CLI wrapper) |
| License | MIT | MIT | Apache 2.0 | MPL 2.0 |
| Template support | Full (load/modify .pptx) | Limited (no template loading) | Full (load/modify .pptx) | Full (via UNO API) |
| Chart support | Native (OOXML charts) | Built-in chart types | Native (OOXML charts) | Full (via template) |
| Table support | Full with merged cells | Full with styling | Full with merged cells | Full |
| Image support | PNG, JPEG, SVG (via EMF) | PNG, JPEG, SVG, GIF | PNG, JPEG, EMF, WMF | All formats |
| Master slides | Read and modify | Create from scratch only | Read and modify | Read and modify |
| File size | Small (efficient XML) | Small | Medium (Java overhead) | Depends on conversion |
| Dependencies | Pure Python | Zero dependencies (browser/Node) | JVM required | LibreOffice installation |
| Maturity | Stable, widely used | Active, growing | Very mature | Very mature |
| Best for | Backend report generation | Browser/Node slide builders | Enterprise Java stacks | Converting other formats |
Decision Guide:
- Choose python-pptx when you have a Python backend, need to load and modify existing templates, require native OOXML chart support, or are building data pipeline report generators
- Choose PptxGenJS when you need browser-side generation, are building a Node.js service, want zero-dependency simplicity, or need to generate slides from a web application
- Choose Apache POI when you are in a Java/Kotlin ecosystem, need enterprise-grade OOXML manipulation, or must integrate with existing Java reporting infrastructure
- Choose LibreOffice CLI when you need to convert other formats (HTML, Markdown, ODP) to PPTX, require a headless server-side converter, or need PDF export from slides
Installation:
# python-pptx (Python)
pip install python-pptx
# or with uv
uv pip install python-pptx
# PptxGenJS (Node.js)
npm install pptxgenjs
# or browser via CDN
# <script src="https://cdn.jsdelivr.net/npm/pptxgenjs/dist/pptxgenjs.bundle.js"></script>
# Apache POI (Maven)
# <dependency>
# <groupId>org.apache.poi</groupId>
# <artifactId>poi-ooxml</artifactId>
# <version>5.2.5</version>
# </dependency>
# LibreOffice CLI (system package)
# apt install libreoffice-impress # Debian/Ubuntu
# brew install --cask libreoffice # macOS
Step 2: Python python-pptx Fundamentals
python-pptx is the most popular Python library for creating and modifying PowerPoint files. It provides full access to the OOXML presentation model including slides, layouts, placeholders, shapes, text frames, tables, charts, and images.
Core Object Model:
Presentation
-> SlideMasters[]
-> SlideLayouts[]
-> Slides[]
-> Shapes[]
-> TextFrame -> Paragraphs[] -> Runs[]
-> Table -> Rows[] -> Cells[]
-> Chart -> ChartData
-> Picture
Creating a Presentation from Scratch:
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.dml.color import RGBColor
def create_presentation() -> Presentation:
"""Create a new presentation with standard 16:9 dimensions."""
prs = Presentation()
# Set slide dimensions to 16:9 (13.333 x 7.5 inches)
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
return prs
Understanding Slide Layouts:
Every presentation has a slide master that contains slide layouts. The default template provides these standard layouts:
def list_available_layouts(prs: Presentation) -> list[dict]:
"""List all available slide layouts from the slide master."""
layouts = []
for idx, layout in enumerate(prs.slide_masters[0].slide_layouts):
layouts.append({
"index": idx,
"name": layout.name,
"placeholders": [
{"idx": ph.placeholder_format.idx, "name": ph.name, "type": ph.placeholder_format.type}
for ph in layout.placeholders
],
})
return layouts
# Standard layout indices (default template):
# 0 = Title Slide (title + subtitle)
# 1 = Title and Content (title + body)
# 2 = Section Header
# 3 = Two Content (title + two body columns)
# 4 = Comparison (title + two columns with subtitles)
# 5 = Title Only
# 6 = Blank
# 7 = Content with Caption
# 8 = Picture with Caption
Adding Slides with Text:
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
def add_title_slide(
prs: Presentation,
title: str,
subtitle: str,
) -> None:
"""Add a title slide (layout index 0) with title and subtitle."""
slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(slide_layout)
slide.placeholders[0].text = title
slide.placeholders[1].text = subtitle
def add_content_slide(
prs: Presentation,
title: str,
body_text: str,
bullet_points: list[str] | None = None,
) -> None:
"""Add a title-and-content slide (layout index 1) with formatted text."""
slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(slide_layout)
# Set title
slide.placeholders[0].text = title
# Set body content
text_frame = slide.placeholders[1].text_frame
text_frame.clear()
if body_text:
paragraph = text_frame.paragraphs[0]
paragraph.text = body_text
paragraph.font.size = Pt(18)
paragraph.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
if bullet_points:
for point in bullet_points:
paragraph = text_frame.add_paragraph()
paragraph.text = point
paragraph.level = 0
paragraph.font.size = Pt(16)
paragraph.space_after = Pt(6)
Working with Text Frames and Runs:
from pptx.util import Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
def add_formatted_text(
prs: Presentation,
title: str,
content_blocks: list[dict],
) -> None:
"""Add a slide with rich text formatting using runs.
Each content_block: {"text": str, "bold": bool, "size": int, "color": str}
"""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only layout
slide.placeholders[0].text = title
# Add a text box for custom-positioned content
from pptx.util import Inches
left = Inches(1.0)
top = Inches(2.0)
width = Inches(11.0)
height = Inches(4.5)
text_box = slide.shapes.add_textbox(left, top, width, height)
text_frame = text_box.text_frame
text_frame.word_wrap = True
for idx, block in enumerate(content_blocks):
if idx == 0:
paragraph = text_frame.paragraphs[0]
else:
paragraph = text_frame.add_paragraph()
run = paragraph.add_run()
run.text = block["text"]
run.font.size = Pt(block.get("size", 14))
run.font.bold = block.get("bold", False)
run.font.italic = block.get("italic", False)
color_hex = block.get("color", "333333")
run.font.color.rgb = RGBColor.from_string(color_hex)
# Set paragraph alignment
for paragraph in text_frame.paragraphs:
paragraph.alignment = PP_ALIGN.LEFT
Adding Images:
from pptx.util import Inches
def add_image_slide(
prs: Presentation,
title: str,
image_path: str,
left: float = 2.0,
top: float = 2.0,
width: float = 9.0,
) -> None:
"""Add a slide with a positioned image.
The height is calculated automatically to maintain aspect ratio.
"""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.placeholders[0].text = title
slide.shapes.add_picture(
image_path,
left=Inches(left),
top=Inches(top),
width=Inches(width),
# height omitted: auto-calculated from aspect ratio
)
Adding Tables:
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
def add_table_slide(
prs: Presentation,
title: str,
headers: list[str],
rows: list[list[str]],
) -> None:
"""Add a slide with a formatted data table."""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.placeholders[0].text = title
row_count = len(rows) + 1 # +1 for header
col_count = len(headers)
left = Inches(1.0)
top = Inches(2.0)
width = Inches(11.0)
height = Inches(0.5 * row_count)
table_shape = slide.shapes.add_table(
row_count, col_count, left, top, width, height,
)
table = table_shape.table
# Style header row
for col_idx, header in enumerate(headers):
cell = table.cell(0, col_idx)
cell.text = header
for paragraph in cell.text_frame.paragraphs:
paragraph.font.bold = True
paragraph.font.size = Pt(12)
paragraph.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
paragraph.alignment = PP_ALIGN.CENTER
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(0x2E, 0x4A, 0x7A)
# Populate data rows
for row_idx, row_data in enumerate(rows):
for col_idx, cell_value in enumerate(row_data):
cell = table.cell(row_idx + 1, col_idx)
cell.text = str(cell_value)
for paragraph in cell.text_frame.paragraphs:
paragraph.font.size = Pt(11)
paragraph.alignment = PP_ALIGN.LEFT
# Set column widths proportionally
total_width = Inches(11.0)
col_width = int(total_width / col_count)
for col_idx in range(col_count):
table.columns[col_idx].width = col_width
Saving the Presentation:
from pathlib import Path
def save_presentation(prs: Presentation, output_path: str | Path) -> Path:
"""Save presentation to disk and return the resolved path."""
output = Path(output_path).resolve()
output.parent.mkdir(parents=True, exist_ok=True)
prs.save(str(output))
return output
Step 3: JavaScript PptxGenJS
PptxGenJS is a zero-dependency JavaScript library that generates PPTX files in both browser and Node.js environments. It provides a fluent API for creating slides with text, images, charts, tables, and shapes.
Creating a Presentation (Node.js):
import PptxGenJS from "pptxgenjs";
interface SlideTheme {
primaryColor: string;
secondaryColor: string;
fontFamily: string;
titleSize: number;
bodySize: number;
}
const DEFAULT_THEME: SlideTheme = {
primaryColor: "2E4A7A",
secondaryColor: "5B8DEF",
fontFamily: "Segoe UI",
titleSize: 28,
bodySize: 14,
};
function createPresentation(theme: SlideTheme = DEFAULT_THEME): PptxGenJS {
const pptx = new PptxGenJS();
// Set presentation metadata
pptx.author = "Automated Report Generator";
pptx.company = "Your Company";
pptx.subject = "Generated Presentation";
// Set 16:9 layout
pptx.layout = "LAYOUT_16x9";
// Define reusable master slides
pptx.defineSlideMaster({
title: "TITLE_SLIDE",
background: { color: theme.primaryColor },
objects: [
{
placeholder: {
options: {
name: "title",
type: "title",
x: 1.0,
y: 2.5,
w: 11.0,
h: 1.5,
fontFace: theme.fontFamily,
fontSize: 36,
color: "FFFFFF",
align: "center",
},
text: "",
},
},
{
placeholder: {
options: {
name: "subtitle",
type: "body",
x: 2.0,
y: 4.2,
w: 9.0,
h: 1.0,
fontFace: theme.fontFamily,
fontSize: 18,
color: "CCCCCC",
align: "center",
},
text: "",
},
},
],
});
pptx.defineSlideMaster({
title: "CONTENT_SLIDE",
background: { color: "FFFFFF" },
objects: [
{
rect: {
x: 0,
y: 0,
w: "100%",
h: 0.75,
fill: { color: theme.primaryColor },
},
},
{
placeholder: {
options: {
name: "title",
type: "title",
x: 0.5,
y: 0.1,
w: 12.0,
h: 0.55,
fontFace: theme.fontFamily,
fontSize: 22,
color: "FFFFFF",
bold: true,
},
text: "",
},
},
],
});
return pptx;
}
Adding Slides with Content:
function addTitleSlide(
pptx: PptxGenJS,
title: string,
subtitle: string,
): void {
const slide = pptx.addSlide({ masterName: "TITLE_SLIDE" });
slide.addText(title, {
placeholder: "title",
});
slide.addText(subtitle, {
placeholder: "subtitle",
});
}
function addContentSlide(
pptx: PptxGenJS,
title: string,
bulletPoints: string[],
theme: SlideTheme = DEFAULT_THEME,
): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
const textRows = bulletPoints.map((point) => ({
text: point,
options: {
fontSize: theme.bodySize,
fontFace: theme.fontFamily,
color: "333333",
bullet: { type: "bullet" as const },
paraSpaceAfter: 6,
},
}));
slide.addText(textRows, {
x: 0.75,
y: 1.2,
w: 11.5,
h: 5.5,
valign: "top",
});
}
Adding Tables:
interface TableConfig {
headers: string[];
rows: string[][];
theme?: SlideTheme;
}
function addTableSlide(
pptx: PptxGenJS,
title: string,
config: TableConfig,
): void {
const theme = config.theme ?? DEFAULT_THEME;
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
const headerRow: PptxGenJS.TableCell[] = config.headers.map((h) => ({
text: h,
options: {
bold: true,
color: "FFFFFF",
fill: { color: theme.primaryColor },
fontSize: 11,
align: "center" as const,
},
}));
const dataRows: PptxGenJS.TableCell[][] = config.rows.map((row) =>
row.map((cell) => ({
text: cell,
options: {
fontSize: 10,
color: "333333",
border: { type: "solid", pt: 0.5, color: "CCCCCC" },
},
})),
);
slide.addTable([headerRow, ...dataRows], {
x: 0.5,
y: 1.2,
w: 12.0,
colW: config.headers.map(() => 12.0 / config.headers.length),
rowH: 0.4,
autoPage: true,
autoPageRepeatHeader: true,
});
}
Adding Images and Shapes:
import * as fs from "node:fs";
import * as path from "node:path";
function addImageSlide(
pptx: PptxGenJS,
title: string,
imagePath: string,
): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
// Read image as base64 for Node.js
const imageBuffer = fs.readFileSync(imagePath);
const base64 = imageBuffer.toString("base64");
const ext = path.extname(imagePath).slice(1).toLowerCase();
slide.addImage({
data: `image/${ext};base64,${base64}`,
x: 2.0,
y: 1.5,
w: 9.0,
h: 5.0,
sizing: { type: "contain", w: 9.0, h: 5.0 },
});
}
function addShapeSlide(pptx: PptxGenJS, title: string): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
// Add a rounded rectangle with text
slide.addShape(pptx.ShapeType.roundRect, {
x: 3.0,
y: 2.0,
w: 7.0,
h: 3.0,
fill: { color: "E8F0FE" },
line: { color: "2E4A7A", width: 2 },
rectRadius: 0.2,
});
slide.addText("Key Insight", {
x: 3.5,
y: 3.0,
w: 6.0,
h: 1.0,
fontSize: 24,
color: "2E4A7A",
align: "center",
bold: true,
});
}
Saving (Node.js and Browser):
// Node.js: save to file
async function saveToFile(
pptx: PptxGenJS,
outputPath: string,
): Promise<string> {
await pptx.writeFile({ fileName: outputPath });
return outputPath;
}
// Browser: trigger download
async function downloadInBrowser(
pptx: PptxGenJS,
fileName: string,
): Promise<void> {
await pptx.writeFile({ fileName });
// PptxGenJS handles the browser download automatically
}
// Get as base64 (for API responses)
async function toBase64(pptx: PptxGenJS): Promise<string> {
const output = await pptx.write({ outputType: "base64" });
return output as string;
}
Step 4: Slide Design Patterns
Consistent slide design requires a defined system of layout types, spacing rules, and visual hierarchy. The following patterns cover the most common slide types needed in automated presentation generation.
Slide Type Taxonomy:
| Slide Type | Purpose | Layout | Key Elements |
|---|---|---|---|
| Title Slide | Opening, section start | Full background color | Title (36pt), subtitle (18pt), logo |
| Content Slide | Body information | Header bar + white body | Title (22pt), bullets/text (14pt) |
| Two-Column | Comparison, dual info | Header bar + two panels | Title, left column, right column |
| Section Divider | Topic transition | Accent background | Section title (32pt), section number |
| Data Slide | Charts and tables | Header bar + data area | Title, chart/table, source note |
| Image Slide | Visuals, screenshots | Header bar + image area | Title, image (contain fit), caption |
| Key Takeaway | Emphasis, callout | Accent background | Icon, headline (28pt), supporting text |
| Closing Slide | End, contact info | Full background color | Thank you text, contact details, logo |
Design System Constants (python-pptx):
from dataclasses import dataclass
from pptx.dml.color import RGBColor
from pptx.util import Inches, Pt
@dataclass(frozen=True)
class DesignSystem:
"""Centralized design constants for consistent slide generation."""
# Colors
primary: RGBColor = RGBColor(0x2E, 0x4A, 0x7A)
secondary: RGBColor = RGBColor(0x5B, 0x8D, 0xEF)
accent: RGBColor = RGBColor(0xE8, 0x6C, 0x00)
text_dark: RGBColor = RGBColor(0x33, 0x33, 0x33)
text_light: RGBColor = RGBColor(0xFF, 0xFF, 0xFF)
background_light: RGBColor = RGBColor(0xF5, 0xF7, 0xFA)
border: RGBColor = RGBColor(0xDD, 0xDD, 0xDD)
# Typography
font_family: str = "Segoe UI"
title_size: Pt = Pt(28)
heading_size: Pt = Pt(22)
body_size: Pt = Pt(14)
caption_size: Pt = Pt(10)
# Spacing (16:9 slide = 13.333 x 7.5 inches)
margin_left: Inches = Inches(0.75)
margin_top: Inches = Inches(1.0)
content_width: Inches = Inches(11.83)
content_height: Inches = Inches(5.75)
header_height: Inches = Inches(0.75)
footer_height: Inches = Inches(0.4)
DESIGN = DesignSystem()
Two-Column Layout:
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
def add_two_column_slide(
prs: Presentation,
title: str,
left_title: str,
left_points: list[str],
right_title: str,
right_points: list[str],
) -> None:
"""Add a two-column comparison slide with independent bullet lists."""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.placeholders[0].text = title
column_width = Inches(5.5)
left_x = Inches(0.75)
right_x = Inches(7.0)
top_y = Inches(1.5)
height = Inches(5.0)
for col_x, col_title, points in [
(left_x, left_title, left_points),
(right_x, right_title, right_points),
]:
text_box = slide.shapes.add_textbox(col_x, top_y, column_width, height)
tf = text_box.text_frame
tf.word_wrap = True
# Column header
p = tf.paragraphs[0]
p.text = col_title
p.font.size = Pt(18)
p.font.bold = True
p.font.color.rgb = DESIGN.primary
p.space_after = Pt(12)
# Bullet points
for point in points:
p = tf.add_paragraph()
p.text = point
p.font.size = Pt(13)
p.font.color.rgb = DESIGN.text_dark
p.level = 0
p.space_after = Pt(6)
Section Divider Slide:
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
def add_section_divider(
prs: Presentation,
section_number: int,
section_title: str,
) -> None:
"""Add a section divider slide with number and title on accent background."""
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
# Full-slide background
background = slide.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = DESIGN.primary
# Section number
num_box = slide.shapes.add_textbox(
Inches(1.0), Inches(2.0), Inches(11.0), Inches(1.5),
)
num_tf = num_box.text_frame
num_tf.paragraphs[0].text = f"0{section_number}" if section_number < 10 else str(section_number)
num_tf.paragraphs[0].font.size = Pt(60)
num_tf.paragraphs[0].font.bold = True
num_tf.paragraphs[0].font.color.rgb = DESIGN.secondary
num_tf.paragraphs[0].alignment = PP_ALIGN.LEFT
# Section title
title_box = slide.shapes.add_textbox(
Inches(1.0), Inches(3.5), Inches(11.0), Inches(1.5),
)
title_tf = title_box.text_frame
title_tf.paragraphs[0].text = section_title
title_tf.paragraphs[0].font.size = Pt(32)
title_tf.paragraphs[0].font.color.rgb = DESIGN.text_light
title_tf.paragraphs[0].alignment = PP_ALIGN.LEFT
Closing Slide:
def add_closing_slide(
prs: Presentation,
title: str = "Thank You",
contact_info: dict | None = None,
) -> None:
"""Add a closing slide with optional contact information."""
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
background = slide.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = DESIGN.primary
# Main title
title_box = slide.shapes.add_textbox(
Inches(1.0), Inches(2.5), Inches(11.0), Inches(1.5),
)
tf = title_box.text_frame
tf.paragraphs[0].text = title
tf.paragraphs[0].font.size = Pt(36)
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.color.rgb = DESIGN.text_light
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Contact details
if contact_info:
info_box = slide.shapes.add_textbox(
Inches(3.0), Inches(4.5), Inches(7.0), Inches(2.0),
)
info_tf = info_box.text_frame
for key, value in contact_info.items():
p = info_tf.add_paragraph()
run = p.add_run()
run.text = f"{key}: {value}"
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(0xCC, 0xCC, 0xCC)
p.alignment = PP_ALIGN.CENTER
p.space_after = Pt(4)
Step 5: Charts and Data Visualization
Both python-pptx and PptxGenJS support native OOXML chart generation. Charts are embedded directly in the PPTX file as editable objects that PowerPoint can re-render.
Bar Chart (python-pptx):
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
def add_bar_chart_slide(
prs: Presentation,
title: str,
categories: list[str],
series_data: dict[str, list[float]],
chart_title: str = "",
) -> None:
"""Add a slide with a clustered bar chart.
Args:
prs: Target presentation.
title: Slide title.
categories: X-axis category labels.
series_data: Mapping of series name to list of values.
chart_title: Optional chart title displayed above the chart area.
"""
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only
slide.placeholders[0].text = title
chart_data = CategoryChartData()
chart_data.categories = categories
for series_name, values in series_data.items():
chart_data.add_series(series_name, values)
chart_shape = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(1.0), Inches(1.5),
Inches(11.0), Inches(5.5),
chart_data,
)
chart = chart_shape.chart
# Configure chart appearance
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
chart.legend.include_in_layout = False
if chart_title:
chart.has_title = True
chart.chart_title.text_frame.paragraphs[0].text = chart_title
chart.chart_title.text_frame.paragraphs[0].font.size = Pt(14)
# Style the value axis
value_axis = chart.value_axis
value_axis.has_title = False
value_axis.major_gridlines.format.line.color.rgb = RGBColor(0xDD, 0xDD, 0xDD)
# Style the category axis
category_axis = chart.category_axis
category_axis.has_major_gridlines = False
category_axis.tick_labels.font.size = Pt(10)
# Apply colors to each series
colors = ["2E4A7A", "5B8DEF", "E86C00", "2ECC71", "E74C3C"]
plot = chart.plots[0]
for idx, series in enumerate(plot.series):
fill = series.format.fill
fill.solid()
fill.fore_color.rgb = RGBColor.from_string(colors[idx % len(colors)])
Line Chart (python-pptx):
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
def add_line_chart_slide(
prs: Presentation,
title: str,
categories: list[str],
series_data: dict[str, list[float]],
smooth_lines: bool = False,
) -> None:
"""Add a slide with a line chart, optionally with smoothed lines."""
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.placeholders[0].text = title
chart_data = CategoryChartData()
chart_data.categories = categories
for series_name, values in series_data.items():
chart_data.add_series(series_name, values)
chart_type = (
XL_CHART_TYPE.LINE_MARKERS_STACKED
if not smooth_lines
else XL_CHART_TYPE.LINE
)
chart_shape = slide.shapes.add_chart(
chart_type,
Inches(1.0), Inches(1.5),
Inches(11.0), Inches(5.5),
chart_data,
)
chart = chart_shape.chart
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
# Configure smooth lines if requested
if smooth_lines:
for series in chart.series:
series.smooth = True
Pie Chart (python-pptx):
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LABEL_POSITION
def add_pie_chart_slide(
prs: Presentation,
title: str,
categories: list[str],
values: list[float],
show_percentages: bool = True,
) -> None:
"""Add a slide with a pie chart showing category distribution."""
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.placeholders[0].text = title
chart_data = CategoryChartData()
chart_data.categories = categories
chart_data.add_series("Values", values)
chart_shape = slide.shapes.add_chart(
XL_CHART_TYPE.PIE,
Inches(2.5), Inches(1.5),
Inches(8.0), Inches(5.5),
chart_data,
)
chart = chart_shape.chart
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.RIGHT
# Configure data labels
plot = chart.plots[0]
data_labels = plot.data_labels
data_labels.show_category_name = True
data_labels.show_percentage = show_percentages
data_labels.show_value = not show_percentages
data_labels.font.size = Pt(10)
data_labels.number_format = "0.0%" if show_percentages else "0"
Charts in PptxGenJS:
function addBarChartSlide(
pptx: PptxGenJS,
title: string,
chartData: { name: string; labels: string[]; values: number[] }[],
): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
slide.addChart(pptx.ChartType.bar, chartData, {
x: 0.75,
y: 1.2,
w: 11.5,
h: 5.5,
showLegend: true,
legendPos: "b",
showValue: false,
catAxisOrientation: "minMax",
valAxisOrientation: "minMax",
chartColors: ["2E4A7A", "5B8DEF", "E86C00", "2ECC71"],
valGridLine: { color: "DDDDDD", size: 0.5 },
});
}
function addLineChartSlide(
pptx: PptxGenJS,
title: string,
chartData: { name: string; labels: string[]; values: number[] }[],
): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
slide.addChart(pptx.ChartType.line, chartData, {
x: 0.75,
y: 1.2,
w: 11.5,
h: 5.5,
showLegend: true,
legendPos: "b",
lineSmooth: true,
lineSize: 2,
showMarker: true,
chartColors: ["2E4A7A", "5B8DEF", "E86C00"],
});
}
function addPieChartSlide(
pptx: PptxGenJS,
title: string,
chartData: { name: string; labels: string[]; values: number[] }[],
): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
slide.addChart(pptx.ChartType.pie, chartData, {
x: 2.0,
y: 1.2,
w: 9.0,
h: 5.5,
showLegend: true,
legendPos: "r",
showPercent: true,
showTitle: false,
chartColors: ["2E4A7A", "5B8DEF", "E86C00", "2ECC71", "E74C3C", "9B59B6"],
});
}
Scatter Chart (python-pptx):
from pptx.chart.data import XyChartData
from pptx.enum.chart import XL_CHART_TYPE
def add_scatter_chart_slide(
prs: Presentation,
title: str,
series_data: dict[str, list[tuple[float, float]]],
) -> None:
"""Add a slide with a scatter (XY) chart.
Args:
series_data: Mapping of series name to list of (x, y) tuples.
"""
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.placeholders[0].text = title
chart_data = XyChartData()
for series_name, points in series_data.items():
series = chart_data.add_series(series_name)
for x, y in points:
series.add_data_point(x, y)
chart_shape = slide.shapes.add_chart(
XL_CHART_TYPE.XY_SCATTER,
Inches(1.0), Inches(1.5),
Inches(11.0), Inches(5.5),
chart_data,
)
chart = chart_shape.chart
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
Step 6: Advanced Features
Master Slide Templates with python-pptx:
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.oxml.ns import qn
from pptx.dml.color import RGBColor
import copy
def create_branded_presentation(
company_name: str,
primary_color: str = "2E4A7A",
logo_path: str | None = None,
) -> Presentation:
"""Create a presentation with custom branded master slides.
Modifies the default slide master to apply corporate branding
including colors, fonts, and an optional logo.
"""
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
# Access the slide master
slide_master = prs.slide_masters[0]
# Set the background color of the title layout
title_layout = prs.slide_layouts[0]
background = title_layout.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = RGBColor.from_string(primary_color)
return prs
Speaker Notes:
def add_slide_with_notes(
prs: Presentation,
title: str,
content: str,
speaker_notes: str,
) -> None:
"""Add a content slide with speaker notes for the presenter."""
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.placeholders[0].text = title
slide.placeholders[1].text = content
# Add speaker notes
notes_slide = slide.notes_slide
notes_text_frame = notes_slide.notes_text_frame
notes_text_frame.text = speaker_notes
Adding Logos to Every Slide:
from pptx.util import Inches
def add_logo_to_all_slides(
prs: Presentation,
logo_path: str,
width: float = 1.2,
position: str = "bottom-right",
) -> None:
"""Add a logo image to every slide in the presentation.
Position options: "top-left", "top-right", "bottom-left", "bottom-right".
"""
slide_width = prs.slide_width
slide_height = prs.slide_height
logo_width = Inches(width)
positions = {
"top-left": (Inches(0.3), Inches(0.2)),
"top-right": (slide_width - logo_width - Inches(0.3), Inches(0.2)),
"bottom-left": (Inches(0.3), slide_height - Inches(0.8)),
"bottom-right": (slide_width - logo_width - Inches(0.3), slide_height - Inches(0.8)),
}
left, top = positions.get(position, positions["bottom-right"])
for slide in prs.slides:
slide.shapes.add_picture(logo_path, left, top, width=logo_width)
Hyperlinks:
from pptx.util import Inches, Pt
from pptx.oxml.ns import qn
from pptx.oxml import parse_xml
def add_hyperlink_text(
prs: Presentation,
title: str,
links: list[dict],
) -> None:
"""Add a slide with clickable hyperlinks.
Each link: {"text": str, "url": str, "description": str}
"""
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.placeholders[0].text = title
text_box = slide.shapes.add_textbox(
Inches(1.0), Inches(2.0), Inches(11.0), Inches(4.5),
)
tf = text_box.text_frame
tf.word_wrap = True
for idx, link_info in enumerate(links):
if idx == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
run = p.add_run()
run.text = link_info["text"]
run.font.size = Pt(16)
run.font.color.rgb = DESIGN.secondary
run.font.underline = True
# Set the hyperlink via the OOXML run element
r_element = run._r
hlinkClick = parse_xml(
f'<a:hlinkClick xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"'
f' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>'
)
r_props = r_element.get_or_add_rPr()
r_props.append(hlinkClick)
# Add the relationship
rel = slide.part.relate_to(
link_info["url"],
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
is_external=True,
)
hlinkClick.set(qn("r:id"), rel.rId)
if link_info.get("description"):
desc_p = tf.add_paragraph()
desc_p.text = f" {link_info['description']}"
desc_p.font.size = Pt(12)
desc_p.font.color.rgb = DESIGN.text_dark
desc_p.space_after = Pt(12)
Animations and Transitions (PptxGenJS):
function addAnimatedSlide(pptx: PptxGenJS, title: string): void {
const slide = pptx.addSlide({ masterName: "CONTENT_SLIDE" });
slide.addText(title, { placeholder: "title" });
// Add slide transition
slide.transition = {
type: "fade",
speed: 1.0, // seconds
};
// Animate text elements
const points = ["First point", "Second point", "Third point"];
points.forEach((point, idx) => {
slide.addText(point, {
x: 1.0,
y: 1.5 + idx * 0.8,
w: 11.0,
h: 0.6,
fontSize: 18,
color: "333333",
bullet: { type: "bullet" },
});
});
// Note: PptxGenJS supports slide transitions but has limited
// shape-level animation support. For complex animations,
// use a template-based approach (see Step 7).
}
Step 7: Template-Based Generation
Template-based generation loads an existing .pptx file (designed in PowerPoint or Google Slides) and populates it with dynamic data. This approach separates design from code and is ideal for branded report generation.
Loading and Inspecting a Template (python-pptx):
from pptx import Presentation
from pptx.util import Inches
def inspect_template(template_path: str) -> dict:
"""Inspect a template to discover its layouts and placeholders.
Run this first when working with a new template to understand
which placeholder indices and names are available.
"""
prs = Presentation(template_path)
template_info = {
"slide_width": str(prs.slide_width),
"slide_height": str(prs.slide_height),
"slide_count": len(prs.slides),
"layouts": [],
}
for layout_idx, layout in enumerate(prs.slide_layouts):
layout_info = {
"index": layout_idx,
"name": layout.name,
"placeholders": [],
}
for ph in layout.placeholders:
layout_info["placeholders"].append({
"idx": ph.placeholder_format.idx,
"name": ph.name,
"type": str(ph.placeholder_format.type),
"left": str(ph.left),
"top": str(ph.top),
"width": str(ph.width),
"height": str(ph.height),
})
template_info["layouts"].append(layout_info)
return template_info
def load_template(template_path: str) -> Presentation:
"""Load an existing .pptx template for population."""
return Presentation(template_path)
Populating Template Placeholders:
from pptx import Presentation
from pptx.util import Pt
def populate_template_slide(
prs: Presentation,
layout_index: int,
placeholder_data: dict[int, str],
) -> None:
"""Add a ne
…(truncated)