PowerPoint PPTX Decks skill for Codex
Template-true slides with charts you can edit. Codex maps the template's layouts and placeholder indexes before adding a single slide, so decks inherit the brand instead of fighting it. Charts are native and editable, monthly decks are refreshed with replace_data rather than rebuilt, and every deck is rendered to images and checked for text overflow before hand-over.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- pptx-presentations Installs to ~/.agents/skills/pptx-presentations/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install pptx-presentations The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project pptx-presentations Installs to .agents/skills/pptx-presentations/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/pptx-presentations.zipunzip -o pptx-presentations.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) pptx-presentations PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $pptx-presentations, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds and edits PowerPoint .pptx decks with python-pptx or PptxGenJS - uses the template's slide layouts and placeholders, native editable charts from data, tables, images, speaker notes, chart data refresh and slide removal, then renders the deck to images to check for overflow. Use when the user asks to generate slides from data or a report, update numbers and charts in an existing deck, fill a corporate PowerPoint template, or automate a recurring presentation.
Use it when
- Slides must be generated from data or a written report
- A recurring deck needs its numbers and charts refreshed
- A corporate .pptx template has to be filled programmatically
- Generated slides have overflowing text or uneditable chart images
Use something else when
- You need help with the storyline and visual design of a deck: use slide-deck-design
- The output is a Google Slides presentation: use google-docs-automation
How to check the result
Convert the deck to PDF with LibreOffice, render each slide to PNG and inspect for overflow and overlap, then assert slide count and titles with python-pptx.
The mistake it prevents
Starting from Presentation() without a template gives 4:3 slides and default Office styling instead of the brand's 16:9 layouts.
Done well means: slides come from the template’s layouts (so theme fonts, colours and logos are inherited), charts are native and editable rather than pasted images, no text overflows its box, and the deck was rendered and looked at before it was handed over.
Before building
- Read
AGENTS.md; Python projects usepython-pptx, Node projectspptxgenjs. Installs need network; ask for approval if the sandbox blocks them. - Get the template (
.pptxor.potxsaved as.pptx). Map its layouts and placeholders:
from pptx import Presentation
prs = Presentation("template.pptx")
print("size:", prs.slide_width.inches, "x", prs.slide_height.inches)
for i, layout in enumerate(prs.slide_layouts):
print(i, layout.name)
for ph in layout.placeholders:
f = ph.placeholder_format
print(f" idx={f.idx:<3} type={f.type!s:<22} name={ph.name}")
- Write a slide plan (one line per slide: layout, title, content source) and confirm it
with the user for anything longer than ~8 slides. Narrative and visual design questions
(storyline, what goes on which slide) belong to
$slide-deck-design.
Build from layouts with python-pptx
from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION
from pptx.util import Inches, Pt
prs = Presentation("template.pptx")
TITLE, TITLE_CONTENT, TITLE_ONLY = 0, 1, 5 # indexes from the layout map above
s = prs.slides.add_slide(prs.slide_layouts[TITLE])
s.shapes.title.text = "Q3 Business Review"
s.placeholders[1].text = "Operations | October 2025"
s = prs.slides.add_slide(prs.slide_layouts[TITLE_CONTENT])
s.shapes.title.text = "Throughput up 12%, cost per order down 6%" # headline = the takeaway
body = s.placeholders[1].text_frame
body.text = "Two carriers onboarded in July"
for line, level in [("Average lead time 2.1 days (from 2.6)", 1), ("Returns backlog cleared", 0)]:
p = body.add_paragraph()
p.text, p.level = line, level
s.notes_slide.notes_text_frame.text = "Mention the carrier contract renewal date."
s = prs.slides.add_slide(prs.slide_layouts[TITLE_ONLY])
s.shapes.title.text = "North leads on volume"
data = CategoryChartData()
data.categories = ["Jul", "Aug", "Sep"]
data.add_series("North", (1180, 1204, 1310))
data.add_series("South", (940, 980, 1015))
frame = s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(0.7), Inches(1.6), Inches(11.9), Inches(5.2), data)
chart = frame.chart
chart.has_legend = True
chart.legend.position = XL_LEGEND_POSITION.BOTTOM
chart.legend.include_in_layout = False
plot = chart.plots[0]
plot.has_data_labels = True
plot.data_labels.number_format = "#,##0"
plot.data_labels.number_format_is_linked = False
chart.value_axis.has_major_gridlines = False
chart.value_axis.tick_labels.font.size = Pt(12)
s = prs.slides.add_slide(prs.slide_layouts[TITLE_ONLY])
s.shapes.title.text = "Regional detail"
rows = [("Region", "Orders", "Defect rate"), ("North", "1,310", "4.1%"), ("South", "1,015", "3.7%")]
tbl = s.shapes.add_table(len(rows), 3, Inches(0.7), Inches(1.6), Inches(8), Inches(1.5)).table
for r, values in enumerate(rows):
for c, value in enumerate(values):
cell = tbl.cell(r, c)
cell.text = value
cell.text_frame.paragraphs[0].runs[0].font.size = Pt(14)
prs.save("q3-review.pptx")
Rules that keep decks editable:
- Put text into placeholders, not new text boxes, whenever the layout has one; the user’s later theme changes then apply.
- Use
placeholder_format.idxfrom the layout map, not position inslide.placeholders(indexes are not contiguous: 0, 1, 10, 13 is normal). - Charts: pass numbers, not formatted strings. Set formats with
number_formatandnumber_format_is_linked = False. For scatter useXyChartData, for bubbleBubbleChartData. - Picture placeholders:
slide.placeholders[idx].insert_picture("photo.jpg")crops to fill; for free placement useshapes.add_picture(path, left, top, width=...)and set only width so the aspect ratio holds.
Update an existing deck
from pptx import Presentation
from pptx.chart.data import CategoryChartData
prs = Presentation("monthly.pptx")
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_chart and shape.name == "Revenue chart":
new = CategoryChartData()
new.categories = ["Jul", "Aug", "Sep", "Oct"]
new.add_series("Revenue", (410, 432, 455, 471))
shape.chart.replace_data(new) # keeps chart formatting
if shape.has_text_frame:
for p in shape.text_frame.paragraphs:
for run in p.runs: # edit runs to keep their formatting
run.text = run.text.replace("{{month}}", "October")
prs.save("monthly-oct.pptx")
Find shapes by shape.name (set it in PowerPoint’s Selection Pane) rather than by order.
Placeholders split across runs need the same paragraph-level replacement used for Word
documents (see $docx-documents).
Removing a slide has no public API. This drops it from the deck and its relationship:
def delete_slide(prs, index):
sld_id = prs.slides._sldIdLst[index]
prs.part.drop_rel(sld_id.rId)
prs.slides._sldIdLst.remove(sld_id)
Duplicating slides is not supported by python-pptx; build each slide from its layout instead, or keep a “source” deck and copy content shape by shape.
PptxGenJS (Node)
import pptxgen from "pptxgenjs";
const pptx = new pptxgen();
pptx.layout = "LAYOUT_WIDE"; // 13.33 x 7.5 in
pptx.defineSlideMaster({
title: "BODY",
background: { color: "FFFFFF" },
objects: [{ text: { text: "Acme Ops", options: { x: 0.4, y: 7.0, fontSize: 10, color: "888888" } } }],
});
const slide = pptx.addSlide({ masterName: "BODY" });
slide.addText("North leads on volume", { x: 0.5, y: 0.3, w: 12.3, h: 0.8, fontSize: 28, bold: true });
slide.addChart(pptx.ChartType.bar, [
{ name: "North", labels: ["Jul", "Aug", "Sep"], values: [1180, 1204, 1310] },
{ name: "South", labels: ["Jul", "Aug", "Sep"], values: [940, 980, 1015] },
], { x: 0.5, y: 1.3, w: 12.3, h: 5.4, barDir: "col", showLegend: true, legendPos: "b", showValue: true });
slide.addNotes("Mention the carrier contract renewal date.");
await pptx.writeFile({ fileName: "q3-review.pptx" });
PptxGenJS creates new decks only; it cannot open an existing .pptx for editing.
Gotchas
- Text overflow is the most common defect. python-pptx does not measure text;
MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPEonly sets a flag PowerPoint applies when the user edits. Keep bullets to about 6 lines of under 12 words, shorten copy, or split the slide. For a hard guarantee,text_frame.fit_text(font_file="fonts/Inter.ttf", max_size=24)computes a size from a real font file. - Slide size: python-pptx’s default template is 4:3 (10 x 7.5 in). Starting from
Presentation()without a template, setprs.slide_width = Inches(13.333)andprs.slide_height = Inches(7.5)for 16:9 before adding slides. - Units are EMU (914,400 per inch); always use
Inches,Pt,Cm,Emu. - Images: compress large photos before inserting (a 12 MB phone photo per slide makes an unshareable deck); 1920 px wide is plenty.
- Fonts render as the viewer’s substitute if the template font is not installed. Use theme fonts from the template rather than hard-coding a font name per run.
- Never paste a chart as a PNG when the user will want to edit numbers later.
Verify by rendering
soffice --headless --convert-to pdf --outdir render q3-review.pptx
pdftoppm -r 50 -png render/q3-review.pdf render/slide
Open every rendered slide image and check: titles on one or two lines, no text past the box or slide edge, charts legible, nothing overlapping the logo or footer. LibreOffice rendering differs slightly from PowerPoint, so treat near-misses as failures. Also confirm programmatically:
prs = Presentation("q3-review.pptx")
assert len(prs.slides) == 4
for i, s in enumerate(prs.slides, 1):
print(i, s.slide_layout.name, "|", s.shapes.title.text if s.shapes.title else "(no title)")
Hand-over note
**Deck:** out/q3-review.pptx (4 slides, 16:9, built on template.pptx layouts)
**Data:** chart and table values from reports/q3.csv (pulled 2025-10-02)
**Editable:** charts are native PowerPoint charts; right-click > Edit Data works
**Checked:** rendered all slides; no overflow; speaker notes on slide 2 What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "PowerPoint PPTX Decks"
short_description: "Template-true slides with charts you can edit"
default_prompt: "$pptx-presentations Turn this quarterly metrics CSV into a 6-slide deck using our company template" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.