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.

  • Documents & Office
  • $pptx-presentations
  • 209 lines
  • 2 files
  • ~2,150 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- pptx-presentations

Installs to ~/.agents/skills/pptx-presentations/ for every project on this machine.

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

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.

~/.agents/skills/pptx-presentations/SKILL.md

--- name: pptx-presentations description: Builds and edits PowerPoint .pptx decks with python-pptx or PptxGenJS - uses the template's slide layouts and placeholders, native editable charts… license: MIT ---

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

  1. Read AGENTS.md; Python projects use python-pptx, Node projects pptxgenjs. Installs need network; ask for approval if the sandbox blocks them.
  2. Get the template (.pptx or .potx saved 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}")
  1. 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.idx from the layout map, not position in slide.placeholders (indexes are not contiguous: 0, 1, 10, 13 is normal).
  • Charts: pass numbers, not formatted strings. Set formats with number_format and number_format_is_linked = False. For scatter use XyChartData, for bubble BubbleChartData.
  • Picture placeholders: slide.placeholders[idx].insert_picture("photo.jpg") crops to fill; for free placement use shapes.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_SHAPE only 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, set prs.slide_width = Inches(13.333) and prs.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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem