DEV Community

jake
jake

Posted on • Originally published at paperjsx.com

Migrate from python-pptx to PaperJSX

PaperJSX is not a drop-in replacement for python-pptx — it is a different programming model in a different language. python-pptx uses imperative Python method calls; PaperJSX uses declarative JSON schemas in Node.js. Migration requires rewriting document definitions, not just swapping imports. This guide provides side-by-side code for every common task so you can evaluate the trade-offs before committing.

Why migrate?

Three factors drive python-pptx migration:

  • Inactive maintenance. python-pptx's last release was v1.0.2 in August 2024. According to Snyk, it is classified as inactive with

    439 open issues

    and 77 open pull requests. The Swiss Federal Railways formally flagged it as a dependency risk in January 2026.

  • Missing features. python-pptx cannot create combo charts (8-year-old gap), slide animations, or Office 2016+ chart types. It has no multi-format output and no AI agent integration.

  • Single-format limitation. python-pptx generates PPTX only. If the same report needs to ship as PDF, DOCX, or XLSX, you need additional libraries with separate APIs. PaperJSX handles all four from one JSON schema.

What do you gain and lose?

Capability python-pptx PaperJSX
Read/modify existing PPTX Yes No (generate only)
Language Python JavaScript / TypeScript
Combo charts No Yes (Pro)
Slide animations No Yes (Pro)
Multi-format output PPTX only PPTX, DOCX, PDF, XLSX
SVG images No Yes
MCP server (AI agents) No Yes
PDF/UA accessibility No Yes (Pro)
Maintenance status Inactive (since Aug 2024) Active
Native dependencies lxml, Pillow, XlsxWriter Zero
Free tier MIT (full library) Apache-2.0 lite engines for all four formats
Slide Masters from templates Yes (read from file) JSON-defined only

The critical trade-off: python-pptx can open and modify existing presentations. PaperJSX cannot. If your workflow involves loading a branded template .pptx file and modifying specific placeholders, PaperJSX requires a different approach — you define the slide master styling in JSON instead of loading it from a file.

Task: add text to a slide

from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor

prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])

txBox = slide.shapes.add_textbox(
    Inches(1), Inches(1), Inches(8), Inches(1)
)
tf = txBox.text_frame
p = tf.paragraphs[0]
p.text = "Q3 2026 results"
p.font.size = Pt(36)
p.font.bold = True
p.font.color.rgb = RGBColor(0x1A, 0x1A, 0x18)

prs.save("output.pptx")
Enter fullscreen mode Exit fullscreen mode
import { generate } from "@paperjsx/json-to-pptx";
import { writeFileSync } from "node:fs";

const doc = {
  slides: [{
    elements: [{
      type: "text",
      value: "Q3 2026 results",
      style: { fontSize: 36, bold: true, color: "#1A1A18" }
    }]
  }]
};

const buffer = await generate(doc);
writeFileSync("output.pptx", buffer);
Enter fullscreen mode Exit fullscreen mode

python-pptx: 12 lines, imperative. You create a presentation, add a slide, add a text box with pixel coordinates, access the text frame, access the paragraph, set text, set font size, set bold, set color, save. PaperJSX: 8 lines, declarative. You describe what the slide contains and call generate.

How do you add a chart?

from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE

chart_data = ChartData()
chart_data.categories = ["NA", "EMEA", "APAC"]
chart_data.add_series("Revenue", (4200, 3100, 2800))
chart_data.add_series("Expenses", (2800, 2100, 1900))

