Nobody starts from zero. The invoice your company sends, the quotation your sales team fills in, the certificate your training platform issues: they already exist, and they exist as Word documents. The person who made them cared about how they look. Asking a team to rebuild those documents from scratch in a template language is where most “PDF automation” projects quietly die.
So pdfs.build imports the Word document. And it does it in two deliberate steps, because there are two different hard problems in there and stacking them helps neither.
Step one: a faithful static template
Upload a .docx and the importer parses what is actually in the file: paragraphs with their runs and formatting, tables with their structure, embedded images. From that it emits Typst code that reproduces the document, wrapped as a render function so it is already the shape every pdfs.build template has.
Two properties of this step are deliberate.
The output is static. The text of your document comes through as text, not as guessed-at placeholders. The one exception is images: embedded pictures are carried as data fields (named from their alt text when the document has it), because a binary blob inlined into source code helps no one. Everything else renders exactly one document: yours.
Static is a feature, not a limitation. The first question after any conversion is “does it look right?”, and that question needs to be answerable by putting the rendered PDF next to the original. If the importer also guessed at which parts should be variables, every visual discrepancy would have two possible causes: a conversion problem, or a bad parameterization guess. You would be debugging both at once. A static conversion gives you a clean fidelity check with exactly one moving part.
Complex Word layouts degrade honestly here: documents built from floating text boxes and absolute positioning carry over worse than documents built from paragraphs, tables, and images. Most business documents are the latter.
Step two: tell the agent what varies
Once the static template renders the way the original looks, you parameterize it by saying what should vary:
“Make the client name, invoice number, date, and the line items table data-driven.”
The agent rewrites the static content into schema-backed fields, and this is where the three-artifact model earns its keep: the Typst code, the JSON schema, and the sample data are edited together, so the template never references a field the schema does not declare, and the sample data always renders a realistic preview. Every edit ends with a compile check, and repairs run automatically when a change breaks something (we wrote up how that loop works separately).
You can do this in one instruction or ten. “Also make the logo replaceable.” “The payment terms differ per client, make that a field too.” Each step is a small, reviewable change to a template you can preview live.
Step three: it is an endpoint now
Publish the template and it is an API. Send the fields you declared:
POST /v2/organizations/org_abc/templates/invoice/render
{
"data": {
"clientName": "Brightline GmbH",
"invoiceNumber": "2026-0142",
"date": "2026-09-03",
"lineItems": [
{ "description": "Onboarding workshop", "qty": 1, "unitPrice": 1200 }
]
}
}
and the PDF that comes back is the document you already had, with your data in it, paginating properly when the line items run long. The API reference covers authentication, validation errors, and webhooks.
Why the seam is where it is
The import draws the line between the two steps exactly where verification changes hands. Fidelity is checked by eyes: does the render match the original? Parameterization is checked by machines: does it compile, does the schema agree, does the sample data render? Splitting the steps means each check happens against a stable baseline.
It also matches how these projects actually go. The person uploading the Word document knows what the document should look like. The decision about what varies per render is sometimes theirs and sometimes a developer’s, made later, once the template is in front of them. The seam in the product matches the seam in the work.
If you have a .docx that has been resisting automation, import it and put the render next to the original. That comparison is the whole first step.