
Render: verified documents from unstructured data
Every document generator assumes you already have clean data. Render is the one connected to the part that gets it. We ran it on DigitalOcean's real credit-agreement amendment: bem read the 33-page filing, generated a verified facility summary, and flagged a malformed figure in the filing instead of silently correcting it. New in bem today.

Today we're launching Render: clean, structured document generation from unstructured data. It is a new bem function that turns structured JSON and a .docx template into a finished document. What makes it a document generator and not a merge step is that it is a node in the same workflow as Classify, Split, Extract, Parse, and Enrich. The JSON that fills the template comes out of the messy source document, and every value traces back to the page it was pulled from. Unstructured in, a finished document out.
Here is why that matters. Every other document tool starts after the hard part. PandaDoc, Docupilot, Docmosis, Carbone, Windward: by their own product design they pull fields from a CRM or a database and merge them into a template. That is real and useful, and it is also the easy part. One vendor in the category estimates that roughly 80% of the manual work in a document workflow happens before generation can start, when someone has to turn unstructured source material into structured data. That step is the actual job, and merge tools begin the moment after it is already done. Render begins before it.

Render is a function type on the bem API, not a standalone tool. It sits at the end of the same workflow as every other primitive.
We ran it on the hardest thing we could find. Below is exactly what happened, including the parts that did not work on the first try.
The test: a real credit-agreement amendment
Every company with a credit facility does this over and over. A borrower amends its agreement, a lender joins, a commitment gets increased, and someone in treasury or legal ops has to read the dense amendment and produce a clean facility summary that says what the current terms actually are. Contract and facility abstraction is a real, high-volume enterprise job. It is exactly the "render a document from a messy source" work that no generation tool touches, because the source is a 33-page legal PDF, not a clean CRM record.
On May 4, 2026, DigitalOcean signed Amendment No. 1 to its credit agreement: Goldman Sachs joined as a new lender, the revolving facility was increased, the letter-of-credit sublimit went up. It is a real, public SEC exhibit. Here is the input:

The source document, exactly as filed: a dense, defined-term-heavy credit-agreement amendment. This is what goes in.
We pointed bem at it. Extract pulled the amended terms out of the legalese, and Render produced a facility summary, every value carrying its source:
| Term | Amended value | Source |
|---|---|---|
| New Aggregate Revolving Commitments | $412,500,00.00 *(as filed)* | DigitalOcean Amendment No. 1 |
| Revolving Credit Increase | $112,500,000.00 | DigitalOcean Amendment No. 1 |
| New Lender | Goldman Sachs Bank USA ($40,000,000 commitment) | DigitalOcean Amendment No. 1 |
| L/C Sublimit Increase | $50,000,000 | DigitalOcean Amendment No. 1 |
| Administrative Agent | Morgan Stanley Senior Funding, Inc. | DigitalOcean Amendment No. 1 |
| Effective Date | May 4, 2026 | DigitalOcean Amendment No. 1 |

