Skip to main content

Guide

docx vs officegen vs pptxgenjs 2026

Compare docx, officegen, and pptxgenjs for generating Word and PowerPoint documents in Node.js. DOCX generation, tables, images, styles, TypeScript, and.

·PkgPulse Team·
0

TL;DR

docx is the best library for generating Word documents (.docx) in Node.js — clean TypeScript API, composable document model, no dependency on LibreOffice or Microsoft Word. pptxgenjs is the go-to for PowerPoint (.pptx) — rich slide API, charts, images, and themes. officegen is an older library that handles both DOCX and PPTX but is less maintained. For modern report generation: docx (Word) + pptxgenjs (PowerPoint). For templating existing documents: consider docxtemplater (fills templates) instead.

Key Takeaways

  • docx: ~450K weekly downloads — TypeScript-first, composable, modern DOCX generation
  • pptxgenjs: ~300K weekly downloads — full PowerPoint API, charts, images, themes
  • officegen: ~100K weekly downloads — legacy, generates both but less maintained
  • None of these require Microsoft Office — pure JavaScript .docx/.pptx file generation
  • For filling templates (not generating from scratch): use docxtemplater instead
  • Generated files open in Microsoft Office, LibreOffice, and Google Docs

docx

docx — TypeScript DOCX generation:

Basic document

import {
  Document,
  Paragraph,
  TextRun,
  HeadingLevel,
  AlignmentType,
  Packer,
} from "docx"
import fs from "fs"

// Build document using composable objects:
const doc = new Document({
  creator: "PkgPulse",
  title: "Package Health Report",
  description: "Weekly npm package health overview",
  sections: [
    {
      children: [
        // Heading:
        new Paragraph({
          text: "Package Health Report — March 2026",
          heading: HeadingLevel.HEADING_1,
        }),

        // Subtitle:
        new Paragraph({
          children: [
            new TextRun({
              text: "Generated by PkgPulse",
              italics: true,
              color: "888888",
            }),
          ],
        }),

        // Body paragraph:
        new Paragraph({
          alignment: AlignmentType.JUSTIFIED,
          children: [
            new TextRun("This report summarizes the health scores for the "),
            new TextRun({
              text: "top 10 npm packages",
              bold: true,
            }),
            new TextRun(" by weekly downloads as of February 2026."),
          ],
        }),
      ],
    },
  ],
})

// Save to file:
const buffer = await Packer.toBuffer(doc)
fs.writeFileSync("report.docx", buffer)

// Or: as a base64 string, Blob, or stream
const base64 = await Packer.toBase64String(doc)
const blob = await Packer.toBlob(doc)  // For browser download

Tables

import {
  Document,
  Table,
  TableRow,
  TableCell,
  Paragraph,
  TextRun,
  WidthType,
  BorderStyle,
  Packer,
} from "docx"

const packages = [
  { name: "react", downloads: "45M", score: 95, status: "Healthy" },
  { name: "lodash", downloads: "35M", score: 78, status: "Stable" },
  { name: "moment", downloads: "12M", score: 42, status: "Legacy" },
]

const table = new Table({
  width: { size: 100, type: WidthType.PERCENTAGE },
  rows: [
    // Header row:
    new TableRow({
      tableHeader: true,
      children: ["Package", "Downloads/Week", "Health Score", "Status"].map(
        (header) =>
          new TableCell({
            children: [
              new Paragraph({
                children: [new TextRun({ text: header, bold: true })],
              }),
            ],
          })
      ),
    }),

    // Data rows:
    ...packages.map(
      (pkg) =>
        new TableRow({
          children: [
            new TableCell({ children: [new Paragraph(pkg.name)] }),
            new TableCell({ children: [new Paragraph(pkg.downloads)] }),
            new TableCell({ children: [new Paragraph(String(pkg.score))] }),
            new TableCell({ children: [new Paragraph(pkg.status)] }),
          ],
        })
    ),
  ],
})

