---
name: docx-js-authoring
source: https://app.decimal.ai/s/docx-js-authoring@1/SKILL.md
source_sha256: 7aea2e41ea56
---

# docx-js authoring idioms

## Contract

Enforces the current public API of the `docx` npm package (docx-js) when building a `.docx`
in JavaScript/TypeScript. Apply to any code that constructs a `Document` from `require("docx")`
/ `import {...} from "docx"`. The rules below are the exact tokens the library expects; the
model's natural defaults diverge from most of them.

## Rules

1. **Page size — set it explicitly, in DXA (twips).** If you omit `properties.page.size`, the
   library uses its A4 default. For US documents pass US Letter in twips: `width: 12240`,
   `height: 15840` (1 inch = 1440 DXA). Use 1440 for each margin.
2. **Landscape — pass PORTRAIT dimensions, then flag it.** Keep `width: 12240` (short edge) and
   `height: 15840` (long edge) and add `orientation: PageOrientation.LANDSCAPE`. The library
   swaps them in the XML — do not pre-swap width and height yourself.
3. **Table widths — always `WidthType.DXA`, never `WidthType.PERCENTAGE`.** Percentage widths
   break in Google Docs. Set the table `width` as `{ size, type: WidthType.DXA }`, give the
   table a `columnWidths` array, AND repeat `width: { size, type: WidthType.DXA }` on every
   cell. The table's `size` must equal the sum of `columnWidths`, and each cell's `size` must
   match its column.
4. **Cell shading — `ShadingType.CLEAR`, never `ShadingType.SOLID`.** Shade a cell with
   `shading: { fill: "RRGGBB", type: ShadingType.CLEAR }`. `SOLID` renders as a black fill.
5. **Lists — a numbering config with `LevelFormat`, never a literal glyph.** Define bullets with
   `LevelFormat.BULLET` and ordered lists with `LevelFormat.DECIMAL` under `numbering.config`,
   then attach each paragraph via `numbering: { reference, level }`. Never put a `•`, `•`,
   `-`, or `1.` character into a `TextRun` to fake a list.
6. **Headings — `HeadingLevel.HEADING_1` … `HEADING_6` (or `TITLE`).** Set `heading:
   HeadingLevel.HEADING_1` on the paragraph. When overriding built-in styles, use the exact
   style IDs `"Heading1"`, `"Heading2"`, … and give each an `outlineLevel` so a table of
   contents can see it.
7. **Images — `ImageRun` with a REQUIRED `type`.** `new ImageRun({ type: "png", data,
   transformation: { width, height } })`. `type` is mandatory in the current API and must be
   one of `"png" | "jpg" | "jpeg" | "gif" | "bmp" | "svg"`.
8. **Page numbers — `PageNumber.CURRENT` / `PageNumber.TOTAL_PAGES`.** Put them in a footer run's
   `children`, e.g. `new TextRun({ children: [PageNumber.CURRENT] })`. There is no `"{PAGE}"`
   string field.
9. **Page breaks — `new PageBreak()` inside a `Paragraph`.** A standalone `PageBreak` is invalid
   XML: `new Paragraph({ children: [new PageBreak()] })`, or `pageBreakBefore: true`.
10. **Output — `Packer.toBuffer(doc)` (a Promise), or `Packer.toBlob` in the browser.** There is
    no `doc.save()` / `doc.write()` method.
11. **Newlines — never `"\n"` in a `TextRun`.** Each visual line is its own `Paragraph`.

## Worked examples

Each pair shows the model's wrong default (BEFORE) and the docx-js form (AFTER).

**Bullet list**
```javascript
// BEFORE — literal glyphs in text runs
new Paragraph({ children: [new TextRun("• First point")] })
// AFTER — numbering config drives the bullet
numbering: { config: [{ reference: "pts",
  levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT }] }] }
new Paragraph({ numbering: { reference: "pts", level: 0 }, children: [new TextRun("First point")] })
```

