DEV Community

jake
jake

Posted on • Originally published at paperjsx.com

Generate PPTX in Express.js with PaperJSX

PaperJSX generates PowerPoint files from JSON inside Express route handlers with zero native dependencies. Install the package, add a POST route, accept a JSON body, call generate(), and return the buffer with PPTX MIME headers. The complete API endpoint is 15 lines of code.

1. Install

This guide is Express-specific. Not on Express? See the framework-agnostic hub, generate PPTX from any data source, or the Google Sheets to PPTX walkthrough.

npm install express @paperjsx/json-to-pptx
Enter fullscreen mode Exit fullscreen mode

Two packages. No postinstall scripts, no native compilation. PaperJSX has zero native dependencies — it runs anywhere Express runs, including Docker containers, PM2 clusters, and traditional VPS deployments.

2. Minimal Express route

import express from "express";
import { generate } from "@paperjsx/json-to-pptx";

const app = express();
app.use(express.json());

app.post("/api/pptx", async (req, res) => {
  try {
    const buffer = await generate(req.body);

    res.set({
      "Content-Type":
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
      "Content-Disposition": 'attachment; filename="report.pptx"',
    });

    res.send(buffer);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.listen(3000, () => console.log("Listening on :3000"));
Enter fullscreen mode Exit fullscreen mode

Test it with curl:

curl -X POST http://localhost:3000/api/pptx \
  -H "Content-Type: application/json" \
  -d '{"slides":[{"elements":[{"type":"text","value":"Hello from Express","style":{"fontSize":36,"bold":true}}]}]}' \
  -o test.pptx
Enter fullscreen mode Exit fullscreen mode

Open test.pptx in PowerPoint, Keynote, or Google Slides. The file is valid OOXML — no repair dialog, no corruption.

How do you add charts and tables?

The JSON body can include any element type PaperJSX supports. Here is a request body that produces a slide with a bar chart and a data table.

{
  "slides": [
    {
      "elements": [
        {
          "type": "text",
          "value": "Q3 revenue by region",
          "style": { "fontSize": 28, "bold": true }
        },
        {
          "type": "chart",
          "chartType": "bar",
          "data": {
            "categories": ["NA", "EMEA", "APAC"],
            "series": [
              { "name": "Revenue", "values": [4200, 3100, 2800] }
            ]
          }
        },
        {
          "type": "table",
          "headers": ["Region", "Revenue", "Growth"],
          "rows": [
            ["NA", "$4.2M", "+10%"],
            ["EMEA", "$3.1M", "+7%"],
            ["APAC", "$2.8M", "+27%"]
          ]
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The chart is native and editable — recipients can click it in PowerPoint and modify the data. For combo charts (bar + line on the same axis), set "chartType": "combo" and add a "type" field per series. Extended features across all four formats require Pro ($199/mo). For the supported schema, see the PPTX documentation.

How do you generate from database data?

In production, PPTX schemas are assembled from database queries — not hardcoded JSON. Here is a route that generates a multi-slide deck from a list of regions stored in a database.

import { db } from "./db.mjs";

app.get("/api/report", async (req, res) => {
  const regions = await db.query("SELECT * FROM regions");

  const doc = {
    slides: [
      // title slide
      {
        elements: [
          { type: "text", value: "Q3 report",
            style: { fontSize: 36, bold: true } }
        ]
      },
      // one slide per region
      ...regions.map(r => ({
        elements: [
          { type: "text", value: r.name,
            style: { fontSize: 24, bold: true } },
          {
            type: "chart",
            chartType: "bar",
            data: {
              categories: ["Q1", "Q2", "Q3"],
              series: [{ name: "Revenue", values: r.quarterly }]
            }
          }
        ]
      }))
    ]
  };

  const buffer = await generate(doc);
  res.set({
    "Content-Type": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    "Content-Disposition": 'attachment; filename="q3-report.pptx"',
  });
  res.send(buffer);
});
Enter fullscreen mode Exit fullscreen mode

How do you add PDF, DOCX, and XLSX?

The same JSON body can produce any of PaperJSX's four output formats. Add a format query parameter and switch the generator. This is the same pattern used in the multi-format tutorial and the Next.js PDF tutorial.

import { generate as toPptx } from "@paperjsx/json-to-pptx";
import { generate as toDocx } from "@paperjsx/json-to-docx";
import { generate as toPdf }  from "@paperjsx/json-to-pdf";
import { generate as toXlsx } from "@paperjsx/json-to-xlsx";

const formats = {
  pptx: { fn: toPptx, mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
  docx: { fn: toDocx, mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
  pdf:  { fn: toPdf,  mime: "application/pdf" },
  xlsx: { fn: toXlsx, mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
};

app.post("/api/generate", async (req, res) => {
  const format = req.query.format || "pptx";
  const gen = formats[format];
  if (!gen) return res.status(400).json({ error: "Invalid format" });

  const buffer = await gen.fn(req.body);
  res.set({
    "Content-Type": gen.mime,
    "Content-Disposition": `attachment; filename="report.${format}"`,
  });
  res.send(buffer);
});
Enter fullscreen mode Exit fullscreen mode

The client calls POST /api/generate?format=pdf with the same JSON body to get a PDF. One codebase, one data structure, four output formats.

Production considerations

Concern Recommendation
Request body size Increase Express's JSON limit for schemas with base64 images: app.use(express.json({ limit: '10mb' }))
Timeout A 10-slide deck generates in ~200ms. Set req.setTimeout(30000) only for very large documents with many embedded images.
Concurrency generate() is CPU-bound and stateless. Use PM2 cluster mode or Node.js cluster module to utilize multiple cores.
Input validation Validate the JSON schema before passing to generate(). At minimum: verify slides is a non-empty array and each element has a type field.
Error handling Wrap generate() in try/catch. Invalid schemas throw descriptive errors. Return 400 with the error message.
Authentication Document generation endpoints should be authenticated. Use your existing Express auth middleware (JWT, session, API key).

Start generating PPTX in Express — read the quick start, explore the PPTX reference, or see the hosted API quick start.

Top comments (0)