DEV Community

Allen Yang
Allen Yang

Posted on

Create and Customize Numbered and Bulleted Lists in Word Using Python

Create and Customize Numbered and Bulleted Lists in Word Using Python

A runbook needs numbered steps. A requirements checklist reads better with bullets. A specification document usually wants a hierarchy such as "1. Overview / 1.1 Background / 1.1.1 Scope". In Word this is a click on the numbering button, but when the document is generated by a script, and the number of items changes from run to run, clicking is not an option.

A Word list is not just a text prefix. It is a structure made of a list style plus per-paragraph list formatting. Once those two layers are clear, Python can produce lists at any depth and starting from any number. This article shows how to create numbered lists, bulleted lists, multi-level numbering, and restarted numbering in Spire.Doc for Python.

Why Build Lists in a Script

  • Stable structure: numbering is driven by the style, so inserting or deleting items renumbers automatically.
  • Consistent levels: depth is set with ListLevelNumber, so indentation no longer drifts between items.
  • Repeatable: the same list style applies to any number of generated documents, with the item text coming from data.

Setting Up the Environment

Install the Spire.Doc library:

pip install Spire.Doc
Enter fullscreen mode Exit fullscreen mode

Then import the required modules in your script:

from spire.doc import *
from spire.doc.common import *
Enter fullscreen mode Exit fullscreen mode

The Two Layers of List Formatting

Spire.Doc separates lists into two parts:

  • The list style (ListStyle): defines the appearance of each level — number format, prefix, starting value, and bullet character. Create one with document.Styles.Add(ListType.Numbered, "name").
  • The paragraph's list format (ListFormat): attaches the style to a paragraph and says which level that paragraph belongs to, through paragraph.ListFormat.ApplyStyle("name") and ListLevelNumber.

The style is defined once; each paragraph then joins that numbering sequence with a single ApplyStyle() call.

Using the Built-in Numbered Style

For a plain consecutive list, ApplyNumberedStyle() does the job in one call:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

items = ["Set up the environment", "Install the library", "Run the first script"]
first = True

for item in items:
    paragraph = section.AddParagraph()
    paragraph.AppendText(item)
    if first:
        paragraph.ListFormat.ApplyNumberedStyle()
        first = False
    else:
        paragraph.ListFormat.ContinueListNumbering()

document.SaveToFile("SimpleNumberedList.docx", FileFormat.Docx2013)
document.Close()
Enter fullscreen mode Exit fullscreen mode

Two details matter here. The first paragraph calls ApplyNumberedStyle() to start the list; every later paragraph calls ContinueListNumbering() to say "keep counting from the previous item". Calling ApplyNumberedStyle() on every paragraph instead produces several separate lists, each restarting at 1.

Building a Custom Numbered Style

When the number format has to be controlled — "1." or "1.1." — create a dedicated list style. Each level is configured in ListRef.Levels:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

# Create a numbered list style
numberList = document.Styles.Add(ListType.Numbered, "numberList")

levels = numberList.ListRef.Levels
# %1 through %9 are placeholders for the counters of each level
levels[0].NumberPrefix = "%1."
levels[0].PatternType = ListPatternType.Arabic
levels[1].NumberPrefix = "%1.%2."
levels[1].PatternType = ListPatternType.Arabic
levels[2].NumberPrefix = "%1.%2.%3."
levels[2].PatternType = ListPatternType.Arabic

# Add one paragraph per level
for text, level in [("Overview", 0), ("Background", 1), ("Scope", 2)]:
    paragraph = section.AddParagraph()
    paragraph.AppendText(text)
    paragraph.ListFormat.ApplyStyle(numberList.Name)
    paragraph.ListFormat.ListLevelNumber = level

document.SaveToFile("CustomNumberedList.docx", FileFormat.Docx2013)
document.Close()
Enter fullscreen mode Exit fullscreen mode

In NumberPrefix, %1, %2, and %3 stand for the counters of levels 1, 2, and 3, so a second level written as %1.%2. and a third written as %1.%2.%3. produce "1.1." and "1.1.1.". PatternType = ListPatternType.Arabic selects Western digits; the enum offers other numeral styles as well.

