office

Office Document Generation

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "office" with this command: npx skills add jezweb/claude-skills/jezweb-claude-skills-office

Office Document Generation

Status: Production Ready Last Updated: 2026-01-12 Dependencies: None (pure JavaScript libraries) Latest Versions: docx@9.5.0, xlsx@0.18.5, pdf-lib@1.17.1, pptxgenjs@4.0.1

Overview

Generate Microsoft Office documents and PDFs programmatically with TypeScript. All libraries are pure JavaScript with zero native dependencies, enabling universal runtime support:

Format Library Workers Browser Node.js

DOCX docx

✅ ✅ ✅

XLSX xlsx (SheetJS) ✅ ✅ ✅

PDF pdf-lib

✅ ✅ ✅

PPTX pptxgenjs

⚠️* ✅ ✅

*PPTX in Workers: Works for local images/data. Remote image fetching needs workaround (uses https module).

Quick Start

Installation

Install all four (or pick what you need)

npm install docx xlsx pdf-lib pptxgenjs

Create a Word Document (30 seconds)

import { Document, Packer, Paragraph, TextRun } from 'docx'; import { writeFileSync } from 'fs';

const doc = new Document({ sections: [{ children: [ new Paragraph({ children: [ new TextRun({ text: 'Hello World', bold: true, size: 48 }), ], }), ], }], });

// Node.js: Save to file const buffer = await Packer.toBuffer(doc); writeFileSync('hello.docx', buffer);

// Browser/Workers: Get as blob const blob = await Packer.toBlob(doc);

Create an Excel Spreadsheet (30 seconds)

import * as XLSX from 'xlsx';

// Create workbook with data const data = [ ['Name', 'Amount', 'Date'], ['Invoice #1', 1500, '2026-01-12'], ['Invoice #2', 2300, '2026-01-13'], ];

const worksheet = XLSX.utils.aoa_to_sheet(data); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 'Invoices');

// Node.js: Save to file XLSX.writeFile(workbook, 'invoices.xlsx');

// Browser/Workers: Get as buffer const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });

Create a PDF (30 seconds)

import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';

const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage([612, 792]); // Letter size const font = await pdfDoc.embedFont(StandardFonts.Helvetica);

page.drawText('Hello World', { x: 50, y: 700, size: 24, font, color: rgb(0, 0, 0), });

// Get as bytes (works everywhere) const pdfBytes = await pdfDoc.save();

// Node.js: Save to file writeFileSync('hello.pdf', pdfBytes);

Create a PowerPoint (30 seconds)

import pptxgen from 'pptxgenjs';

const pptx = new pptxgen(); pptx.author = 'Your Name'; pptx.title = 'Sample Presentation';

// Add a slide const slide = pptx.addSlide(); slide.addText('Hello World', { x: 1, y: 1, w: 8, h: 1.5, fontSize: 36, bold: true, color: '363636', });

// Node.js: Save to file await pptx.writeFile({ fileName: 'hello.pptx' });

// Browser: Trigger download await pptx.writeFile({ fileName: 'hello.pptx' });

DOCX: Word Documents

Key Concepts

The docx package uses a builder pattern:

  • Document - The root container

  • Section - Contains pages (most docs have 1 section)

  • Paragraph - A line/block of text

  • TextRun - Formatted text within a paragraph

  • Table - Grid of TableRow → TableCell → Paragraph

Document with Headings and Paragraphs

import { Document, Packer, Paragraph, TextRun, HeadingLevel } from 'docx';

const doc = new Document({ sections: [{ children: [ new Paragraph({ text: 'Monthly Report', heading: HeadingLevel.HEADING_1, }), new Paragraph({ children: [ new TextRun('This is a '), new TextRun({ text: 'bold', bold: true }), new TextRun(' and '), new TextRun({ text: 'italic', italics: true }), new TextRun(' text example.'), ], }), ], }], });

Tables

import { Document, Table, TableRow, TableCell, Paragraph, WidthType } from 'docx';

const table = new Table({ width: { size: 100, type: WidthType.PERCENTAGE }, rows: [ new TableRow({ children: [ new TableCell({ children: [new Paragraph('Header 1')] }), new TableCell({ children: [new Paragraph('Header 2')] }), ], }), new TableRow({ children: [ new TableCell({ children: [new Paragraph('Data 1')] }), new TableCell({ children: [new Paragraph('Data 2')] }), ], }), ], });

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

