Skip to content

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.

RecipeAPIs in playOutput
1. Contract and financial reportTemplate pipes, ${block} / ${if}, nested w:tblcontract-report.docx
2. Academic and engineering paperAddMath OMML, SetColumns, AddTOCpaper.docx
3. Lossless multi-document spliceAppendDocument + style / bookmark / rId isolationdossier.docx
4. Onboarding form, tiled watermark, edit exceptionsAddSDT*, SetHeaderRow, SetTextWatermark, Protect, AllowEditonboarding.docx
5. Full-feature matrix (80 documents)All exported modules v0.1.0–v0.9.0, dual-engine validatorstest_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 ..

go
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 (confidential is 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 through formatCurrency.
  • The footer cell spans both outer columns (gridSpan=2).
  • Diagonal CONFIDENTIAL watermark. Word prompts for password goword on 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.

go
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, not w:separator). Formulas open in Word’s equation editor (m:oMathPara / inline m:oMath).
  • First-page header stays on the cover because SetDifferentFirstPage(true) wrote w: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.

go
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.

PartStyle ID after mergeBookmarkMedia
CoverNote (after-spacing 120)sharedword/media/image1.png navy
Technical notenote_Note (after-spacing 240)note_sharedimage2.png orange
Appendixannex_Note (after-spacing 80)annex_sharedimage3.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.

go
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.

sh
go run ./tests/matrix
go run tests/matrix/validate_reader.go
EngineCheckv0.9.0
word.Open / word.ReadDOM restore, no panic, error == nil80 PASS
word.StreamExtractText / word.StreamExtractImagesStreaming extract80 PASS
Word COM DisplayAlerts=0 + OpenNoRepairDialogRepair dialogs / parse exceptions80 PASS

There is no word.ReadDOM. Details and schema notes: OpenXML Compatibility.

Released under the GNU LGPL v3.0.