**Table width**
```javascript
// BEFORE — percentage width (breaks in Google Docs), no cell width
new Table({ width: { size: 100, type: WidthType.PERCENTAGE }, rows: [...] })
// AFTER — DXA on the table AND each cell, columnWidths sum to the table width
new Table({ width: { size: 9360, type: WidthType.DXA }, columnWidths: [4680, 4680], rows: [
  new TableRow({ children: [ new TableCell({ width: { size: 4680, type: WidthType.DXA },
    children: [new Paragraph("Cell")] }) ] }) ] })
```

**Cell shading**
```javascript
// BEFORE — SOLID renders black
shading: { fill: "D5E8F0", type: ShadingType.SOLID }
// AFTER
shading: { fill: "D5E8F0", type: ShadingType.CLEAR }
```

**Image**
```javascript
// BEFORE — pre-v8 API, now throws (no type)
new ImageRun({ data: fs.readFileSync("logo.png"), transformation: { width: 120, height: 60 } })
// AFTER — type is required
new ImageRun({ type: "png", data: fs.readFileSync("logo.png"), transformation: { width: 120, height: 60 } })
```

**Page setup + output**
```javascript
// BEFORE — implicit A4, and a save() that does not exist
const doc = new Document({ sections: [{ children }] });
doc.save("out.docx");
// AFTER — explicit US Letter in DXA, Packer output
const doc = new Document({ sections: [{
  properties: { page: { size: { width: 12240, height: 15840 },
    margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } }, children }] });
Packer.toBuffer(doc).then(buf => fs.writeFileSync("out.docx", buf));
```

## Edge cases & exceptions

- **A4 target.** If the document is genuinely A4, still set the size explicitly (`width: 11906`,
  `height: 16838`) rather than relying on the default — be explicit either way.
- **Full-width table on US Letter.** Content width with 1-inch margins is `12240 − 2880 = 9360`
  DXA; the table `size` and the sum of `columnWidths` both equal 9360.
- **Landscape content width** uses the long edge (15840) minus the two margins.
- **Continuing vs restarting numbering.** Reusing the same `reference` continues the count;
  a different `reference` restarts at 1.
- **Table of contents.** `new TableOfContents(...)` only sees paragraphs styled with
  `HeadingLevel`; custom run styles are ignored by the TOC.

## Do / Don't

- DO set `type: WidthType.DXA` on the table and every cell. DON'T use `WidthType.PERCENTAGE`.
- DO drive lists from a `LevelFormat` numbering config. DON'T hand-type `•`, `•`, or `1.`.
- DO use `ShadingType.CLEAR`. DON'T use `ShadingType.SOLID`.
- DO give `ImageRun` a `type`. DON'T omit it.
- DO set page size in DXA (12240 × 15840 for US Letter). DON'T rely on the A4 default.
- DO render with `Packer.toBuffer`. DON'T call `doc.save()`.
- DO put a `PageBreak` inside a `Paragraph`. DON'T emit it as a top-level child.

## Common mistakes

- Faking bullets/numbers with glyph `TextRun`s instead of a `LevelFormat` numbering config.
- `WidthType.PERCENTAGE` (or omitting width) so the table collapses in Google Docs.
- `ShadingType.SOLID`, producing black cells.
- Omitting the required `ImageRun` `type`.
- Leaving page size implicit (silent A4) or pre-swapping width/height for landscape.
- Inventing `doc.save()` instead of `Packer.toBuffer`.
- `"\n"` inside a run instead of separate paragraphs.

## Quick checklist

- [ ] Page size explicit in DXA (US Letter 12240 × 15840, margins 1440).
- [ ] Landscape = portrait dims + `PageOrientation.LANDSCAPE` (no manual swap).
- [ ] Tables: `WidthType.DXA` on table + cells; `size` = sum of `columnWidths`.
- [ ] Shading `ShadingType.CLEAR`.
- [ ] Lists via `LevelFormat.BULLET` / `LevelFormat.DECIMAL` numbering config.
- [ ] Headings via `HeadingLevel`; `ImageRun` has `type`.
- [ ] `PageNumber.CURRENT` in the footer; `PageBreak` inside a `Paragraph`.
- [ ] Output via `Packer.toBuffer`.