The actual document bem produced from DigitalOcean's real credit-agreement amendment. Every value cites its source, and the malformed figure is flagged rather than silently corrected.
Now look at the first row. The aggregate commitment reads $412,500,00.00. That is not our typo. It is what the publicly filed amendment says, a stray digit in the original document, the kind of small error that ends up in filings all the time. It is almost certainly meant to be $412,500,000.00. And it is the moment that separates a real document system from a demo.
A mail merge tool never sees it, because it never read the source. A naive AI extractor "helpfully" normalizes it to $412,500,000.00 and hands you a clean, confident, and unverifiable number. bem did neither. It captured the figure exactly as filed and flagged it for a human, with the reason:
> The new aggregate Revolving Credit Commitments figure is captured verbatim as "$412,500,00.00", exactly as printed in the amendment. This value appears malformed (likely intended to be $412,500,000.00). Confirm against the Commitment Schedule before relying on it.
That is the difference between a tool that fills in a document and a tool you can put in front of a credit committee.
It doesn't live in isolation
The reason this works is that Render is not a separate app you export data into. It is one function type in a workflow graph, and the other functions are what make the data clean enough to render. A production pipeline reads like this:
- Classify routes an incoming document to the right handling.
- Split breaks a long file into the sections that matter.
- Extract pulls structured fields out of the prose, with the page each value came from.
- Parse builds the entity and relationship graph when the structure is not known in advance.
- Enrich hydrates the extracted data against your own database or a collection, so the document can carry your canonical customer IDs, SKUs, or account records, not just what the source happened to say.
- Render composes the finished document.
- Send delivers it.
To prove it is genuinely one graph and not a hand-off, we wired Extract directly to Render as a two-node workflow and pointed it at the raw DigitalOcean credit-agreement amendment. One API call. Extract emitted render-ready JSON, Render consumed it, and a finished .docx executive brief came back, with no code in between the two. The document you generate is built, in the same call, out of the document you ingested. That is the whole difference: a merge tool starts when you hand it clean data; bem's pipeline produces that data and the document in one motion.
The part that matters more than the output: it refuses to guess
The research on why document automation fails keeps landing on one phrase: silent failure. A field gets populated, the document looks finished, and nobody notices the value is wrong. A blank field tells your team to go check. A filled-but-wrong field tells them everything is fine. In contracts, that is how a wrong renewal date becomes a missed termination window.
Render does not do silent failure. We tried to make it. Every attempt was rejected before it produced a single byte of output:
1null value for a required field -> data is missing keys the template references: ['governing_law']2an extra field in the data -> data carries keys the template does not reference: ['unexpected_field']3a style the template never defined -> source references styleIds not defined in the template: ['NoSuchStyle']
It does not partially fill a template. It does not guess. If the data and the template disagree, you get an error that names the exact field, not a plausible-looking document with a hole in it.
And when the source itself is the problem, as with DigitalOcean's malformed commitment figure above, bem does not paper over it either. It carries the value through exactly as filed and routes it to the "Items Flagged for Human Review" section with the reason and the place to check. That is what verified generation looks like. Not "the AI is perfect." Honest about what it is sure of, explicit about what a human should confirm, and traceable to the source either way.
Conditions, without a template language
Contracts are conditional. A late-fee clause appears only if there is a late fee. An auto-renewal warning appears only if the agreement auto-renews. Most tools make you learn a proprietary syntax of merge fields and IF-blocks, pasted into the document, where a mistake corrupts the file.
Render has no template language. It fills placeholders and it composes blocks, and nothing else. The conditional logic lives where logic belongs, in your workflow, deciding which blocks to emit. We proved it with a Master Services Agreement rendered two ways from one template:
- A Delaware engagement with auto-renewal, a 1.5% late fee, and a data-processing addendum renders all three clauses, a numbered obligations list, and a fee-milestone table.
- A New York engagement with none of those conditions renders the same document with the Special Conditions section collapsed to a single line: "None."
Same template. Different upstream conditions. Materially different contracts. No IF-blocks in the .docx.
How it works
Render is a bem function. You author a normal .docx in Word or Google Docs, mark where data goes with {{ placeholder }} for inline text and {{p placeholder }} for a block, and upload it. bem reads the file and derives the contract itself: the placeholder set, the style catalog, the table styles, the list numbering. You never hand-maintain a data schema in a proprietary format.
1{2 "functionName": "invoice-render",3 "type": "render",4 "renderConfig": {5 "template": { "name": "invoice-template.docx", "base64": "UEsDBBQ...docx bytes..." }6 }7}
The data that fills it is an ordinary JSON object whose keys match the placeholders. Blocks are composed from four primitives: paragraph, table, image, and list. In a workflow, that JSON comes from Extract, so the document you generate is built from the document you ingested. Every call is one API call, in the same graph as everything else bem does.
Render produces .docx today, with PDF and HTML output on the roadmap. It is strict about template hygiene: when we first ran it, the default Word template shipped five different numbered-list definitions and Render refused the ambiguous template until we reduced it to one per kind. Annoying for five minutes, correct forever after. A generator that enforces a clean template is a generator you can trust in production.
The point
Document generation has been treated as the easy last step, a mail merge you bolt onto the end. It was only easy because everyone defined away the hard part: getting trustworthy, structured data out of the mess, and proving the finished document is right. That is the whole job, and it is the job bem already does. Render is the last mile finally connected to the road.
If you generate documents from data that starts its life unstructured, contracts, invoices, reports, letters, summaries, this is the pipeline you have been assembling by hand. It runs on the public bem API.
Point bem at your own messy documents and generate the finished ones. [Start here.](https://app.bem.ai/auth/sign-up)

Written by
Antonio Bustamante
Jul 23, 2026


Ready to see it in action?
Talk to our team to walk through how Bem can work inside your stack.
Talk to the team