Document AI is usually described as a one-way street: paper goes in, structured data comes out. But a large share of the processes that consume that data end by producing another document — a contract, an offer letter, a policy schedule, a decision notice. The fields that were extracted from the incoming documents are exactly the fields that populate the outgoing one.
Generating that document looks like the easiest step in the pipeline, and it has the highest rate of silent failure. Extraction has confidence scores, validation and review. Template rendering typically has none of that, and its characteristic failure is a finished PDF that went out with {{customer_name}} printed in the middle of a paragraph.
This is what it takes to make the generation step as accountable as the extraction step in front of it.
The placeholder is a contract, and it is fragile
The standard approach is a literal marker in the document body — a key wrapped in braces — that the renderer substitutes. It works, and it has a structural weakness that surprises people the first time they hit it.
Word processing formats do not store a paragraph as text. They store it as a sequence of runs, and the editor splits runs for reasons that have nothing to do with content: a spellcheck pass, a tracked change accepted, a cursor placed mid-word, a language mark. A placeholder typed by hand can end up stored like this:
<w:r><w:t>{{cust</w:t></w:r>
<w:r><w:t>omer_name}}</w:t></w:r>
It looks perfect on screen. A renderer matching on literal text finds nothing, substitutes nothing, and emits the braces into the output. Nothing errors, because from the renderer's point of view there was no placeholder there.
The fix is to make the marker a structured object that the editor guarantees stays whole — a content control with a tag — while keeping the literal braces as its visible text. Two halves, one marker:
- The tag — structured, readable directly from the document XML without flattening runs and matching patterns over the result.
- The visible literal — keeps the marker compatible with substitution over text, so templates authored before the structured tag existed continue to render unchanged.
Dropping the braces once the tag exists is tempting and it is the mistake. It breaks every previously uploaded template in silence, producing PDFs with the marker still visible inside. Both halves are written, and a test pins that they stay written.
Validate the key where the author is, not after upload
The second failure is subtler. The author writes a placeholder for a field the process does not produce, or writes the right field with a slightly wrong name. The template uploads cleanly. It renders cleanly. The value is simply absent.
Two things stop this, and both are about moving the check earlier.
First, validate the key against the same pattern the server uses to detect keys. If the extractor recognises keys matching a specific character class, an authoring tool that accepts anything else is guaranteeing a marker that looks correct and never appears in the detected key list. Rejecting at authoring time is a message the author can act on; discovering it after upload is a message about a file they have already stopped thinking about.
Second, and more valuable: show the author the fields that actually exist. A data dictionary published by the server — these are the keys this process can supply, with their types — converts template authoring from guesswork into selection. The author picks from what the process provides instead of inventing a name and hoping it matches.
Author where the author already lives
The conventional way to expose templating is a web editor. It is also the reason template backlogs do not move.
The people who own these documents are in legal, operations or underwriting. They have written the contract in a word processor for years, with their own styles, numbering, clause libraries and review habits. Asking them to re-learn document authoring inside a browser to change one clause means they do not change it — they email the file to someone in engineering, who becomes the bottleneck for every wording change.
The design that works is the one the document composition industry settled on decades ago: leave authoring in the tool the author already knows, and connect that tool to the server's data dictionary. A task pane inside the word processor lists the available fields, inserts them as correct structured markers, and publishes the file as a new template version directly — no export to disk, no drag into a web upload, no chance to insert a malformed key.
The measurable effect is not that authoring is faster. It is that the person who owns the wording is the person who changes it.
Versions, publication and archival
A template is not a file, it is a series of versions with a publication state. This matters more than it does for most content because a rendered document may be signed, sent to a counterparty and relied on for years.
| State | Behaviour |
|---|---|
| Draft version | Uploaded, key-extracted, previewable, never rendered in production |
| Published version | The version new renders resolve to; immutable once published |
| Archived version | No longer selectable for new renders, still resolvable for existing artifacts |
Every render should record which template version produced it, alongside the values supplied. That record is what lets you answer, two years later, exactly which wording a specific counterparty received — the same question workflow versioning answers for the flow that produced it.
Fail loudly on a missing value
The single most important behavioural choice in the renderer is what happens when a key has no value.
Rendering it as an empty string is the default in most template libraries and it is the wrong default here. An offer letter missing the interest rate reads as a complete document with an odd gap. It will be signed. A strict mode that refuses to render, names the missing keys and returns an error is the behaviour a document with legal weight deserves.
Both modes have a place — an optional block genuinely may be absent — so make it explicit per render rather than global, and default to strict. The rule of thumb: if the absence of a value would change what a reader believes the document says, the render must fail rather than proceed.
The same reasoning applies to values that arrived with low confidence. A field the extractor was unsure about should not flow into a contract without having passed human review first. The generation phase is the correct place to enforce that, because it is the last point where the data is still internal.
Determinism is a feature
There is a live temptation to generate the whole document with a language model instead of rendering a template. For some artifacts — a summary, an internal note, a draft for a human to edit — that is reasonable.
For a document that will be signed, it is not. A template renders the same output for the same inputs, every time, and the wording was reviewed once by someone qualified to review it. Generation produces plausible wording that nobody approved, varies between runs, and moves legal review from one artifact to every artifact.
The productive division is that the model produces the data and the template produces the document. Extraction, classification and validation are where the probabilistic work belongs. The final artifact is assembled by substitution into wording a person signed off on. Same-input-same-output is not a limitation of the approach; it is the property that makes the output defensible.
Close the loop
A document process that reads documents and cannot write one is only half a process, and the missing half is where the manual work concentrates. Someone is copying extracted values into a contract template by hand, and that person is both the slowest step and the most likely source of an error nobody catches.
Structured markers that cannot be broken, keys validated against a published dictionary, authoring in the tool the author already uses, versions that stay resolvable, and a renderer that fails loudly rather than shipping a gap — none of it is difficult. It is just usually skipped, because generating the document looks like the easy step.