Version: 2.3.0 Last Updated: February 2026
- Introduction
- Installation
- Quick Start
- API Patterns
- Core Features
- Examples
- Template / Mail Merge
- Migration from v1
go-docx v2 is a complete rewrite with clean architecture, proper error handling, and comprehensive OOXML support. Key improvements:
✅ Two API Styles:
- Builder Pattern: Fluent API with error accumulation (recommended for new code)
- Direct Domain API: Interface-based design for advanced use cases
✅ Complete Feature Set:
- Paragraphs, runs, tables, images
- Fields (PAGE, NUMPAGES, TOC, HYPERLINK, etc.)
- Sections with headers/footers
- 40+ built-in styles
- Advanced table features (cell merging, nested tables)
✅ Better Quality:
- Type-safe API (minimal
interface{}) - Comprehensive error handling
- Thread-safe operations
- 95%+ test coverage
go get github.com/mmonterroca/docxgo/v2@latestMinimum Go version: 1.20
package main
import (
"log"
docx "github.com/mmonterroca/docxgo/v2"
)
func main() {
// Create builder with options
builder := docx.NewDocumentBuilder(
docx.WithTitle("My Document"),
docx.WithAuthor("John Doe"),
)
// Add content with fluent API
builder.AddParagraph().
Text("Hello, World!").
Bold().
FontSize(14).
Color(docx.Red).
Alignment(docx.AlignmentCenter).
End()
// Build and validate
doc, err := builder.Build()
if err != nil {
log.Fatal(err)
}
// Save
if err := doc.SaveAs("hello.docx"); err != nil {
log.Fatal(err)
}
}package main
import (
"log"
docx "github.com/mmonterroca/docxgo/v2"
)
func main() {
// Create document
doc := docx.NewDocument()
// Add paragraph
para, err := doc.AddParagraph()
if err != nil {
log.Fatal(err)
}
// Add run with text
run, err := para.AddRun()
if err != nil {
log.Fatal(err)
}
run.SetText("Hello, World!")
run.SetBold(true)
run.SetSize(28) // 14pt (size is in half-points)
run.SetColor(docx.Red)
// Save
if err := doc.SaveAs("hello.docx"); err != nil {
log.Fatal(err)
}
}The builder pattern accumulates errors and returns them in Build(). This allows for fluent chaining without checking errors at every step.
Benefits:
- Cleaner code with method chaining
- Error accumulation (all errors collected and returned in
Build()) - Better for sequential document construction
Example:
builder := docx.NewDocumentBuilder()
// Errors are accumulated internally
builder.AddParagraph().
Text("Paragraph 1").
Bold().
End().
AddParagraph().
Text("Paragraph 2").
Italic().
End()
// All errors are returned here
doc, err := builder.Build()
if err != nil {
// Handle accumulated errors
log.Fatal(err)
}The direct API returns errors immediately, giving you fine-grained control.
Benefits:
- Explicit error handling at each step
- Better for conditional logic
- More control over document construction
Example:
doc := docx.NewDocument()
para, err := doc.AddParagraph()
if err != nil {
return err
}
run, err := para.AddRun()
if err != nil {
return err
}
if err := run.SetText("Hello"); err != nil {
return err
}builder := docx.NewDocumentBuilder(
docx.WithTitle("Annual Report 2025"),
docx.WithAuthor("Jane Smith"),
docx.WithSubject("Financial Data"),
docx.WithKeywords("finance, report, 2025"),
docx.WithDefaultFont("Calibri"),
)
doc, err := builder.Build()doc := docx.NewDocument()
metadata := &domain.Metadata{
Title: "Annual Report 2025",
Author: "Jane Smith",
Subject: "Financial Data",
Keywords: "finance, report, 2025",
}
err := doc.SetMetadata(metadata)builder.AddParagraph().
Text("This is ").
Text("bold text").Bold().
Text(" and this is ").
Text("italic text").Italic().
Text(" and ").
Text("red text").Color(docx.Red).
End()para, _ := doc.AddParagraph()
run1, _ := para.AddRun()
run1.SetText("This is ")
run2, _ := para.AddRun()
run2.SetText("bold text")
run2.SetBold(true)
run3, _ := para.AddRun()
run3.SetText(" and this is ")
run4, _ := para.AddRun()
run4.SetText("italic text")
run4.SetItalic(true)// Builder
builder.AddParagraph().
Text("Centered text").
Alignment(docx.AlignmentCenter).
End()
// Direct API
para, _ := doc.AddParagraph()
para.SetAlignment(docx.AlignmentCenter)
run, _ := para.AddRun()
run.SetText("Centered text")Available methods (both APIs):
// Font size (in points for builder, half-points for direct API)
.FontSize(14) // Builder: 14pt
.SetSize(28) // Direct API: 28 half-points = 14pt
// Colors (use predefined constants or domain.Color)
.Color(docx.Red)
.SetColor(docx.Blue)
// Styles
.Bold()
.SetBold(true)
.Italic()
.SetItalic(true)
.Underline(docx.UnderlineSingle)
.SetUnderline(domain.UnderlineSingle)Predefined Colors:
docx.Black,docx.Whitedocx.Red,docx.Green,docx.Bluedocx.Yellow,docx.Cyan,docx.Magentadocx.Orange,docx.Purpledocx.Gray,docx.Silver
builder.AddTable(3, 3).
Row(0).Cell(0).Text("Header 1").Bold().End().
Row(0).Cell(1).Text("Header 2").Bold().End().
Row(0).Cell(2).Text("Header 3").Bold().End().
Row(1).Cell(0).Text("Data 1").End().
Row(1).Cell(1).Text("Data 2").End().
Row(1).Cell(2).Text("Data 3").End().
End()table, _ := doc.AddTable(3, 3)
// Access rows and cells
row0, _ := table.Row(0)
cell00, _ := row0.Cell(0)
// Add content to cell
para, _ := cell00.AddParagraph()
run, _ := para.AddRun()
run.SetText("Header 1")
run.SetBold(true)Cell Merging:
// Builder
builder.AddTable(3, 3).
Row(0).Cell(0).
Text("Merged Cell").
Merge(2, 1). // colspan=2, rowspan=1
End().
End()
// Direct API
table, _ := doc.AddTable(3, 3)
row, _ := table.Row(0)
cell, _ := row.Cell(0)
cell.Merge(2, 1) // Merge 2 columns, 1 rowNested Tables:
// Direct API
outerTable, _ := doc.AddTable(2, 2)
row, _ := outerTable.Row(0)
cell, _ := row.Cell(0)
// Add nested table inside cell
nestedTable, _ := cell.AddTable(2, 2)Table Styles:
// Builder
builder.AddTable(3, 3).
Style(domain.TableStyleGrid).
End()
// Direct API
table, _ := doc.AddTable(3, 3)
table.SetStyle(domain.TableStyleGrid)Predefined table styles:
TableStyleGridTableStyleListTableStyleColorfulTableStyleAccent1throughTableStyleAccent6
// Simple image (default size)
builder.AddParagraph().
AddImage("path/to/image.png").
End()
// Custom size
builder.AddParagraph().
AddImageWithSize("logo.png", domain.ImageSize{
Width: 914400 * 2, // 2 inches in EMUs
Height: 914400 * 1, // 1 inch in EMUs
}).
End()
// Floating image with position
builder.AddParagraph().
AddImageWithPosition("diagram.png",
domain.ImageSize{Width: 914400 * 4, Height: 914400 * 3},
domain.ImagePosition{
IsInline: false,
HOffset: 914400, // 1 inch from left
VOffset: 914400, // 1 inch from top
}).
End()para, _ := doc.AddParagraph()
// Simple image
img, _ := para.AddImage("path/to/image.png")
// Custom size
img, _ := para.AddImageWithSize("logo.png", domain.ImageSize{
Width: 914400 * 2,
Height: 914400 * 1,
})
// Floating image
img, _ := para.AddImageWithPosition("diagram.png",
domain.ImageSize{Width: 914400 * 4, Height: 914400 * 3},
domain.ImagePosition{
IsInline: false,
HOffset: 914400,
VOffset: 914400,
})Supported Formats:
- PNG, JPEG, GIF, BMP
- TIFF, SVG, WEBP
- ICO, EMF
Size Units:
- EMUs (English Metric Units): 914400 EMUs = 1 inch
- Pixels to EMUs:
pixels * 9525 - Inches to EMUs:
inches * 914400
Fields are dynamic elements that Word updates automatically.
// Page number
docx.NewPageNumberField()
// Total page count
docx.NewPageCountField()
// Table of Contents
docx.NewTOCField(map[string]string{
"levels": "1-3",
"hyperlinks": "true",
"hidePageNumbers": "false",
})
// Hyperlink
docx.NewHyperlinkField("https://example.com", "Click here")
// StyleRef (references heading text)
docx.NewStyleRefField("Heading1")
// Sequence (auto-numbering)
docx.NewSeqField("Figure", "ARABIC")
// Reference to bookmark
docx.NewRefField("MyBookmark")
// Page reference to bookmark
docx.NewPageRefField("_Toc000000001")Direct API:
para, _ := doc.AddParagraph()
run1, _ := para.AddRun()
run1.SetText("Page ")
run2, _ := para.AddRun()
run2.AddField(docx.NewPageNumberField())
run3, _ := para.AddRun()
run3.SetText(" of ")
run4, _ := para.AddRun()
run4.AddField(docx.NewPageCountField())In Headers/Footers:
section, _ := doc.DefaultSection()
footer, _ := section.Footer(domain.FooterDefault)
para, _ := footer.AddParagraph()
para.SetAlignment(domain.AlignmentCenter)
run1, _ := para.AddRun()
run1.SetText("Page ")
run2, _ := para.AddRun()
run2.AddField(docx.NewPageNumberField())
run3, _ := para.AddRun()
run3.SetText(" of ")
run4, _ := para.AddRun()
run4.AddField(docx.NewPageCountField())Sections allow different page layouts within the same document.
section, err := doc.DefaultSection()
if err != nil {
log.Fatal(err)
}
// Set page size
section.SetPageSize(domain.PageSizeA4)
// Set margins
section.SetMargins(domain.Margins{
Top: 1440, // 1 inch (1440 twips)
Right: 1440,
Bottom: 1440,
Left: 1440,
Header: 720, // 0.5 inch from top
Footer: 720, // 0.5 inch from bottom
})
// Set orientation
section.SetOrientation(domain.OrientationLandscape)
// Set columns
section.SetColumns(2) // Two-column layoutdomain.PageSizeA4 // 210mm x 297mm
domain.PageSizeLetter // 8.5" x 11"
domain.PageSizeLegal // 8.5" x 14"
domain.PageSizeA3 // 297mm x 420mm
domain.PageSizeTableid // 11" x 17"section, _ := doc.DefaultSection()
// Get header
header, _ := section.Header(domain.HeaderDefault)
// Add content to header
para, _ := header.AddParagraph()
para.SetAlignment(domain.AlignmentRight)
run, _ := para.AddRun()
run.SetText("Company Name")
run.SetBold(true)
// Get footer
footer, _ := section.Footer(domain.FooterDefault)
// Add page numbers to footer
para, _ := footer.AddParagraph()
para.SetAlignment(domain.AlignmentCenter)
run1, _ := para.AddRun()
run1.SetText("Page ")
run2, _ := para.AddRun()
run2.AddField(docx.NewPageNumberField())
run3, _ := para.AddRun()
run3.SetText(" of ")
run4, _ := para.AddRun()
run4.AddField(docx.NewPageCountField())Header/Footer Types:
domain.HeaderDefault- Default header for all pagesdomain.HeaderFirst- Header for first page onlydomain.HeaderEven- Header for even pagesdomain.FooterDefault- Default footer for all pagesdomain.FooterFirst- Footer for first page onlydomain.FooterEven- Footer for even pages
v2 includes 40+ built-in paragraph styles.
// Builder (via paragraph style - to be added)
// Currently use direct API for styles
// Direct API
para, _ := doc.AddParagraph()
para.SetStyle("Heading1")
run, _ := para.AddRun()
run.SetText("Chapter 1: Introduction")Headings:
Heading1,Heading2,Heading3,Heading4,Heading5,Heading6,Heading7,Heading8,Heading9
Title Styles:
Title,Subtitle
Text Styles:
Normal(default)BodyText,BodyText2,BodyText3Quote,IntenseQuoteEmphasis,Strong,IntenseEmphasis
List Styles:
ListParagraphListNumber,ListNumber2,ListNumber3ListBullet,ListBullet2,ListBullet3
Special Styles:
NoSpacingCaptionFooter,Header
builder := docx.NewDocumentBuilder(
docx.WithTitle("Technical Documentation"),
docx.WithAuthor("Engineering Team"),
)
// Add title
builder.AddParagraph().
Text("Technical Documentation").
FontSize(18).
Bold().
Alignment(docx.AlignmentCenter).
End()
// Add TOC
doc, _ := builder.Build()
para, _ := doc.AddParagraph()
run, _ := para.AddRun()
run.AddField(docx.NewTOCField(map[string]string{
"levels": "1-3",
"hyperlinks": "true",
"hidePageNumbers": "false",
}))
// Add sections with styled headings
para1, _ := doc.AddParagraph()
para1.SetStyle("Heading1")
run1, _ := para1.AddRun()
run1.SetText("1. Introduction")
para2, _ := doc.AddParagraph()
run2, _ := para2.AddRun()
run2.SetText("This document describes...")
// Save
doc.SaveAs("technical_doc.docx")builder := docx.NewDocumentBuilder()
builder.AddParagraph().
Text("Product Catalog").
FontSize(16).
Bold().
Alignment(docx.AlignmentCenter).
End()
builder.AddTable(4, 3).
Row(0).
Cell(0).Text("Product").Bold().End().
Cell(1).Text("Price").Bold().End().
Cell(2).Text("Stock").Bold().End().
End().
Row(1).
Cell(0).Text("Widget A").End().
Cell(1).Text("$19.99").End().
Cell(2).Text("100").End().
End().
Row(2).
Cell(0).Text("Widget B").End().
Cell(1).Text("$29.99").End().
Cell(2).Text("50").End().
End().
Row(3).
Cell(0).Text("Widget C").End().
Cell(1).Text("$39.99").End().
Cell(2).Text("25").End().
End().
End()
doc, _ := builder.Build()
doc.SaveAs("catalog.docx")builder := docx.NewDocumentBuilder()
builder.AddParagraph().
Text("Figure 1: System Architecture").
Alignment(docx.AlignmentCenter).
End()
builder.AddParagraph().
AddImageWithSize("architecture.png", domain.ImageSize{
Width: 914400 * 6, // 6 inches
Height: 914400 * 4, // 4 inches
}).
End()
doc, _ := builder.Build()
doc.SaveAs("report.docx")import "github.com/fumiama/go-docx"
doc := docx.New()
para := doc.AddParagraph()
run := para.AddText("Hello")
run.Bold().Color("FF0000").Size("28")
doc.WriteTo(file)import docx "github.com/mmonterroca/docxgo/v2"
builder := docx.NewDocumentBuilder()
builder.AddParagraph().
Text("Hello").
Bold().
Color(docx.Red).
FontSize(14).
End()
doc, _ := builder.Build()
doc.SaveAs("output.docx")import docx "github.com/mmonterroca/docxgo/v2"
doc := docx.NewDocument()
para, _ := doc.AddParagraph()
run, _ := para.AddRun()
run.SetText("Hello")
run.SetBold(true)
run.SetColor(docx.Red)
run.SetSize(28) // 14pt in half-points
doc.SaveAs("output.docx")| Feature | v1 (Legacy) | v2 (Builder) | v2 (Direct) |
|---|---|---|---|
| Import | fumiama/go-docx |
mmonterroca/docxgo |
mmonterroca/docxgo |
| Create doc | docx.New() |
NewDocumentBuilder() |
NewDocument() |
| Add text | para.AddText() |
para.Text() |
run.SetText() |
| Bold | run.Bold() |
para.Bold() |
run.SetBold(true) |
| Color | run.Color("FF0000") |
para.Color(docx.Red) |
run.SetColor(docx.Red) |
| Size | run.Size("28") (string) |
para.FontSize(14) (points) |
run.SetSize(28) (half-points) |
| Error handling | No errors | Accumulated in Build() |
Immediate errors |
| Save | WriteTo(file) |
doc.SaveAs(path) |
doc.SaveAs(path) |
The pkg/template package provides mail merge functionality for replacing {{placeholder}} tokens in documents with actual data.
import (
docx "github.com/mmonterroca/docxgo/v2"
"github.com/mmonterroca/docxgo/v2/pkg/template"
)
// Open a template document
doc, _ := docx.OpenDocument("invoice_template.docx")
// Merge with data
err := template.MergeTemplate(doc, template.MergeData{
"customer_name": "Acme Corp",
"invoice_date": "2025-01-15",
"total": "$1,234.56",
})
// Save the result
doc.SaveAs("invoice_acme.docx")// Inspect all placeholders in a document
names := template.PlaceholderNames(doc)
// ["customer_name", "invoice_date", "total"]
// Get detailed placeholder info with locations
placeholders := template.FindPlaceholders(doc)
for _, p := range placeholders {
fmt.Printf("%s at %v\n", p.Name, p.Location.Type)
}// Check for missing or unused keys before merging
errors := template.ValidateTemplate(doc, data)
for _, e := range errors {
fmt.Println(e.Error())
// [ERROR] missing_key: placeholder has no corresponding data key
// [WARNING] unused_key: data key has no corresponding placeholder
}// Use ${key} instead of {{key}}
opts := template.MergeOptions{
OpenDelimiter: "${",
CloseDelimiter: "}",
}
err := template.MergeTemplate(doc, data, opts)// Return error if any placeholder has no matching data key
opts := template.MergeOptions{
OpenDelimiter: "{{",
CloseDelimiter: "}}",
StrictMode: true,
}
err := template.MergeTemplate(doc, data, opts)
// err: "template: missing keys: role, department"// Generate multiple documents from the same template
customers := []template.MergeData{
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
}
for _, customer := range customers {
doc, _ := docx.OpenDocument("template.docx")
template.MergeTemplate(doc, customer)
doc.SaveAs(fmt.Sprintf("letter_%s.docx", customer["name"]))
}See examples/14_mail_merge/ for a complete working example.
- Examples Directory - Working code examples
- V2 Design Document - Architecture and implementation phases
- Migration Guide - Detailed v1 to v2 migration
- API Reference - Full API documentation
Last Updated: February 2026 Version: 2.3.0