Last modified: Jul 08, 2026

Python-Docx Custom Bullets Guide

Bullet lists are a staple of professional documents. They help organize information clearly. Python-docx makes creating bullet lists easy. But standard bullets can be boring. You might want custom symbols. This guide shows you how to use custom bullet symbols in Python-docx. We will cover unicode symbols, images, and more.

Why Use Custom Bullet Symbols?

Custom bullets make your documents stand out. They can match your brand. They can improve readability. For example, a checkmark bullet works well for to-do lists. A star bullet can highlight key points. Custom bullets add personality to your reports. They also make your content more engaging.

Standard bullets like dots and dashes are fine. But they are overused. Custom symbols give your document a unique feel. They can also convey meaning. A warning sign symbol is perfect for cautions. An arrow symbol can point to next steps. This small change can have a big impact.

Setting Up Python-docx

First, install python-docx. Use pip for this.


# Install python-docx
pip install python-docx

Then import the module in your script.


from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Pt, Inches, RGBColor

Create a new document object. This is your starting point.


doc = Document()

Understanding Bullet Lists in Python-docx

Python-docx uses paragraph styles for lists. The default bullet style is List Bullet. To add a bullet list item, you use the add_paragraph method. You pass the style parameter.


# Add a standard bullet list item
para = doc.add_paragraph("This is a standard bullet", style='List Bullet')

This creates a paragraph with a default bullet symbol. But we want custom symbols. To do this, we need to modify the paragraph's format. We can change the bullet character directly.

Method 1: Using Unicode Symbols

The easiest way is to use unicode symbols. Python-docx supports unicode characters. You can replace the bullet with any unicode symbol. For example, a checkmark (✓) or a star (★).

To do this, you need to access the paragraph's paragraph_format. Then set the bullet property. But python-docx does not have a direct bullet property. Instead, you use the ListFormat object. This object controls list formatting.

Here is a step-by-step example.


from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

doc = Document()

# Create a paragraph
para = doc.add_paragraph("Task completed successfully")

# Access list format
list_format = para.paragraph_format

# Create a new bullet symbol (checkmark)
bullet_char = "✓"

# Add a run with the bullet symbol
run = para.add_run(bullet_char)
run.font.size = Pt(12)
run.font.color.rgb = RGBColor(0, 128, 0)  # Green color

# Now we need to make this run appear as bullet
# We can use paragraph style or direct XML manipulation
# For simplicity, we set the paragraph style to 'List Bullet'
para.style = doc.styles['List Bullet']

# But this will replace our text. So we need a different approach.
# We will manually create the bullet using a tab and the symbol.

The above approach has issues. The style overrides the text. A better method is to use XML manipulation. This gives full control.

Using XML for Custom Unicode Bullets

Python-docx allows direct XML access. This is powerful. You can modify the underlying XML of the list formatting. Here is a clean example.


from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

doc = Document()

# Create a paragraph
para = doc.add_paragraph("Custom bullet item")

# Access the paragraph's XML element
pPr = para._element.get_or_add_pPr()

# Create a numPr element (numbering properties)
numPr = OxmlElement('w:numPr')

# Create a numId element (reference to numbering definition)
numId = OxmlElement('w:numId')
numId.set(qn('w:val'), '0')  # 0 means no numbering, we will use bullet

# Create an ilvl element (indent level)
ilvl = OxmlElement('w:ilvl')
ilvl.set(qn('w:val'), '0')

numPr.append(ilvl)
numPr.append(numId)
pPr.append(numPr)

# Now add the bullet symbol as a run
run = para.add_run("★ ")  # Star symbol
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(255, 215, 0)  # Gold color

# Add the text after the bullet
run_text = para.add_run("This is a star bullet item")
run_text.font.size = Pt(12)

This approach works well. But it is a bit complex. For simpler cases, we can use a different method.

Method 2: Using a Tab and Symbol

A simpler method is to use a tab character. You add the symbol, then a tab, then the text. Then you set the paragraph to have a hanging indent. This mimics a bullet list.


from docx import Document
from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH

doc = Document()

# Create paragraph
para = doc.add_paragraph()

# Set hanging indent
para.paragraph_format.left_indent = Inches(0.5)
para.paragraph_format.first_line_indent = Inches(-0.25)

# Add bullet symbol
run_bullet = para.add_run("→ ")  # Arrow symbol
run_bullet.bold = True
run_bullet.font.size = Pt(12)

# Add tab
run_tab = para.add_run("\t")

# Add text
run_text = para.add_run("This is an arrow bullet item")
run_text.font.size = Pt(12)

doc.save("custom_bullet_tab.docx")

This method is easy to understand. It works for any symbol. You can adjust the indent values. The left_indent sets the overall margin. The first_line_indent creates the hanging effect.