slide.shapes.add_chart(
    XL_CHART_TYPE.COLUMN_CLUSTERED,
    Inches(1), Inches(2), Inches(8), Inches(4),
    chart_data
)
Enter fullscreen mode Exit fullscreen mode
{
  type: "chart",
  chartType: "bar",
  data: {
    categories: ["NA", "EMEA", "APAC"],
    series: [
      { name: "Revenue", values: [4200, 3100, 2800] },
      { name: "Expenses", values: [2800, 2100, 1900] }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The data structure is nearly identical — categories and series with names and values. The difference: python-pptx requires ChartData objects and explicit positioning in inches. PaperJSX takes a JSON object with auto-layout. Both produce native, editable OOXML charts. PaperJSX additionally supports chartType: "combo" — a feature python-pptx has lacked since 2017.

Task: add an image

slide.shapes.add_picture(
    "logo.png",
    Inches(0.5), Inches(0.5),
    Inches(2), Inches(0.6)
)
Enter fullscreen mode Exit fullscreen mode
{
  type: "image",
  src: "./logo.png",
  style: { width: 2, height: 0.6 }
}
Enter fullscreen mode Exit fullscreen mode

Both accept file paths. PaperJSX additionally accepts HTTP URLs and base64 strings. python-pptx requires Inches() wrappers for positioning; PaperJSX uses plain numbers. PaperJSX supports SVG images; python-pptx does not.

Task: add a table

rows, cols = 4, 3
table_shape = slide.shapes.add_table(
    rows, cols,
    Inches(1), Inches(2), Inches(8), Inches(3)
)
table = table_shape.table

# set headers
table.cell(0, 0).text = "Region"
table.cell(0, 1).text = "Revenue"
table.cell(0, 2).text = "Growth"

# set data rows
data = [
    ("NA", "$4.2M", "+10.5%"),
    ("EMEA", "$3.1M", "+6.9%"),
    ("APAC", "$2.8M", "+27.3%"),
]
for i, row_data in enumerate(data, start=1):
    for j, val in enumerate(row_data):
        table.cell(i, j).text = val
Enter fullscreen mode Exit fullscreen mode
{
  type: "table",
  headers: ["Region", "Revenue", "Growth"],
  rows: [
    ["NA", "$4.2M", "+10.5%"],
    ["EMEA", "$3.1M", "+6.9%"],
    ["APAC", "$2.8M", "+27.3%"]
  ],
  style: {
    headerBackground: "#1A1A18",
    headerColor: "#FFFFFF"
  }
}
Enter fullscreen mode Exit fullscreen mode

python-pptx tables require pre-declaring row and column counts, then iterating through cells to set values individually. PaperJSX takes a headers array and a rows array — the structure maps directly to how developers think about tabular data. python-pptx: 16 lines. PaperJSX: 10 lines.

Task: multi-slide deck from data

This is where the architectural difference matters most. Building a 10-slide deck from database records in python-pptx means 10 iterations of imperative calls. In PaperJSX, it means mapping data to JSON objects.

prs = Presentation()

for region in regions:
    slide = prs.slides.add_slide(prs.slide_layouts[6])

    # title
    txBox = slide.shapes.add_textbox(
        Inches(1), Inches(0.5), Inches(8), Inches(1)
    )
    txBox.text_frame.paragraphs[0].text = region["name"]

    # chart
    chart_data = ChartData()
    chart_data.categories = region["quarters"]
    chart_data.add_series("Revenue", region["values"])
    slide.shapes.add_chart(
        XL_CHART_TYPE.COLUMN_CLUSTERED,
        Inches(1), Inches(2), Inches(8), Inches(4),
        chart_data
    )

prs.save("report.pptx")
Enter fullscreen mode Exit fullscreen mode
const doc = {
  slides: regions.map(region => ({
    elements: [
      { type: "text", value: region.name,
        style: { fontSize: 28, bold: true } },
      {
        type: "chart",
        chartType: "bar",
        data: {
          categories: region.quarters,
          series: [{ name: "Revenue", values: region.values }]
        }
      }
    ]
  }))
};

const buffer = await generate(doc);
Enter fullscreen mode Exit fullscreen mode

The PaperJSX version is a single .map() call. The entire deck is a JavaScript expression — it can be snapshot-tested, JSON-diffed, and schema-validated before generation. The python-pptx version is a loop of imperative side effects that can only be tested by generating the file and opening it.

How do you call PaperJSX from Python?

If your backend is Python and you cannot rewrite it in Node.js, you can still use PaperJSX by deploying it as an HTTP service.

# Python client
import requests
import json

schema = {
    "slides": [{
        "elements": [{
            "type": "text",
            "value": "Generated from Python",
            "style": { "fontSize": 36 }
        }]
    }]
}

response = requests.post(
    "https://your-api.vercel.app/api/generate?format=pptx",
    json=schema
)

with open("output.pptx", "wb") as f:
    f.write(response.content)
Enter fullscreen mode Exit fullscreen mode

Deploy PaperJSX as a Next.js API route, a Vercel Function, or an Express microservice. Call it from Python via HTTP. The JSON schema is language-agnostic — Python's dict serializes to the same JSON that PaperJSX expects.

# Python calling Node.js via subprocess
import subprocess
import json

schema = { /* ... */ }

result = subprocess.run(
    ["node", "generate.mjs"],
    input=json.dumps(schema),
    capture_output=True,
    text=True
)

# generate.mjs reads stdin, calls generate(), writes to stdout
Enter fullscreen mode Exit fullscreen mode

The subprocess approach avoids network latency but requires Node.js installed alongside Python. The HTTP approach is cleaner for production — it decouples the document generation service from the Python backend.

Start migrating — read the JSON-to-PPTX quickstart, see the PPTX package guide, or compare all three PPTX libraries.

Top comments (0)