const doc = new Document({
  sections: [{ children: [table] }],
})

Styles and formatting

import {
  Document,
  Paragraph,
  TextRun,
  NumberingType,
  UnderlineType,
  PageOrientation,
  Packer,
} from "docx"

const doc = new Document({
  // Page setup:
  sections: [
    {
      properties: {
        page: {
          orientation: PageOrientation.LANDSCAPE,
          margin: {
            top: 720,    // twips (1 inch = 1440 twips)
            bottom: 720,
            left: 1440,
            right: 1440,
          },
        },
      },
      children: [
        // Bulleted list:
        new Paragraph({
          text: "Top packages this week:",
          numbering: {
            reference: "my-numbering",
            level: 0,
          },
        }),

        // Styled text:
        new Paragraph({
          children: [
            new TextRun({
              text: "react",
              bold: true,
              color: "FF8800",
              font: "Courier New",
              size: 24,  // half-points (24 = 12pt)
            }),
            new TextRun({ text: " — 45M weekly downloads" }),
          ],
        }),

        // Page break:
        new Paragraph({ pageBreakBefore: true }),
      ],
    },
  ],
})

Express endpoint (generate on demand)

import express from "express"
import { Document, Paragraph, Packer } from "docx"

const app = express()

app.get("/report/download", async (req, res) => {
  const doc = new Document({
    sections: [{
      children: [
        new Paragraph({ text: "PkgPulse Report", heading: "Heading1" }),
        new Paragraph({ text: `Generated: ${new Date().toLocaleDateString()}` }),
      ],
    }],
  })

  const buffer = await Packer.toBuffer(doc)

  res.setHeader(
    "Content-Type",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  )
  res.setHeader("Content-Disposition", "attachment; filename=report.docx")
  res.send(buffer)
})

pptxgenjs

pptxgenjs — PowerPoint generation for Node.js and browsers:

Basic presentation

import pptxgen from "pptxgenjs"

const prs = new pptxgen()

// Set presentation properties:
prs.layout = "LAYOUT_WIDE"  // 13.33" x 7.5"
prs.author = "PkgPulse"
prs.company = "PkgPulse Inc."
prs.title = "Package Health Report"

// Add a title slide:
const slide1 = prs.addSlide()

slide1.addText("Package Health Report", {
  x: 1,
  y: 1.5,
  w: "80%",
  h: 1.5,
  fontSize: 36,
  bold: true,
  color: "FF8800",
  align: "center",
})

slide1.addText("Q1 2026 — PkgPulse Analytics", {
  x: 1,
  y: 3.2,
  w: "80%",
  h: 0.5,
  fontSize: 18,
  color: "666666",
  align: "center",
})

// Add background:
slide1.background = { color: "F5F5F5" }

// Save:
await prs.writeFile({ fileName: "presentation.pptx" })
// Or: const buffer = await prs.write({ outputType: "nodebuffer" })

Tables in slides

const slide2 = prs.addSlide()
slide2.addText("Top Packages by Health Score", {
  x: 0.5, y: 0.3, w: 9, h: 0.6,
  fontSize: 24, bold: true,
})

const tableData = [
  [
    { text: "Package", options: { bold: true, fill: { color: "2D3748" }, color: "FFFFFF" } },
    { text: "Downloads", options: { bold: true, fill: { color: "2D3748" }, color: "FFFFFF" } },
    { text: "Score", options: { bold: true, fill: { color: "2D3748" }, color: "FFFFFF" } },
  ],
  [
    { text: "react" },
    { text: "45M/week" },
    { text: "95/100", options: { color: "22C55E" } },  // Green
  ],
  [
    { text: "lodash" },
    { text: "35M/week" },
    { text: "78/100", options: { color: "EAB308" } },  // Yellow
  ],
  [
    { text: "moment" },
    { text: "12M/week" },
    { text: "42/100", options: { color: "EF4444" } },  // Red
  ],
]

