create-docx

Word documents are generated by writing a JavaScript file that uses the docx npm package, running it with Node, then cleaning up the temp files. The docx package must be installed locally in the same folder as the script - global install does not work with require('docx').

maskedcontrol/skills3 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "create-docx"
description: "Word documents are generated by writing a JavaScript file that uses the docx npm package, running it with Node, then cleaning up the temp files. The docx package must be installed locally in the same folder as the script - global install does not work with require('docx')."
license: "MIT"
---

# Create Word Documents (docx-js on Windows)

## Overview

Word documents are generated by writing a JavaScript file that uses the `docx` npm package,
running it with Node, then cleaning up the temp files. The `docx` package must be installed
**locally** in the same folder as the script - global install does not work with `require('docx')`.

The full docx-js API reference is in the `example-skills:docx` skill. Load it for syntax on
tables, images, headers/footers, tracked changes, and anything not covered here.

## Process

### 1. Write the script

Write the JS file to a working location (e.g. Desktop). Output path should be absolute.

```javascript
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
        HeadingLevel, AlignmentType, LevelFormat, BorderStyle, WidthType,
        ShadingType, VerticalAlign, PageBreak } = require('docx');
const fs = require('fs');
// ... build doc ...
Packer.toBuffer(doc).then(buf => {
  fs.writeFileSync("C:/Users/rosem/Desktop/output.docx", buf);
  console.log("Done");
}).catch(err => { console.error(err); process.exit(1); });
```

### 2. Install and run

```powershell
cd C:/Users/rosem/Desktop
npm init -y
npm install docx
node build-doc.js
```

### 3. Clean up

```powershell
Remove-Item -Recurse -Force build-doc.js, package.json, package-lock.json, node_modules
```

## Windows Validation Caveat

The `validate.py` script from `example-skills:docx` throws a `UnicodeEncodeError` on Windows
consoles (cp1252 can't encode the arrow character it prints). This is a validator bug, not a
document problem. Skip validation - open the file in Word to verify instead.

## Page Setup (Always Set Explicitly)

docx-js defaults to A4. Always override:

```javascript
sections: [{
  properties: {
    page: {
      size: { width: 12240, height: 15840 },        // US Letter
      margin: { top: 1080, right: 1080, bottom: 1080, left: 1080 } // 0.75" margins
    }
  },
  children: [...]
}]
// Content width = 12240 - (2 x 1080) = 10080 DXA
```

## Numbering (Never Use Unicode Bullets Directly)

```javascript
// Define once in Document
numbering: {
  config: [
    { reference: "bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "•",
        alignment: AlignmentType.LEFT,
        style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
    { reference: "steps",   levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.",
        alignment: AlignmentType.LEFT,
        style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
  ]
}

// Use on paragraphs
new Paragraph({ numbering: { reference: "bullets", level: 0 }, children: [...] })
```

Use a separate reference for bullets inside table cells (avoids numbering continuation issues):

```javascript
{ reference: "cell-bullets", levels: [{ level: 0, format: LevelFormat.BULLET, text: "•",
    alignment: AlignmentType.LEFT,
    style: { paragraph: { indent: { left: 360, hanging: 180 } } } }] }
```

## Tables (Dual Widths Required)

Both `columnWidths` on the table AND `width` on each cell. They must match.
Always use `WidthType.DXA` - never `WidthType.PERCENTAGE` (breaks in Google Docs).
Always use `ShadingType.CLEAR` - never SOLID (causes black cell backgrounds).

```javascript
const border = { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" };
const borders = { top: border, bottom: border, left: border, right: border };
const col1 = 5040, col2 = 5040; // must sum to content width (10080)

new Table({
  width: { size: 10080, type: WidthType.DXA },
  columnWidths: [col1, col2],
  rows: [new TableRow({ children: [
    new TableCell({
      borders,
      width: { size: col1, type: WidthType.DXA },
      shading: { fill: "F0F0F0", type: ShadingType.CLEAR },
      margins: { top: 80, bottom: 80, left: 120, right: 120 },
      children: [new Paragraph({ children: [new TextRun("Cell content")] })]
    }),
  ]})]
})
```

## Screenshot Placeholder

For documents that will have screenshots inserted manually:

```javascript
function screenshotBox(description) {
  const br = { style: BorderStyle.SINGLE, size: 8, color: "999999" };
  const borders = { top: br, bottom: br, left: br, right: br };
  return new Table({
    width: { size: 10080, type: WidthType.DXA },
    columnWidths: [10080],
    rows: [new TableRow({ children: [new TableCell({
      borders,
      width: { size: 10080, type: WidthType.DXA },
      shading: { fill: "DEDEDE", type: ShadingType.CLEAR },
      margins: { top: 280, bottom: 280, left: 280, right: 280 },
      verticalAlign: VerticalAlign.CENTER,
      children: [
        new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 40, after: 60 },
          children: [new TextRun({ text: "[ INSERT SCREENSHOT ]", font: "Arial",
            size: 20, bold: true, color: "666666" })] }),
        new Paragraph({ alignment: AlignmentType.CENTER,
          children: [new TextRun({ text: description, font: "Arial",
            size: 18, italics: true, color: "666666" })] })
      ]
    })})]
  });
}
```

## Callout Box

Coloured border box with a bold title and bulleted lines:

```javascript
function callout(title, lines, fill, borderColor) {
  const br = { style: BorderStyle.SINGLE, size: 14, color: borderColor };
  const borders = { top: br, bottom: br, left: br, right: br };
  return new Table({
    width: { size: 10080, type: WidthType.DXA },
    columnWidths: [10080],
    rows: [new TableRow({ children: [new TableCell({
      borders,
      width: { size: 10080, type: WidthType.DXA },
      shading: { fill, type: ShadingType.CLEAR },
      margins: { top: 120, bottom: 120, left: 240, right: 240 },
      children: [
        new Paragraph({ spacing: { before: 40, after: 80 },
          children: [new TextRun({ text: title, font: "Arial", size: 22, bold: true, color: "404040" })] }),
        ...lines.map(line => new Paragraph({
          numbering: { reference: "cell-bullets", level: 0 },
          spacing: { before: 40, after: 40 },
          children: [new TextRun({ text: line, font: "Arial", size: 22, color: "404040" })]
        }))
      ]
    })})]
  });
}
// Usage: callout("What to ask:", ["Question one", "Question two"], "FFF2CC", "FFC000")
```

## Common Mistakes

| Mistake | Fix |
|---|---|
| `require('docx')` fails after `npm install -g` | Install locally: `cd Desktop && npm init -y && npm install docx` |
| Black table cell backgrounds | Use `ShadingType.CLEAR` not `SOLID` |
| Table renders incorrectly | Set `columnWidths` on table AND `width` on each cell, both in DXA |
| `WidthType.PERCENTAGE` used | Switch to `WidthType.DXA` - percentage breaks in Google Docs |
| PageBreak causes invalid XML | Wrap it: `new Paragraph({ children: [new PageBreak()] })` |
| Bullet shows as literal text | Never `new TextRun("• item")` - use numbering config |
| Em dashes (--) appear in Word | Do not use `--` or `—` in content - rephrase the sentence instead |
| Validator crashes on Windows | Known encoding bug in validate.py - open in Word instead |
| node_modules left on Desktop | Always run cleanup step after generating |

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY