Enterprise Recipes
Four complete programs that teams copy into a main.go and run with go run .. Each writes a .docx Microsoft Word 2007 through Microsoft 365 opens without a repair dialog. A fifth track regenerates the 80-document full-feature matrix used to certify v0.9.0.
| Recipe | APIs in play | Output |
|---|---|---|
| 1. Contract and financial report | Template pipes, ${block} / ${if}, nested w:tbl | contract-report.docx |
| 2. Academic and engineering paper | AddMath OMML, SetColumns, AddTOC | paper.docx |
| 3. Lossless multi-document splice | AppendDocument + style / bookmark / rId isolation | dossier.docx |
| 4. Onboarding form, tiled watermark, edit exceptions | AddSDT*, SetHeaderRow, SetTextWatermark, Protect, AllowEdit | onboarding.docx |
| 5. Full-feature matrix (80 documents) | All exported modules v0.1.0–v0.9.0, dual-engine validators | test_output_docs/*.docx |
Related reference pages: Template Engine v2, Tables, SDT form controls, Watermark & Protection, Office Math, Columns, TOC, Document Merger, OpenXML Compatibility.
1. Contract and financial report
A cover that legal can still edit in Word, plus a P&L whose cells contain another table. Placeholders stay inside a single w:t because the template is generated by GoWord (NewTemplateProcessorBytes never sees a GUI-split ${name}).
Pipes used below: upper, trim, formatDate, formatCurrency, default. Nested tables use Cell.AddTable — Cell embeds Container.
Save as main.go and run go run ..
package main
import (
"log"
"github.com/yunkeweb/go-word"
"github.com/yunkeweb/go-word/style"
)
func main() {
src := word.New()
src.SetDefaultFontName("Calibri")
src.SetDefaultAsianFontName("Microsoft YaHei")
src.SetTextWatermark("CONFIDENTIAL")
sec := src.AddSection()
hdr := sec.AddHeader()
hdr.AddText("GoWord · Master Services Agreement", style.Font{Size: 9, Color: "666666"})
ftr := sec.AddFooter()
ftr.AddText("Page ", style.Font{Size: 9})
ftr.AddPageNumber()
sec.AddTitle("Master Services Agreement", 1)
sec.AddText("Party A: ${party_a | upper}")
sec.AddText("Party B: ${party_b | trim | upper}")
sec.AddText("Contract No. ${contract_no | default:DRAFT}")
sec.AddText("Signed ${signed_at | formatDate:\"2006-01-02\"}")
sec.AddText("Contract value ${total | formatCurrency:¥}")
sec.AddText("${if confidential}This copy is issued under NDA. Do not forward.${endif}")
sec.AddTitle("Line items", 2)
lines := sec.AddTable(style.Table{Width: 9000})
lh := lines.AddRow()
lh.AddCell(4500, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("SKU", style.Font{Bold: true, Color: "FFFFFF"})
lh.AddCell(4500, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("Amount", style.Font{Bold: true, Color: "FFFFFF"})
lr := lines.AddRow()
lr.AddCell(4500).AddText("${sku}")
lr.AddCell(4500).AddText("${amount}")
sec.AddTitle("Q1 regional P&L", 2)
outer := sec.AddTable(style.Table{Width: 9000})
oh := outer.AddRow()
oh.AddCell(3000, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("Region", style.Font{Bold: true, Color: "FFFFFF"})
oh.AddCell(6000, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("Breakdown", style.Font{Bold: true, Color: "FFFFFF"})
apac := outer.AddRow()
apac.AddCell(3000, style.Cell{VMerge: "restart", VAlign: "center"}).
AddText("${region | upper}")
host := apac.AddCell(6000)
inner := host.AddTable(style.Table{Width: 5800})
r1 := inner.AddRow()
r1.AddCell(2900).AddText("Hardware")
r1.AddCell(2900).AddText("${q1_hw | formatCurrency:¥}")
r2 := inner.AddRow()
r2.AddCell(2900).AddText("Software")
r2.AddCell(2900).AddText("${q1_sw | formatCurrency:¥}")
cont := outer.AddRow()
cont.AddCell(3000, style.Cell{VMerge: "continue"})
cont.AddCell(6000).AddText("APAC continues on the next visual row (w:vMerge).")
total := outer.AddRow()
total.AddCell(9000, style.Cell{GridSpan: 2, Shading: style.Shading{Fill: "D6DCE4"}}).
AddText("Q1 total ${q1_total | formatCurrency:¥}", style.Font{Bold: true})
sec.AddText("${clauses}")
sec.AddText("${title | upper}. ${body}")
sec.AddText("${/clauses}")
if err := src.Protect(word.ProtectTypeReadOnly, "goword"); err != nil {
log.Fatal(err)
}
raw, err := src.Bytes()
if err != nil {
log.Fatal(err)
}
tp, err := word.NewTemplateProcessorBytes(raw)
if err != nil {
log.Fatal(err)
}
tp.SetValues(map[string]string{
"party_a": "northwind trading",
"party_b": " contoso labs ",
"contract_no": "MSA-2026-0418",
"signed_at": "2026-09-26T08:00:00Z",
"total": "1280000",
"region": "apac",
"q1_hw": "420000",
"q1_sw": "180000",
"q1_total": "600000",
})
if err := tp.SetCondition("confidential", true); err != nil {
log.Fatal(err)
}
if err := tp.CloneRowAndSetValues("sku", []map[string]string{
{"sku": "HW-4401 Server", "amount": "¥420,000.00"},
{"sku": "SW-2208 License", "amount": "¥180,000.00"},
{"sku": "SV-1102 Support", "amount": "¥80,000.00"},
}); err != nil {
log.Fatal(err)
}
if err := tp.CloneBlockAndSetValues("clauses", []map[string]string{
{"title": "Payment", "body": "Net 30 from invoice date, wire to the account in Schedule A."},
{"title": "Liability", "body": "Each party’s aggregate liability is capped at the contract value."},
}); err != nil {
log.Fatal(err)
}
if err := tp.Save("contract-report.docx"); err != nil {
log.Fatal(err)
}
}What Word shows
- Cover lines resolve to
NORTHWIND TRADING,CONTOSO LABS,MSA-2026-0418,2026-09-26,¥1280000.00. - The NDA sentence stays (
confidentialis true). The three SKU rows are cloned from one template row. - APAC occupies two visual rows (
vMerge). The right cell holds a 2×2 nested table whose amounts went throughformatCurrency. - The footer cell spans both outer columns (
gridSpan=2). - Diagonal
CONFIDENTIALwatermark. Word prompts for passwordgowordon edit (w:documentProtection— the ZIP itself is not encrypted). See Watermark & Protection.
2. Academic and engineering paper
A title page and TOC stay single-column. The body section is two equal columns (w:cols w:num="2" w:sep="1") and carries native OMML so reviewers double-click equations instead of opening an image. Titles use AddTitle so AddTOC has w:outlineLvl to collect.
package main
import (
"log"
"github.com/yunkeweb/go-word"
"github.com/yunkeweb/go-word/style"
)
func main() {
doc := word.New()
doc.SetDefaultFontName("Calibri")
doc.SetDefaultAsianFontName("Microsoft YaHei")
doc.AddTitleStyle(1, style.Font{Bold: true, Size: 16})
doc.AddTitleStyle(2, style.Font{Bold: true, Size: 13})
doc.SetDifferentFirstPage(true)
cover := doc.AddSection()
cover.AddHeader().AddText("GoWord engineering note")
cover.AddHeader("first").AddText("Cover — not numbered")
cover.AddFooter().AddPageNumber()
cover.AddTitle("Maximum bending stress of a simply supported beam", 1)
cover.AddText("Yunke Research · GoWord v0.9.0 · 26 September 2026", style.Font{Italic: true, Size: 11})
cover.AddText("Abstract. A concentrated force at mid-span is reduced to a closed-form peak stress. Equations are Office Math (OMML), not pictures. Right-click the table of contents and choose Update Field after opening the file.")
cover.AddTitle("Contents", 1)
cover.AddTOC(nil, nil, 1, 2)
body := doc.AddSection(style.Section{
BreakType: "nextPage",
PageSizeW: style.DefaultPageWidth,
PageSizeH: style.DefaultPageHeight,
MarginTop: 1134, MarginBottom: 1134, MarginLeft: 1134, MarginRight: 1134,
})
body.SetColumns(2, 720, true)
body.AddHeader().AddText("Bending stress · two-column body")
body.AddFooter().AddPageNumber()
body.AddTitle("Governing equation", 1)
body.AddText("For a beam of length L, second moment I and distance c to the extreme fibre, the peak bending stress under a mid-span point load P is")
body.AddMath(`\sigma = \frac{M c}{I}`)
body.AddText("with the mid-span moment")
body.AddMath(`M = \frac{P L}{4}`)
body.AddTitle("Worked example", 2)
body.AddText("Substitute the moment into the flexure formula. Nested superscripts and a radical stay editable:")
body.AddMath(`\sigma = \frac{P L c}{4 I}`)
body.AddMath(`\sqrt{x_1} + \pi`)
p := body.AddTextRun()
p.AddText("Euler’s identity ")
p.AddMath(`e^{i\pi} + 1 = 0`)
p.AddText(" sits in the same paragraph as the surrounding words.")
body.AddTitle("Results", 1)
body.AddText("Peak stress stays below yield for the section tabulated below. Word flows this paragraph into the second column as the first column fills.")
tbl := body.AddTable(style.Table{Width: 4200})
th := tbl.AddRow()
th.AddCell(2100, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("Symbol", style.Font{Bold: true, Color: "FFFFFF"})
th.AddCell(2100, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
AddText("Value", style.Font{Bold: true, Color: "FFFFFF"})
for _, pair := range [][2]string{{"P", "12 kN"}, {"L", "4.0 m"}, {"c", "75 mm"}} {
tr := tbl.AddRow()
tr.AddCell(2100).AddText(pair[0])
tr.AddCell(2100).AddText(pair[1])
}
if err := doc.Save("paper.docx"); err != nil {
log.Fatal(err)
}
}What Word shows
- Page 1 is a single-column cover: title, abstract, TOC field (
TOC \o "1-3" \h \z \u). Right-click the field → Update Field → Update entire table to materialise page numbers. - Page 2 starts a new section. Two equal columns with a separator (
w:sep, notw:separator). Formulas open in Word’s equation editor (m:oMathPara/ inlinem:oMath). - First-page header stays on the cover because
SetDifferentFirstPage(true)wrotew:titlePg.
Column width is a section property. Keep the TOC in the previous section if it must stay full width. See Columns, TOC, LaTeX → OMML.
3. Lossless multi-document splice
Three independently authored .docx trees (cover letter, technical note, appendix) share style IDs (Note), bookmark names (shared) and media names (word/media/image1.png). AppendDocument remaps the collisions so the merged ZIP never overwrites a picture or a style definition.
rIds are allocated at write time (word2007Writer.nextRel). Clearing RelationID on cloned images is enough — the writer assigns imageN and a fresh rId on Save.
package main
import (
"bytes"
"image"
"image/color"
"image/png"
"log"
"github.com/yunkeweb/go-word"
"github.com/yunkeweb/go-word/style"
)
func main() {
cover := word.New()
cover.AddParagraphStyle("Note", style.Paragraph{Spacing: style.Spacing{After: 120}})
c := cover.AddSection()
c.AddTitle("Cover letter", 1)
c.AddText("Bookmark and navy swatch from the cover.", nil, "Note")
c.AddBookmark("shared")
c.AddImageBytes("cover.png", swatch(color.RGBA{R: 31, G: 78, B: 121, A: 255}), style.Image{Width: 48, Height: 24})
note := word.New()
note.AddParagraphStyle("Note", style.Paragraph{Spacing: style.Spacing{After: 240}})
n := note.AddSection()
n.AddTitle("Technical note", 1)
n.AddText("Same style id Note, same bookmark name, orange swatch, native formula.", nil, "Note")
n.AddBookmark("shared")
n.AddImageBytes("note.png", swatch(color.RGBA{R: 237, G: 125, B: 49, A: 255}), style.Image{Width: 48, Height: 24})
n.AddMath(`\frac{P L}{4}`)
annex := word.New()
annex.AddParagraphStyle("Note", style.Paragraph{Spacing: style.Spacing{After: 80}})
a := annex.AddSection()
a.AddTitle("Appendix A — test matrix", 1)
a.AddText("Green swatch and a third Note style.", nil, "Note")
a.AddBookmark("shared")
a.AddImageBytes("annex.png", swatch(color.RGBA{R: 112, G: 173, B: 71, A: 255}), style.Image{Width: 48, Height: 24})
if err := cover.AppendDocument(note, word.MergeOptions{
StylePrefix: "note_",
BookmarkPrefix: "note_",
SectionBreak: "nextPage",
}); err != nil {
log.Fatal(err)
}
if err := cover.AppendDocument(annex, word.MergeOptions{
StylePrefix: "annex_",
BookmarkPrefix: "annex_",
SectionBreak: "nextPage",
}); err != nil {
log.Fatal(err)
}
if err := cover.Save("dossier.docx"); err != nil {
log.Fatal(err)
}
}
func swatch(c color.RGBA) []byte {
img := image.NewRGBA(image.Rect(0, 0, 48, 24))
for y := 0; y < 24; y++ {
for x := 0; x < 48; x++ {
img.Set(x, y, c)
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
log.Fatal(err)
}
return buf.Bytes()
}What Word shows
Three pages, each starting after a next-page section break.
| Part | Style ID after merge | Bookmark | Media |
|---|---|---|---|
| Cover | Note (after-spacing 120) | shared | word/media/image1.png navy |
| Technical note | note_Note (after-spacing 240) | note_shared | image2.png orange |
| Appendix | annex_Note (after-spacing 80) | annex_shared | image3.png green |
Unzipping the package never overwrites a picture. Internal hyperlinks that pointed at shared in the cloned trees are rewritten to the prefixed names. The OMML fraction from the technical note stays editable.
Default prefixes (when the option is empty) are src_ for both styles and bookmarks. Empty SectionBreak becomes nextPage. See Document Merger and FAQ — style remapping.
4. Onboarding form, tiled watermark, edit exceptions
A protected onboarding pack: four SDT content controls, a repeating table header, a 3×3 tiled CONFIDENTIAL watermark, and two w:permStart exception ranges that stay editable while the rest of the file is read-only.
package main
import (
"log"
"github.com/yunkeweb/go-word"
"github.com/yunkeweb/go-word/style"
)
func main() {
doc := word.New()
doc.SetDefaultFontName("Calibri")
doc.SetDefaultAsianFontName("Microsoft YaHei")
doc.SetTextWatermark("CONFIDENTIAL", word.WatermarkOptions{
Angle: -45, Color: "C0C0C0", FontSize: 36, Opacity: 0.28,
Tile: true, Rows: 3, Cols: 3,
})
if err := doc.Protect(word.ProtectTypeReadOnly, "goword"); err != nil {
log.Fatal(err)
}
sec := doc.AddSection()
sec.AddTitle("Employee onboarding", 1)
sec.AddText("Locked clauses stay grey. Highlighted ranges are w:permStart exceptions. Password: goword.")
sec.AddSDTText("Full name", "full_name", "Enter full name")
sec.AddSDTDropdown("Department", "dept", map[string]string{
"eng": "Engineering",
"hr": "Human Resources",
})
sec.AddSDTDate("Start date", "start_date", "yyyy-MM-dd")
p := sec.AddTextRun()
p.AddText("I have read the handbook ")
p.AddSDTCheckbox("Handbook", "handbook_ack", false)
sec.AddText("Party A: ________________", style.Font{Bold: true}).AllowEdit("Everyone")
tbl := sec.AddTable(style.Table{Width: 9000})
hdr := tbl.AddRow(360)
tbl.SetHeaderRow(hdr)
hdr.SetCantSplit(true)
hdr.AddCell(3000, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
SetVAlign("center").
AddText("Field", style.Font{Bold: true, Color: "FFFFFF"})
hdr.AddCell(6000, style.Cell{Shading: style.Shading{Fill: "1F4E79"}}).
SetVAlign("center").
AddText("Value", style.Font{Bold: true, Color: "FFFFFF"})
row := tbl.AddRow(320)
row.SetCantSplit(true)
row.AddCell(3000).AddText("Contract no.")
row.AddCell(6000, style.Cell{Shading: style.Shading{Fill: "E2F0D9"}}).
AllowEdit("Everyone").
AddText("CN-2026-001")
if err := doc.Save("onboarding.docx"); err != nil {
log.Fatal(err)
}
}What Word shows
- Four content controls (plain text, drop-down, date, checkbox) that fill without the Developer tab. See SDT.
- Header row repeats on every page (
w:tblHeader) and stays unsplit (w:cantSplit). See Tables. - A 3×3 grey CONFIDENTIAL grid in every header. Restrict Editing lists password
goword. Without it, only the Party A paragraph and the green contract-number cell accept input (w:permStart/w:permEnd). See Watermark & Protection.
Longer samples: examples/v0.9.0_sdt, examples/v0.9.0_table_advanced, examples/v0.9.0_watermark_security.
5. Full-feature matrix
Regenerate the 80 randomly combined documents that cover every exported module from v0.1.0 through v0.9.0, then reverse-parse them in Go and open them in Microsoft Word. Output stays in ./test_output_docs (gitignored). Scripts live in tests/matrix.
go run ./tests/matrix
go run tests/matrix/validate_reader.go| Engine | Check | v0.9.0 |
|---|---|---|
word.Open / word.Read | DOM restore, no panic, error == nil | 80 PASS |
word.StreamExtractText / word.StreamExtractImages | Streaming extract | 80 PASS |
Word COM DisplayAlerts=0 + OpenNoRepairDialog | Repair dialogs / parse exceptions | 80 PASS |
There is no word.ReadDOM. Details and schema notes: OpenXML Compatibility.