ListLevelNumber is zero-based: 0 is the first level, 1 the second, and so on. It is easy to confuse with the index into Levels, but the two are consistent — both start at 0.

Bulleted Lists

Bulleted lists use the same machinery, with ListType.Bulleted in place of ListType.Numbered:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

# Create a bulleted list style
bulletList = document.Styles.Add(ListType.Bulleted, "bulletList")

items = ["Reusable template", "Batch output", "Consistent formatting"]
for item in items:
    paragraph = section.AddParagraph()
    paragraph.AppendText(item)
    paragraph.ListFormat.ApplyStyle(bulletList.Name)

document.SaveToFile("BulletedList.docx", FileFormat.Docx2013)
document.Close()
Enter fullscreen mode Exit fullscreen mode

To change the symbol, set BulletCharacter on the level. A Word bullet is really a character, so any character that maps to a glyph in the right font will do:

# Use a character from the Wingdings font as the bullet
bulletList = document.Styles.Add(ListType.Bulleted, "squareBullet")
levels = bulletList.ListRef.Levels
levels[0].BulletCharacter = "\u006e"
levels[0].CharacterFormat.FontName = "Wingdings"
Enter fullscreen mode Exit fullscreen mode

In Wingdings, \u006e renders as a solid square; \u0075, \u00b2, or \u00d8 yield other symbols. The essential part is that CharacterFormat.FontName must match the symbol font the character came from, or the bullet shows up as a blank box or a question mark.

Starting a List From a Given Number

Sometimes a second block of items must continue a numbering scheme, or deliberately begin at a specific number. Rather than building two styles, set the starting value:

from spire.doc import *
from spire.doc.common import *

document = Document()
section = document.AddSection()

numberList = document.Styles.Add(ListType.Numbered, "Numbered1")
for text in ["List Item 1", "List Item 2"]:
    paragraph = section.AddParagraph()
    paragraph.AppendText(text)
    paragraph.ListFormat.ApplyStyle(numberList.Name)

# The second list starts at 10
numberList2 = document.Styles.Add(ListType.Numbered, "Numbered2")
numberList2.ListRef.Levels[0].StartAt = 10

for text in ["List Item 3", "List Item 4"]:
    paragraph = section.AddParagraph()
    paragraph.AppendText(text)
    paragraph.ListFormat.ApplyStyle(numberList2.Name)

document.SaveToFile("RestartList.docx", FileFormat.Docx2013)
document.Close()
Enter fullscreen mode Exit fullscreen mode

StartAt applies to the level it is set on; setting it on the first level restarts the whole sequence from that value. If the two blocks should belong to one continuous sequence with a gap in between, ContinueListNumbering() is the better fit — StartAt is for a deliberate restart at a chosen number.

Practical Tips

  • Create the style once: Styles.Add() is called a single time; every paragraph then reuses it through ApplyStyle().
  • Levels are zero-based: ListLevelNumber and the Levels index agree, and an off-by-one shifts both the prefix and the indentation.
  • Do not mix ApplyNumberedStyle() and ApplyStyle(): the former brings its own built-in numbering while the latter references a custom style, and mixing them in one block breaks the sequence.
  • Match the symbol font: a custom BulletCharacter needs its FontName set, and that font has to be available where the document is opened.
  • Release resources: call document.Close() after saving, and in batch jobs create, save, and close one document at a time.

Conclusion

This article covered several ways to build lists in Word documents with Python: quick consecutive numbering with ApplyNumberedStyle(), multi-level number formats defined through Styles.Add(ListType.Numbered, ...) and Levels[i].NumberPrefix, custom bullets with ListType.Bulleted and BulletCharacter, and control over continuation and restart through ContinueListNumbering() and StartAt. Because numbering is driven by the style, the sequence adjusts itself whenever the number of items changes, which makes these operations a good fit for template-based document generation.

Top comments (0)