better docx
Usage

Bullets and Numbering

Bullets and Numbering requires an understanding of Sections and Paragraphs.

docx is quite flexible in its bullets and numbering system, allowing the user great freedom in how bullets and numbers are to be styled and displayed. E.g., numbers can be shown using Arabic numerals, roman numerals, or even ordinal words ("one", "two", "three", ...). The format also supports re-using bullets/numbering styles throughout the document, so that different lists using the same style need not redefine them.

Configuration

Numbering is configured by adding config into Document:

new Document({
    numbering: {
        config: [...]
    }
})

Each config entry includes the following properties:

PropertyTypeNotesPossible Values
referencestringRequiredA unique string
levelsILevelOptions[]Requireda series of levels which form a sequence starting at 0 indicating the top-level list look and increasing from there to describe the sublists, then sub-sublists, etc

Level Options

Levels define the numbering definition itself, what it looks like, the indention, the alignment and the style. The reason why it is an array is because it allows the ability to create sub-lists. A sub list will have a different configuration because you may want the sub-list to have a different indentation or different bullet.

PropertyTypeNotesPossible Values
levelnumberRequiredThe list level this definition is for. 0 is for the root level, 1 is for a sub list, 2 is for a sub-sub-list etc.
formatNumberFormatOptionalAn OOXML number format such as DECIMAL, UPPER_ROMAN, LOWER_LETTER, ORDINAL, or BULLET. NumberFormat contains the complete supported value set.
textstringOptionalA unique string to describe the shape of the bullet
alignmentstringOptionalSTART, CENTER, END, BOTH, MEDIUM_KASHIDA, DISTRIBUTE, NUM_TAB, HIGH_KASHIDA, LOW_KASHIDA, THAI_DISTRIBUTE, LEFT, RIGHT, JUSTIFIED. Defaults to START
startnumberOptionalThe value this level starts counting at. Defaults to 1
suffixLevelSuffixOptionalThe character placed between the number/bullet and the paragraph text: LevelSuffix.NOTHING, LevelSuffix.SPACE or LevelSuffix.TAB. If omitted, Word uses a tab
isLegalNumberingStylebooleanOptionalWhen true, displays all preceding level numbers in this level's text in decimal ("legal" numbering), e.g. a %1 from an UPPER_ROMAN parent level renders as 1 instead of I
stylestringOptionalSections

For example, a level that starts counting at 5, puts a space between the number and the text, and renders parent levels in decimal:

{
    level: 1,
    format: NumberFormat.DECIMAL,
    text: "%1.%2",
    start: 5,
    suffix: LevelSuffix.SPACE,
    isLegalNumberingStyle: true,
}

Note: NumberFormat.CUSTOM maps to the OOXML custom number format, which is meant to be paired with a separate format descriptor attribute. The library does not emit that descriptor, so CUSTOM may not render as expected in word processors — prefer one of the concrete formats above. LevelFormat remains as a deprecated compatibility alias.

Using ordered lists in docx

Add a numbering section to the Document to numbering style, define your levels. Use NumberFormat.UPPER_ROMAN for the format in levels:

const doc = new Document({
    ...
    numbering: {
        config: [
            {
                reference: "my-numbering",
                levels: [
                    {
                        level: 0,
                        format: NumberFormat.UPPER_ROMAN,
                        text: "%1",
                        alignment: AlignmentType.START,
                        style: {
                            paragraph: {
                                indent: { left: 2880, hanging: 2420 },
                            },
                        },
                    },
                    ...
                ],
            },
        ],
    },
    ...
});

And then on a Paragraph, we can add use the numbering created:

new Paragraph({
    text: "Hey you!",
    numbering: {
        reference: "my-numbering",
        level: 0,
    },
}),

Numbering options

Along with reference and level, the numbering object supports an optional instance property:

PropertyTypeNotes
referencestringThe numbering style reference, must match a defined config
levelnumberThe list level (0 = top-level, 1 = sub-list, etc.)
instancenumber(Optional) Identifies the instance of the list. Lists with the same instance (or none) continue numbering across paragraphs. A new instance value restarts numbering from 1.

Example:

new Paragraph({
    text: "First list item",
    numbering: { reference: "my-numbering", level: 0, instance: 1 },
});

new Paragraph({
    text: "Second list item",
    numbering: { reference: "my-numbering", level: 0, instance: 1 },
});

new Paragraph({
    text: "New list, starts again at 1",
    numbering: { reference: "my-numbering", level: 0, instance: 2 },
});

How instance restarts work: level overrides

Under the hood, every distinct reference/instance pair used on a paragraph creates its own concrete numbering (a w:num element) pointing at the shared abstract definition. Each concrete numbering carries an override for level 0 (IConcreteNumberingOptions.overrideLevels, a list of { num, start } entries emitted as w:lvlOverride/w:startOverride):

{
    overrideLevels: [
        {
            num: 0, // the level being overridden
            start: 1, // the value that level restarts at
        },
    ],
}

Because each instance gets its own concrete numbering with a fresh level-0 start override, a new instance value restarts numbering. The restart value is taken from the start option of the first level in your config (defaulting to 1), so a config with start: 5 on level 0 makes every new instance begin at 5.

ConcreteNumbering and its options are exported for advanced use, but when building documents through Document you normally never construct them yourself — using instance on a paragraph is all that is needed.

Advanced: the exported Numbering class accepts an optional second constructor argument, INumberingIdOffsets ({ abstractNum?, num? }), which offsets generated numbering ids to avoid collisions when merging into a document that already defines numberings. This is internal plumbing used by the patcher — documents created via new Document(...) always build their own Numbering without offsets.

Un-ordered lists / Bullet points

Add a numbering section to the Document to numbering style, define your levels. Use NumberFormat.BULLET for the format in levels:

const doc = new Document({
    ...
    numbering: {
        config: [
            {
                reference: "my-bullet-points",
                levels: [
                    {
                        level: 0,
                        format: NumberFormat.BULLET,
                        text: "\u1F60",
                        alignment: AlignmentType.LEFT,
                        style: {
                            paragraph: {
                                indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
                            },
                        },
                    },
                ],
            },
        ],
    },
    ...
});

And then on a Paragraph, we can add use the numbering created:

new Paragraph({
    text: "Hey you!",
    numbering: {
        reference: "my-bullet-points",
        level: 0,
    },
}),

Disabling numbering inherited from paragraph style

If the numbering is set on a paragraph style, you may wish to disable it for a specific paragraph:

const doc = new Document({
    ...
    numbering: {
        config: [
            {
                reference: "my-bullet-points",
                levels: [
                    {
                        level: 0,
                        format: NumberFormat.BULLET,
                        text: "\u1F60",
                        alignment: AlignmentType.LEFT,
                        style: {
                            paragraph: {
                                indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
                            },
                        },
                    },
                ],
            },
        ],
    },
    styles: {
        paragraphStyles: [
            {
                id: 'bullet',
                name: 'Bullet',
                basedOn: 'Normal',
                next: 'Normal',
                run: {},
                paragraph: {
                    numbering: {
                        reference: 'my-bullet-points',
                        level: 0,
                    },
                },
            },
        ],
    },
    ...
});
new Paragraph({
    text: "No bullet points!",
    style: "Bullet",
    numbering: false,
}),

Full Example

Example

Source: https://github.com/dolanmiu/docx/blob/master/demo/3-numbering-and-bullet-points.ts

On this page