# Create DOCX

> Use when creating or generating a Word document (.docx) for a client, email, or report. Covers Windows setup, running docx-js scripts in a temp folder, cleanup, and formatting patterns (tables, bullets, callout boxes, screenshot placeholders). Triggers on requests like "create a Word doc", "generate a .docx", "make a report in Word", or "build a document for the client."

- Skill: `maskedcontrol/create-docx` (Agent Skill)
- Install (CLI): `npx skillmds@latest add maskedcontrol/create-docx`
- Raw SKILL.md: https://api.skillmd.com/api/skills/maskedcontrol/create-docx/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: MaskedControl (https://skillmd.com/u/maskedcontrol)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/maskedcontrol/create-docx

---


# 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")
```

## Single-Cell Table Helper Pattern

Single-cell tables (code blocks, callout boxes, prompt boxes) follow this structure. The closing sequence is the most common source of syntax errors — get it wrong and Node throws `Unexpected token '}'`.

```javascript
return new Table({
  width: { size: CW, type: WidthType.DXA }, columnWidths: [CW],
  rows: [new TableRow({ children: [new TableCell({   // opens: rows[, TableRow{, children[, TableCell{
    borders, width: { size: CW, type: WidthType.DXA },
    shading: { fill: "F4F4F4", type: ShadingType.CLEAR },
    margins: { top: 120, bottom: 120, left: 200, right: 200 },
    children: [/* paragraphs */]
  })]})   // closes: TableCell{, TableCell(, children[, TableRow{, TableRow(
  ]        // closes: rows[
  });      // closes: Table{, Table(
}
```

Closing sequence: `}` `)` `]` `}` `)` then `]` on next line, then `});`.
**Never write `})})]`** — it skips the `]` that closes `children:[` and crashes at parse time.

## 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 |
| SyntaxError: Unexpected token `}` in table helper | Missing `]` to close `children:[` of TableRow before closing `}` of TableRow args — use the single-cell table pattern above |