Images

import { Document, Paragraph, ImageRun } from 'docx'; import { readFileSync } from 'fs';

const imageBuffer = readFileSync('logo.png');

const doc = new Document({ sections: [{ children: [ new Paragraph({ children: [ new ImageRun({ data: imageBuffer, transformation: { width: 200, height: 100 }, type: 'png', }), ], }), ], }], });

Export Patterns

// Node.js - Save to file import { writeFileSync } from 'fs'; const buffer = await Packer.toBuffer(doc); writeFileSync('document.docx', buffer);

// Browser - Download const blob = await Packer.toBlob(doc); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'document.docx'; a.click();

// Cloudflare Workers - Return as Response const buffer = await Packer.toBuffer(doc); return new Response(buffer, { headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition': 'attachment; filename="document.docx"', }, });

XLSX: Excel Spreadsheets

Key Concepts

SheetJS (xlsx) uses utility functions:

  • Workbook - Container for sheets

  • Worksheet - Single sheet (grid of cells)

  • utils.aoa_to_sheet - Array of arrays → worksheet

  • utils.json_to_sheet - JSON array → worksheet

  • utils.book_new/book_append_sheet - Create/add sheets

From Array of Arrays

import * as XLSX from 'xlsx';

const data = [ ['Product', 'Price', 'Quantity', 'Total'], ['Widget A', 10, 5, { f: 'B2C2' }], // Formula ['Widget B', 15, 3, { f: 'B3C3' }], ['', '', 'Grand Total:', { f: 'SUM(D2:D3)' }], ];

const ws = XLSX.utils.aoa_to_sheet(data); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Sales');

From JSON

const invoices = [ { id: 1, customer: 'Acme Corp', amount: 1500, date: '2026-01-12' }, { id: 2, customer: 'Globex', amount: 2300, date: '2026-01-13' }, ];

const ws = XLSX.utils.json_to_sheet(invoices); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Invoices');

Column Widths

// Set column widths (in characters) ws['!cols'] = [ { wch: 10 }, // Column A { wch: 20 }, // Column B { wch: 15 }, // Column C ];

Multiple Sheets

const wb = XLSX.utils.book_new();

const summaryData = [['Total Sales', 3800]]; const detailData = [['Item', 'Amount'], ['Item 1', 1500], ['Item 2', 2300]];

XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(summaryData), 'Summary'); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(detailData), 'Details');

Export Patterns

// Node.js - Save to file XLSX.writeFile(workbook, 'report.xlsx');

// Browser/Workers - Get buffer const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });

// Cloudflare Workers - Return as Response return new Response(buffer, { headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Disposition': 'attachment; filename="report.xlsx"', }, });

PDF: PDF Documents

Key Concepts

pdf-lib uses a page-based approach:

  • PDFDocument - The PDF file

  • PDFPage - Individual page

  • Coordinates - Origin at BOTTOM-LEFT (not top-left!)

  • Units - Points (72 points = 1 inch)

Standard Page Sizes

// Common sizes in points [width, height] const LETTER = [612, 792]; // 8.5" x 11" const A4 = [595.28, 841.89]; // 210mm x 297mm const LEGAL = [612, 1008]; // 8.5" x 14"

Text and Fonts

import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';

const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage([612, 792]);

// Embed standard fonts const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica); const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);

// Draw text (y=0 is BOTTOM of page) page.drawText('Invoice #12345', { x: 50, y: 750, // Near top of page size: 24, font: helveticaBold, color: rgb(0, 0, 0), });

page.drawText('Thank you for your business!', { x: 50, y: 50, // Near bottom of page size: 12, font: helvetica, color: rgb(0.5, 0.5, 0.5), });

Drawing Shapes

// Rectangle page.drawRectangle({ x: 50, y: 600, width: 200, height: 100, borderColor: rgb(0, 0, 0), borderWidth: 1, color: rgb(0.95, 0.95, 0.95), // Fill color });

// Line page.drawLine({ start: { x: 50, y: 500 }, end: { x: 550, y: 500 }, thickness: 1, color: rgb(0, 0, 0), });

Images

// From URL/fetch const imageBytes = await fetch('https://example.com/logo.png').then(r => r.arrayBuffer()); const image = await pdfDoc.embedPng(imageBytes);

// Or embedJpg for JPEG // const image = await pdfDoc.embedJpg(imageBytes);

page.drawImage(image, { x: 50, y: 700, width: 100, height: 50, });

Fill PDF Forms

import { PDFDocument } from 'pdf-lib';

// Load existing PDF with form const existingPdfBytes = await fetch('form.pdf').then(r => r.arrayBuffer()); const pdfDoc = await PDFDocument.load(existingPdfBytes);

// Get form and fields const form = pdfDoc.getForm(); const nameField = form.getTextField('customer_name'); const dateField = form.getTextField('date');

// Fill fields nameField.setText('John Doe'); dateField.setText('2026-01-12');

// Flatten form (make fields non-editable) form.flatten();

const pdfBytes = await pdfDoc.save();

Merge PDFs

import { PDFDocument } from 'pdf-lib';

const pdf1Bytes = await fetch('doc1.pdf').then(r => r.arrayBuffer()); const pdf2Bytes = await fetch('doc2.pdf').then(r => r.arrayBuffer());

const mergedPdf = await PDFDocument.create(); const pdf1 = await PDFDocument.load(pdf1Bytes); const pdf2 = await PDFDocument.load(pdf2Bytes);

// Copy all pages from both documents const pages1 = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices()); const pages2 = await mergedPdf.copyPages(pdf2, pdf2.getPageIndices());

pages1.forEach(page => mergedPdf.addPage(page)); pages2.forEach(page => mergedPdf.addPage(page));

const mergedBytes = await mergedPdf.save();

PPTX: PowerPoint Presentations

Key Concepts

pptxgenjs uses a slide-based approach:

  • Presentation - The PPTX file (new pptxgen())

  • Slide - Individual slide (addSlide())

  • Elements - Text, images, shapes, tables, charts

  • Units - Inches by default (can change to cm/points)

Basic Presentation

import pptxgen from 'pptxgenjs';

const pptx = new pptxgen(); pptx.author = 'Author Name'; pptx.title = 'Presentation Title'; pptx.subject = 'Subject'; pptx.company = 'Company';

// Title slide const titleSlide = pptx.addSlide(); titleSlide.addText('Quarterly Report', { x: 0.5, y: 2, w: 9, h: 1, fontSize: 44, bold: true, color: '0066CC', align: 'center', }); titleSlide.addText('Q1 2026', { x: 0.5, y: 3.5, w: 9, h: 0.5, fontSize: 24, color: '666666', align: 'center', });

Slide with Bullet Points

const contentSlide = pptx.addSlide();

// Title contentSlide.addText('Key Highlights', { x: 0.5, y: 0.5, w: 9, h: 0.8, fontSize: 32, bold: true, color: '333333', });

// Bullet points contentSlide.addText([ { text: 'Revenue up 25% YoY', options: { bullet: true, fontSize: 20 } }, { text: 'Customer base grew to 10,000', options: { bullet: true, fontSize: 20 } }, { text: 'New product launch successful', options: { bullet: true, fontSize: 20 } }, { text: 'Expanded to 5 new markets', options: { bullet: true, fontSize: 20 } }, ], { x: 0.5, y: 1.5, w: 8, h: 4, valign: 'top' });

Tables

const tableSlide = pptx.addSlide();

tableSlide.addText('Sales Summary', { x: 0.5, y: 0.5, w: 9, h: 0.8, fontSize: 28, bold: true, });

const tableData = [ [{ text: 'Region', options: { bold: true, fill: '0066CC', color: 'FFFFFF' } }, { text: 'Q1', options: { bold: true, fill: '0066CC', color: 'FFFFFF' } }, { text: 'Q2', options: { bold: true, fill: '0066CC', color: 'FFFFFF' } }], ['North America', '$2.5M', '$2.8M'], ['Europe', '$1.8M', '$2.1M'], ['Asia Pacific', '$1.2M', '$1.5M'], ];

tableSlide.addTable(tableData, { x: 0.5, y: 1.5, w: 9, border: { pt: 1, color: 'CCCCCC' }, fontFace: 'Arial', fontSize: 14, align: 'center', valign: 'middle', });

Charts

const chartSlide = pptx.addSlide();

chartSlide.addText('Revenue Trend', { x: 0.5, y: 0.5, w: 9, h: 0.6, fontSize: 28, bold: true, });

chartSlide.addChart(pptx.ChartType.line, [ { name: 'Revenue', labels: ['Jan', 'Feb', 'Mar', 'Apr'], values: [100, 120, 150, 180] }, { name: 'Expenses', labels: ['Jan', 'Feb', 'Mar', 'Apr'], values: [80, 85, 90, 95] }, ], { x: 0.5, y: 1.2, w: 9, h: 4, showLegend: true, legendPos: 'b', showTitle: false, });

Images

import { readFileSync } from 'fs';

const imageSlide = pptx.addSlide();

// From file (Node.js) imageSlide.addImage({ path: 'logo.png', x: 0.5, y: 0.5, w: 2, h: 1, });

// From base64 const imageBase64 = readFileSync('chart.png').toString('base64'); imageSlide.addImage({ data: image/png;base64,${imageBase64}, x: 0.5, y: 2, w: 4, h: 3, });

// From URL (Node.js only - uses https module) imageSlide.addImage({ path: 'https://example.com/image.png', x: 5, y: 2, w: 4, h: 3, });

Shapes

const shapeSlide = pptx.addSlide();

// Rectangle shapeSlide.addShape(pptx.ShapeType.rect, { x: 0.5, y: 0.5, w: 3, h: 2, fill: { color: '0066CC' }, line: { color: '004499', pt: 2 }, });

// Circle/Oval shapeSlide.addShape(pptx.ShapeType.ellipse, { x: 4, y: 0.5, w: 2, h: 2, fill: { color: '00AA00' }, });

// Arrow shapeSlide.addShape(pptx.ShapeType.rightArrow, { x: 1, y: 3, w: 3, h: 1, fill: { color: 'FF6600' }, });

Slide Layouts and Masters

// Define a master slide pptx.defineSlideMaster({ title: 'COMPANY_MASTER', background: { color: 'FFFFFF' }, objects: [ { text: { text: 'Company Name', options: { x: 0.5, y: 0.2, w: 4, h: 0.3, fontSize: 10, color: '999999' } } }, { line: { x: 0.5, y: 0.6, w: 9, h: 0, line: { color: '0066CC', pt: 2 } } }, ], });

// Use the master const slide = pptx.addSlide({ masterName: 'COMPANY_MASTER' });

Export Patterns

// Node.js - Save to file await pptx.writeFile({ fileName: 'presentation.pptx' });

// Browser - Trigger download await pptx.writeFile({ fileName: 'presentation.pptx' });

// Get as base64 (for email, API, etc.) const base64 = await pptx.write({ outputType: 'base64' });

// Get as Blob (browser) const blob = await pptx.write({ outputType: 'blob' });

// Get as ArrayBuffer const arrayBuffer = await pptx.write({ outputType: 'arraybuffer' });

// Cloudflare Workers - Return as Response const arrayBuffer = await pptx.write({ outputType: 'arraybuffer' }); return new Response(arrayBuffer, { headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'Content-Disposition': 'attachment; filename="presentation.pptx"', }, });

Cloudflare Workers Integration

All four libraries work in Cloudflare Workers (PPTX with caveats for remote images).

DOCX Generation Endpoint

import { Hono } from 'hono'; import { Document, Packer, Paragraph, TextRun } from 'docx';

const app = new Hono();

app.get('/api/invoice/:id', async (c) => { const invoiceId = c.req.param('id');

const doc = new Document({ sections: [{ children: [ new Paragraph({ children: [new TextRun({ text: Invoice #${invoiceId}, bold: true, size: 48 })], }), ], }], });

const buffer = await Packer.toBuffer(doc);

return new Response(buffer, { headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition': attachment; filename="invoice-${invoiceId}.docx", }, }); });

export default app;

HTML to PDF with Browser Rendering

For complex layouts with CSS, use Cloudflare Browser Rendering (paid feature):

import puppeteer from '@cloudflare/puppeteer';

export default { async fetch(request, env) { const browser = await puppeteer.launch(env.BROWSER); const page = await browser.newPage();

// Set HTML content
await page.setContent(`
  <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; padding: 40px; }
        h1 { color: #333; }
        table { width: 100%; border-collapse: collapse; }
        td, th { border: 1px solid #ddd; padding: 8px; }
      </style>
    </head>
    <body>
      <h1>Invoice #12345</h1>
      <table>
        <tr><th>Item</th><th>Amount</th></tr>
        <tr><td>Service</td><td>$500</td></tr>
      </table>
    </body>
  </html>
`);

const pdf = await page.pdf({ format: 'A4' });
await browser.close();

return new Response(pdf, {
  headers: {
    'Content-Type': 'application/pdf',
    'Content-Disposition': 'attachment; filename="invoice.pdf"',
  },
});

}, };

wrangler.jsonc binding:

{ "browser": { "binding": "BROWSER" } }

Critical Rules

Always Do

✅ Use await Packer.toBuffer() for DOCX (it's async) ✅ Remember PDF coordinates start at BOTTOM-LEFT ✅ Use type: 'buffer' for XLSX in Workers/browser ✅ Embed fonts in PDF before using them ✅ Set proper Content-Type headers for downloads ✅ Use await pptx.writeFile() or await pptx.write() for PPTX ✅ Use base64 images in PPTX for Workers (avoid remote URLs)

Never Do

❌ Use Packer.toBuffer() without await (returns Promise) ❌ Assume PDF y=0 is at top (it's at bottom) ❌ Use writeFile in Workers (use Response instead) ❌ Forget to set Content-Disposition for downloads ❌ Use Node.js fs module in browser/Workers ❌ Use PPTX path URLs in Workers (https module not available)

Known Issues Prevention

Issue #1: DOCX Packer Returns Promise

Error: [object Promise] in file or empty document Why: Packer.toBuffer() is async but called without await Prevention: Always await Packer.toBuffer(doc)

Issue #2: PDF Text at Wrong Position

Error: Text appears at bottom of page or off-page Why: PDF coordinates have origin at bottom-left, not top-left Prevention: For text near top, use high y values (e.g., y=750 for letter size)

Issue #3: XLSX Formula Not Calculating

Error: Cell shows formula text instead of result Why: SheetJS doesn't execute formulas, Excel does on open Prevention: This is expected - formulas calculate when opened in Excel

Issue #4: Workers DOCX/PDF Returns Empty

Error: Downloaded file is empty or corrupted Why: Returning wrong type or missing Content-Type header Prevention: Return buffer directly with proper headers

Issue #5: PPTX Remote Images Fail in Workers

Error: https is not defined or image not appearing Why: pptxgenjs uses Node.js https module for remote images Prevention: Use base64 data URIs or local images in Workers environment

Using Bundled Resources

Templates (templates/)

  • docx-basic.ts

  • Complete Word document with headings, tables, images

  • xlsx-basic.ts

  • Excel workbook with formulas and formatting

  • pdf-basic.ts

  • PDF with text, images, shapes

  • pptx-basic.ts

  • PowerPoint with slides, charts, tables

  • workers-pdf.ts

  • Cloudflare Workers PDF generation example

References (references/)

  • docx-api.md

  • Quick reference for docx npm package

  • xlsx-api.md

  • Quick reference for SheetJS functions

  • pdf-lib-api.md

  • Quick reference for pdf-lib methods

  • pptxgenjs-api.md

  • Quick reference for pptxgenjs

Scripts (scripts/)

  • verify-deps.sh
  • Check library versions are current

Limitations vs Anthropic's Official Skills

This skill focuses on document creation with portable TypeScript libraries:

Feature This Skill Anthropic's Skills

Create DOCX/XLSX/PDF/PPTX ✅ ✅

Works in Cloudflare Workers ✅ ❌

Plugin installable ✅ ❌

TypeScript-first ✅ ❌ (Python)

Edit existing DOCX with tracked changes ❌ ✅

Excel formula validation ❌ ✅

OCR scanned PDFs ❌ ✅

For advanced editing scenarios (tracked changes, formula validation), consider Anthropic's official skills with Python tooling.

Package Versions (Verified 2026-01-12)

{ "dependencies": { "docx": "^9.5.0", "xlsx": "^0.18.5", "pdf-lib": "^1.17.1", "pptxgenjs": "^4.0.1" } }

Official Documentation

Complete Setup Checklist

  • Installed required packages (npm install docx xlsx pdf-lib )

  • Verified import statements match runtime (Node.js vs browser)

  • Used proper export method for target environment

  • Set Content-Type headers for HTTP responses

  • Tested document opens correctly in target application

  • Confirmed Workers compatibility (if deploying to edge)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

tailwind-v4-shadcn

No summary provided by upstream source.

Repository SourceNeeds Review
2.7K-jezweb
General

tanstack-query

No summary provided by upstream source.

Repository SourceNeeds Review
2.5K-jezweb
General

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review
General

zustand-state-management

No summary provided by upstream source.

Repository SourceNeeds Review
1.2K-jezweb