There are three ways to generate PowerPoint files from Python in 2026: the python-pptx library (free, full control, you own every pixel), a structured rendering API like SlideForge (typed data in, validated consulting-grade .pptx out, $0.05/slide), or — new since mid-2026 — both at once: writing python-pptx code and running it in SlideForge's sandbox, where it gets themes, chart widgets, and output verification for free. This tutorial covers all three with working code.
1. python-pptx: The Standard Library
python-pptxis the de facto Python library for creating and modifying PowerPoint files. It's free, open source (MIT license), and gives you full programmatic control over every element in a .pptx file.
Install and create a basic slide
pip install python-pptxfrom pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
# Create presentation
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
# Add a blank slide
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Add a title
title = slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(12), Inches(0.6))
tf = title.text_frame
tf.text = "Q1 2026 Performance"
tf.paragraphs[0].font.size = Pt(28)
tf.paragraphs[0].font.bold = True
# Save
prs.save("basic_slide.pptx")This works. But the output looks like it was made by a developer, not a designer. No color harmony, no spacing system, no visual hierarchy beyond font size.
Building a real slide: KPI Dashboard
Here's what it takes to build a consulting-quality KPI dashboard with python-pptx:
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Title
title = slide.shapes.add_textbox(
Inches(0.5), Inches(0.3), Inches(12), Inches(0.6)
)
tf = title.text_frame
tf.text = "Q1 2026 Performance Summary"
tf.paragraphs[0].font.size = Pt(28)
tf.paragraphs[0].font.bold = True
# KPI metrics — repeat for each one
metrics = [
("$12.4M", "Revenue", "+18% YoY"),
("847", "New Clients", "+23%"),
("94.2%", "Retention Rate", ""),
("4.7", "NPS Score", "+0.3"),
]
for i, (value, label, trend) in enumerate(metrics):
left = Inches(0.5 + i * 3.1)
top = Inches(1.5)
width = Inches(2.8)
height = Inches(2.0)
# Background box
box = slide.shapes.add_shape(1, left, top, width, height)
box.fill.solid()
box.fill.fore_color.rgb = RGBColor(0xF5, 0xF5, 0xF5)
box.line.fill.background()
# Value text
val_box = slide.shapes.add_textbox(left, Inches(1.7), width, Inches(0.8))
val_tf = val_box.text_frame
val_tf.text = value
val_tf.paragraphs[0].font.size = Pt(36)
val_tf.paragraphs[0].font.bold = True
val_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# Label text
lbl_box = slide.shapes.add_textbox(left, Inches(2.5), width, Inches(0.4))
lbl_tf = lbl_box.text_frame
lbl_tf.text = label.upper()
lbl_tf.paragraphs[0].font.size = Pt(9)
lbl_tf.paragraphs[0].font.color.rgb = RGBColor(0x99, 0x99, 0x99)
lbl_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
# ... and trend text, accent lines, spacing adjustments...
prs.save("kpi_dashboard.pptx")
# ~60 lines for a basic version. A polished version is 100+.That's ~60 lines for a basic KPI dashboard. A polished version with accent lines, trend arrows, tinted backgrounds, and a takeaway banner runs 100+ lines. And this is one slide type — a consulting deck needs 10-15 different layouts.
The pain points
- Alignment is manual. Every coordinate is hardcoded in inches. Move one element and everything else needs adjusting.
- No design system. Colors, fonts, and spacing are ad-hoc. Consistency across slides requires discipline and shared constants.
- Limited chart support. No waterfall, treemap, or sunburst charts. Combo charts can corrupt files.
- No animations. The #1 requested feature (GitHub issue #1106) — still unsupported.
- Nothing checks the output. If your loop drops a metric or a textbox overflows the canvas, python-pptx saves the file happily. You find out in the meeting.
- Maintenance burden. Every new slide type is a multi-day project. Bug fixes in one template can break another.
2. SlideForge API: Typed Data In, Validated Slide Out
SlideForge renders the same KPI dashboard from a typed request: pick a form from the catalog (150+ layouts — KPI boards, waterfalls, Gantt plans, org charts, funnels…), put your numbers in the documented fields, and the engine binds them verbatim — your values appear exactly as sent, deterministically, with no LLM in the loop:
import requests
API = "https://api.slideforge.dev"
KEY = {"Authorization": "Bearer sf_live_YOUR_KEY"}
resp = requests.post(f"{API}/v1/render/intent", headers=KEY, json={
"form": "kpi_metrics",
"headline": "Q1 2026: all four KPIs trending up",
"data": {
"metrics": [
{"value": "$12.4M", "label": "Revenue", "trend": "+18% YoY"},
{"value": "847", "label": "New Clients", "trend": "+23%"},
{"value": "94.2%", "label": "Retention Rate"},
{"value": "4.7", "label": "NPS Score", "trend": "+0.3"},
],
},
"takeaway": "On track for the $50M full-year target",
})
result = resp.json()
print(result["status"]) # "complete"
print(result["fidelity"]) # "verbatim" — every value bound exactly as sent
job_id = result["job_id"]
# Download with the same Bearer key
pptx = requests.get(f"{API}/v1/jobs/{job_id}/pptx", headers=KEY)
open("kpi_dashboard.pptx", "wb").write(pptx.content)Rendering completes in well under a second at $0.05/slide, and the output is a native .pptx with editable shapes — open it in PowerPoint and change anything.
The part python-pptx can't give you is the honesty layer: every response declares what happened. fidelity: verbatim means every supplied value rendered exactly. If your waterfall's deltas don't sum to the declared total, you don't get a wrong slide — you get status: completed_with_errorswith the exact discrepancy ("start+deltas = 49, declared end = 70"), and you aren't billed. A render that isn't usable is free.
For content that doesn't arrive as typed data yet, send a prose brief and let the router pick the form:
resp = requests.post(f"{API}/v1/render/auto", headers=KEY, json={
"brief": "Revenue bridge Q4 to Q1: start $10.2M. New clients +$2.1M, "
"upsell +$1.3M, churn -$0.8M, FX -$0.2M.",
})
# Routed to the waterfall form, numbers bound verbatim, <1.5s, $0.053. Both: Your python-pptx Code, Run in the SlideForge Sandbox
If you already write python-pptx — or your layout doesn't exist in any catalog — you don't have to choose. POST /v1/render/code executes your build() function in a sandbox where it gets what raw python-pptx lacks: a theme, pre-built chart widgets, chrome (title/takeaway furniture), a geometry linter, and the same output verification as the structured path.
code = '''
def build(prs, slide):
# Chrome (title, takeaway band) is already rendered — draw the body.
# add_widget draws SlideForge's own theme-correct parts:
add_widget(slide, "kpi_metrics", x=0.6, y=1.4, w=12.1, h=4.6, content={
"metrics": [
{"value": "$12.4M", "label": "Revenue", "trend": "+18% YoY"},
{"value": "847", "label": "New Clients", "trend": "+23%"},
{"value": "94.2%", "label": "Retention Rate"},
{"value": "4.7", "label": "NPS Score", "trend": "+0.3"},
],
}, theme=THEME)
'''
resp = requests.post(f"{API}/v1/render/code", headers=KEY, json={
"code": code,
"headline": "Q1 2026: all four KPIs trending up",
"takeaway": "On track for the $50M full-year target",
})
# Your code, their theme + widgets + linter. $0.05, ~2s, no LLM involved.The widget catalog covers the engine's own chart primitives — waterfall, Gantt, funnel, org chart and the rest — so "custom layout" stops meaning "hand-place every rectangle." Static analysis catches overlapping and off-canvas shapes before you open the file, and geometry that can't work (negative sizes, impossible constraints) is rejected with the exact numbers instead of silently producing a broken slide.
4. When to Use Which
| Criteria | python-pptx | SlideForge intent | SlideForge code mode |
|---|---|---|---|
| Cost | Free | $0.05/slide | $0.05/slide |
| Setup time | Days-weeks | Minutes | Minutes (it's your python-pptx skill) |
| Layout work | All yours | None — pick a form | Yours, with widgets + theme + linter |
| Output validation | None | Reconciliation, capacity, overflow + fidelity flag | Geometry linter + content checks |
| Charts (waterfall, Gantt, funnel…) | Build by hand | Built in | add_widget() |
| Offline / air-gapped | Yes | No (API) | No (API) |
| Full XML control | Yes | No | Nearly — your code draws the body |
| Maintenance | You maintain it | Managed | Your code, managed runtime |
Use python-pptx alone if you need full control in an air-gapped environment, or have zero budget and unlimited time.
Use the intent API if your data is structured and the deck must be right unattended — reports, agents, SaaS export. Typed fields in, verbatim slide out, validated.
Use code mode if you think in python-pptx but want the theme, the chart widgets, and something checking the output — or your layout is genuinely outside any catalog.
They're also compatible in the other direction: every SlideForge output is a standard .pptx that python-pptx can open and post-process.
Getting Started
Sign up free (60 free slides, no card) and follow the quickstart guide to render your first slide. Browse the form catalog and per-form schemas in the template gallery, or read the detailed comparison of SlideForge vs python-pptx.