slide2.addTable(tableData, {
  x: 0.5,
  y: 1.2,
  w: 9,
  colW: [3, 3, 3],
  border: { type: "solid", color: "CCCCCC", pt: 1 },
  rowH: 0.5,
  fontSize: 14,
})

Charts

const slide3 = prs.addSlide()
slide3.addText("Download Trends — Top 5 Packages", {
  x: 0.5, y: 0.2, h: 0.6, fontSize: 20, bold: true,
})

// Bar chart:
slide3.addChart(prs.ChartType.bar, [
  {
    name: "Downloads (Millions/week)",
    labels: ["react", "lodash", "axios", "moment", "express"],
    values: [45, 35, 28, 12, 10],
  },
], {
  x: 0.5,
  y: 0.8,
  w: 9,
  h: 5.5,
  barDir: "col",
  chartColors: ["FF8800", "3B82F6", "22C55E", "EAB308", "EF4444"],
  valAxisMaxVal: 50,
  showValue: true,
})

Feature Comparison

Featuredocxpptxgenjsofficegen
Word (.docx)
PowerPoint (.pptx)
Excel (.xlsx)
Tables
Images
Charts
Styles/themesLimited
TypeScript⚠️ @types
ESM
Maintenance✅ Active✅ Active⚠️ Low
Browser support

When to Use Each

Choose docx if:

  • Generating Word documents (.docx) — invoices, reports, letters, contracts
  • TypeScript-first with a clean composable API
  • You need paragraph styling, tables, headers/footers, page layout control

Choose pptxgenjs if:

  • Generating PowerPoint presentations (.pptx) — slide decks, reports, charts
  • You need charts directly in slides
  • Marketing reports, data presentations, automated slide generation

Choose officegen if:

  • You need a single library for both DOCX and PPTX (though trade-offs apply)
  • Legacy codebase already using it

Consider docxtemplater instead if:

// When you want to fill existing Word templates rather than generate from scratch:
// docxtemplater takes a .docx template with {placeholders} and fills them:
// Template: "Dear {name}, your invoice total is {amount}..."
// Much simpler than building documents programmatically
// Good for: mail merge, invoice templates, letter templates

The docx Composable Document Model

Understanding why docx's API is designed the way it is helps you write cleaner document generation code. The library models a Word document as a tree of composable objects that mirror the OOXML specification (Office Open XML, the standard that defines the .docx format). A Document contains Section objects, sections contain Paragraph and Table objects, paragraphs contain TextRun objects, and tables contain TableRow objects containing TableCell objects. This hierarchy is rigid by design — Word's format is rigid — but the composability means you can build document sections as functions and compose them.

This composable pattern is particularly valuable for report generation workflows where different sections come from different data sources. A function that returns a Paragraph[] for an executive summary, another that returns a Table for raw data, and another that returns formatted TextRun objects for a footer can all be written and tested independently, then assembled into the final document in a single new Document({ sections: [{ children: [...execSummary, ...dataTable, ...footer] }] }) call. This functional approach to document construction is significantly easier to maintain than string template approaches.

Performance Considerations for High-Volume Document Generation

When generating Office documents at scale — invoices, reports, exports — the performance characteristics of each library matter. The docx library is pure JavaScript and generates documents synchronously up until the Packer.toBuffer() step, which returns a Promise. Under Node.js, generating a simple 1-5 page Word document takes roughly 10-50ms depending on content complexity. For an API endpoint that generates documents on demand, this is fast enough to serve synchronously. For batch generation of thousands of documents, consider using a worker thread pool so document generation doesn't block the event loop.

pptxgenjs is similarly synchronous in its document construction phase, with the async step being the final prs.write() or prs.writeFile() call. One performance consideration specific to pptxgenjs is chart generation: slides with charts require more processing time than slides with only text and tables, because chart data is serialized into a complex OOXML format. For presentations with many chart slides (10+), the generation time can exceed a second, which makes background processing via a queue the right architecture for on-demand presentation generation.

