Skip to content

Replace Placeholders

Placeholder substitution is the lowest-tech way to fill a prepared document. Somebody types {{TITLE}} into a .docx where a value belongs, you supply a map from placeholder name to replacement text, and the code walks the document swapping one for the other. No special Word feature is involved, which is both why it is popular with template authors and why it is the least reliable of the three mechanisms.

There is no placeholder API in UniOffice. The example carries its own fillTemplate function, and that code is the thing to read; how well substitution works is entirely a property of how carefully it is written.

Filling the template

doc, err := document.Open("report.docx")
if err != nil {
    log.Fatalf("error opening document: %s", err)
}
defer doc.Close()

replacements := map[string]string{
    "TITLE":        "Annual Report",
    "YEAR":         "2025",
    "COMPANY NAME": "UniDoc",
}

if err := fillTemplate(doc, replacements); err != nil {
    log.Fatalf("error while filling template: %v", err)
}
err = doc.SaveToFile("template-placeholder-replacement.docx")

Map keys carry no braces. fillTemplate strips those itself, so a key is the bare name between them, spaces and mixed case included: Title Heading and COMPANY NAME are both valid keys and both matched exactly.

Why the run walk is not a string replace

A paragraph in OOXML is a list of runs, and Word splits a run wherever formatting changes or whenever it feels like it after an edit. {{TITLE}} typed in one go frequently ends up stored as {{, TITLE, }} or worse, with nothing on screen to suggest it. A plain strings.Replace over each run’s text therefore stops finding placeholders that look perfectly fine in Word.

fillTemplate deals with that by accumulating text across the runs of a paragraph. It appends each run’s Text() to a buffer, remembers which runs it has consumed since the opening {{, and only when the buffer contains a closing }} does it call extractPlaceholder on what it collected. Substitution then happens in updateRuns, which walks the remembered runs and edits their text content in place, leaving each run’s formatting alone.

Limitations

The accumulation makes a split placeholder findable, not always replaceable. updateRuns replaces the placeholder with a per-run strings.ReplaceAll, so a run has to contain the whole name for the swap to happen. {{TITLE plus }} works, because the name sits in one run. {{TIT plus LE}} does not: the braces get stripped from both runs and the name is left behind as plain text. The output looks like a template that was half filled in, with no error and nothing logged.

Two placeholders in a single run are also a problem. The buffer resets after the first closing }}, so the second name is never extracted, while updateRuns has already removed every {{ and }} from that run. The second placeholder loses its braces and keeps its name.

A placeholder with no matching map key is left exactly as it is, braces and all, which at least makes a missing key visible in the output.

The walk covers doc.Paragraphs(), which is the document body plus the paragraphs inside tables. Headers, footers and footnotes are not touched. If a template puts {{COMPANY NAME}} in its letterhead, that copy will still be there after the run. Mail merge fields do reach headers and footers, so a template that needs both is better off with those.

Placeholders cannot span paragraphs. The buffer is discarded at the end of each paragraph, and a placeholder broken by a paragraph mark is never assembled.

fillTemplate finishes by calling doc.Validate(), which checks document structure and returns an error if the edits produced something malformed. That is a structural check only; it has no opinion on placeholders left unfilled.

Run the example

report.docx ships with the example and contains the placeholders the map in main fills. fillTemplate prints every placeholder it extracts, so the console output is a quick way to see what the walk found and what it missed.

git clone https://github.com/unidoc/unioffice-examples.git
cd unioffice-examples/document/template-placeholder-replacement
go run main.go

If this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on