Method 3: Using a Custom List Style

For advanced users, you can create a custom list style. This is useful for reusing the same bullet across many paragraphs. Python-docx supports creating styles. But it is complex.

Instead, you can modify an existing list style. Here is an example that changes the default bullet symbol for all list items.


from docx import Document
from docx.oxml.ns import qn
from lxml import etree

doc = Document()

# Access the numbering part
numbering_part = doc.part.numbering_part
numbering_xml = numbering_part._element

# Find the abstract numbering for bullets
# This is usually the first one with numId 1
# We will create a new numbering definition

# Add a new abstract numbering
abstract_num = OxmlElement('w:abstractNum')
abstract_num.set(qn('w:abstractNumId'), '100')

# Add level 0
lvl = OxmlElement('w:lvl')
lvl.set(qn('w:ilvl'), '0')

# Set the bullet symbol
lvlText = OxmlElement('w:lvlText')
lvlText.set(qn('w:val'), '★')
lvl.append(lvlText)

# Set font properties
rPr = OxmlElement('w:rPr')
rFonts = OxmlElement('w:rFonts')
rFonts.set(qn('w:ascii'), 'Symbol')
rFonts.set(qn('w:hAnsi'), 'Symbol')
rPr.append(rFonts)
lvl.append(rPr)

abstract_num.append(lvl)
numbering_xml.append(abstract_num)

# Create a numbering instance
num = OxmlElement('w:num')
num.set(qn('w:numId'), '100')
num_abstract_numId = OxmlElement('w:abstractNumId')
num_abstract_numId.set(qn('w:val'), '100')
num.append(num_abstract_numId)
numbering_xml.append(num)

# Now use this numbering in a paragraph
para = doc.add_paragraph("Custom star bullet")
pPr = para._element.get_or_add_pPr()
numPr = OxmlElement('w:numPr')
numId = OxmlElement('w:numId')
numId.set(qn('w:val'), '100')
ilvl = OxmlElement('w:ilvl')
ilvl.set(qn('w:val'), '0')
numPr.append(ilvl)
numPr.append(numId)
pPr.append(numPr)

doc.save("custom_list_style.docx")

This method is powerful. It allows you to define a complete list style. You can reuse it across multiple paragraphs. But it requires understanding of Word XML structure.

Practical Examples

Example 1: Checkmark Bullets for To-Do Lists

To-do lists are common in documents. Use a checkmark symbol.


from docx import Document
from docx.shared import Inches, Pt

doc = Document()

tasks = ["Buy groceries", "Finish report", "Call client"]

for task in tasks:
    para = doc.add_paragraph()
    para.paragraph_format.left_indent = Inches(0.5)
    para.paragraph_format.first_line_indent = Inches(-0.25)
    
    run_bullet = para.add_run("✓ ")
    run_bullet.font.color.rgb = RGBColor(0, 128, 0)
    run_bullet.font.size = Pt(14)
    
    run_tab = para.add_run("\t")
    
    run_text = para.add_run(task)
    run_text.font.size = Pt(12)

doc.save("todo_list.docx")

Example 2: Arrow Bullets for Steps

Use arrows to indicate next steps.


from docx import Document
from docx.shared import Inches, Pt

doc = Document()

steps = ["Step 1: Plan", "Step 2: Execute", "Step 3: Review"]

for step in steps:
    para = doc.add_paragraph()
    para.paragraph_format.left_indent = Inches(0.5)
    para.paragraph_format.first_line_indent = Inches(-0.25)
    
    run_bullet = para.add_run("➤ ")
    run_bullet.font.color.rgb = RGBColor(0, 102, 204)
    run_bullet.font.size = Pt(14)
    
    run_tab = para.add_run("\t")
    
    run_text = para.add_run(step)
    run_text.font.size = Pt(12)

doc.save("steps_bullets.docx")

Best Practices

Keep your bullets consistent. Use the same symbol for similar content. Avoid too many symbols. This can confuse readers. Use bold or color to highlight important bullets. But do not overdo it.

Test your document in Word. Some symbols may not render correctly. Use standard unicode symbols. These are widely supported. For advanced formatting, consider using Python docx Best Practices for Clean Generation to ensure clean output.

If you need multi-level lists, check the Python docx Numbered Headings Guide for hierarchy tips. For complex documents, see Python DOCX Templates with Jinja2 to streamline your workflow.

Conclusion

Custom bullet symbols enhance your documents. Python-docx offers several methods. The tab and symbol method is simplest. The XML method gives full control. Choose the method that fits your skill level. Start with simple unicode symbols. Then explore advanced list styles. Your documents will look more professional. They will also be more engaging. Try these examples today. Customize your bullets and stand out.