Handling Complex Formatting: Headers, Footers, and Sections

One area where docx's API depth becomes apparent is in complex document layout. Word documents support per-section headers and footers, which means a document can have different headers on the first page versus subsequent pages, and different headers in odd versus even page layouts. docx exposes this through the headers and footers properties on the section object.

Tables in Word have more complexity than most developers expect. Column widths in docx are specified in DXA units (twentieths of a point), which means a width of 2880 represents exactly two inches. The WidthType.DXA, WidthType.PERCENTAGE, and WidthType.AUTO options give you control over how widths are calculated. Merged cells require the columnSpan and rowSpan properties on TableCell, and the OOXML requirement that merged continuation cells still exist (just with vMerge flags) is handled automatically by docx. Getting multi-level nested tables right requires understanding that Word's rendering engine can handle three or four levels of nesting before visual quality degrades.

pptxgenjs and Chart Integration

pptxgenjs's chart API is one of its most distinguishing features. The library supports bar, line, pie, doughnut, scatter, bubble, and area charts natively, generating the complete OOXML DrawingML chart specification that PowerPoint requires. The chart data model takes arrays of labels and value series, and the library handles the internal formatting including axis labels, legend positioning, and color schemes.

One important technical nuance: pptxgenjs charts are embedded into the PPTX file as DrawingML XML, not as image files. This means the charts are fully editable in PowerPoint after generation — users can click a chart, see the underlying data table, and modify it. For business reporting where executives expect to be able to adjust generated presentations, this live-editability is a significant feature. Image-based chart embedding (using Canvas or SVG → PNG) would be static and non-editable.

The chartColors array in pptxgenjs accepts hex color strings without the leading #, which is a minor inconsistency compared to CSS convention. For brand-consistent reporting, keep a shared array of brand colors and pass it to every chart's chartColors property.

Document Security and Access Control

Neither docx, pptxgenjs, nor officegen natively supports the password protection or digital signature features available in Word and PowerPoint. These features are part of the ECMA 376 spec but are complex to implement and not commonly needed in server-side generation workflows. If you need password-protected documents — common in financial services, healthcare, and legal document workflows — the standard approach is to post-process the generated .docx file with a tool like LibreOffice in headless mode (libreoffice --headless --writer --convert-to docx) or use a dedicated document processing service.

For documents containing sensitive information that are generated and served via an API endpoint, the more practical security approach is to protect the API endpoint itself (authentication, authorization) and serve documents over HTTPS with appropriate Content-Security-Policy headers. Logging document access events and implementing short-lived download URLs (signed S3 URLs, or time-limited tokens) provides an audit trail without the complexity of password-protecting individual files.

Templating vs Programmatic Generation

The docx and pptxgenjs approach is fully programmatic — you construct every element in code. This is powerful but verbose for documents where the structure is largely fixed and only the data changes. For that pattern, docxtemplater is the correct tool: it takes an existing .docx or .pptx template with {placeholder} syntax and fills the placeholders with data, preserving all formatting, images, and layout from the template.

The tradeoff is control: docxtemplater is limited to the placeholders you define in the template, and complex conditional logic or dynamic tables require docxtemplater's Pro modules (paid). For documents with simple variable substitution — mail merge, certificate generation, standard contracts with varying party names — docxtemplater is significantly faster to implement than building the document programmatically. For documents where the structure itself varies based on data (dynamic number of sections, conditional content blocks, data-driven charts), programmatic generation with docx and pptxgenjs is the right architecture.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on docx v8.x, pptxgenjs v3.x, and officegen v0.6.x.

Compare document generation and utility packages on PkgPulse →

See also: pm2 vs node:cluster vs tsx watch and h3 vs polka vs koa 2026, better-sqlite3 vs libsql vs sql.js.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.