The Template-Merge Trap
You need to generate invoices. Or contracts. Or quarterly reports. The data lives in your database, the layout is standardized, and you want a PDF at the end. This should be straightforward.
Carbone gives you a familiar starting point: open a DOCX or ODT file, sprinkle in {d.field} tags where your data should go, upload the template, and call the API with a JSON payload. The template gets merged with the data, and you get a document back. It’s the mail merge model — modernized with an API, but fundamentally unchanged.
The appeal is obvious. Business users can edit templates in Word or LibreOffice. Designers control the layout. Developers control the data. Everyone stays in their lane.
But that clean separation has a cost. Your templates are binary files. You can’t meaningfully diff a DOCX in a pull request. You can’t generate a template dynamically based on runtime conditions. And every time the layout needs to change — a new column in the report, a different header for a client — someone has to open a Word document and move tags around.
Files You Can’t Diff
Carbone’s template model means every document layout is a file. An invoice template. A contract template. A report template. Each one is a DOCX or ODT sitting in a directory or uploaded to Carbone’s cloud, and the only way to edit it is to open it in a word processor.
No meaningful version control. You can commit a DOCX to Git, but the diff is useless. Binary file changed. Was it a font tweak or a restructured table? Nobody knows without opening both versions side by side in Word.
No code review. When someone rearranges a table or moves a {d.field} tag, there’s no pull request with a visible diff. The change happens in a word processor, and the first time anyone notices something broke is when the merged output looks wrong in production.
Template-data mismatches are silent. Rename a field in your JSON payload but forget to update the template tag, and Carbone outputs a document with a blank where data should be. There’s no compile-time check, no schema validation between your data and your template. The mismatch shows up in the rendered document — if anyone catches it.
One template per layout. Need the same report with a slightly different header for enterprise clients? That’s another template file. Need a condensed version without the appendix? Another file. The template count grows with every variation, and each one is a binary artifact you can’t generate programmatically.
LibreOffice in the Middle
Carbone’s PDF output depends on LibreOffice for conversion. The pipeline is: merge data into DOCX, then run LibreOffice to convert the DOCX to PDF. This adds a dependency, latency, and a class of bugs you didn’t ask for.
LibreOffice’s rendering engine doesn’t match Word’s. Fonts render slightly differently. Table column widths shift. Page breaks land in different places. If your template was designed in Word and looks perfect there, the PDF output through LibreOffice might not match — and the discrepancies are the kind of thing that only shows up when a client notices their contract has a table that wraps to the next page when it shouldn’t.
The conversion also adds latency. LibreOffice is a desktop application repurposed as a server-side renderer. It’s not built for high-throughput API workloads. Under load, conversion times climb and resource usage spikes.
For PPTX and ODP templates, there’s an additional limitation — Carbone doesn’t auto-create new slides when content overflows. If your data produces more rows than the template slide can hold, the extra content doesn’t flow to a new slide. You need to anticipate the maximum content length and design your template accordingly, or accept truncated output.
And if you need EPUB output — for ebooks, documentation, or long-form content — Carbone doesn’t support it.
Structured Content, Not Template Files
The Iteration Layer Document Generation API doesn’t use template files at all. You describe your document as structured JSON — a content model made up of blocks, styles, and metadata. The API renders it directly into your target format.
No DOCX files to manage. No LibreOffice in the pipeline. No binary artifacts to version-control. The document definition is JSON, which means it’s code — reviewable, diffable, and generated programmatically from whatever data source drives your application.
Block types cover the building blocks of professional documents:
- paragraph — text with full style control and markdown support
- headline — h1 through h6 with configurable styles
- image — embedded from URLs with width and height control
- table — header rows, body rows, per-cell content, border and style configuration
- grid — 12-column layout for multi-column content within a single page
- list — ordered and unordered with nesting
- table-of-contents — auto-generated from headlines with configurable depth and dot leaders
- page-break — explicit page boundaries
- separator — horizontal rules with style options
- qr-code — generated from any value, with size and color control
- barcode — six formats including Code 128, EAN-13, and Codabar
Four output formats from a single content model: PDF, DOCX, EPUB, and PPTX. Define your document once, render it in whatever format the recipient needs. The same quarterly report can be a PDF for email, a DOCX for stakeholders who want to annotate, and a PPTX for the board meeting — all from one API call each, all from the same content definition.
Full Style Control in JSON
Every visual property is explicit in the request payload. Text styles define font family, size, weight, color, line height, and alignment. Headline styles define per-level typography. Table styles control header backgrounds, body text, border width and color. List styles configure bullet characters and indentation.
Custom fonts are uploaded as base64-encoded TTF, OTF, WOFF, or WOFF2 files, with explicit weight and style declarations. Your brand typeface renders identically across all four output formats — no font substitution, no system font fallbacks.
Headers and footers support text, images, and page numbers. Define them once in the document configuration and they repeat on every page. Page numbers auto-increment without manual placement.
19 page size presets cover standard formats from A0 to A6, Letter, Legal, Tabloid, and more. Custom dimensions work too — set explicit width and height in points for non-standard sizes.
Here’s a quarterly report built from structured content:
import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const result = await client.generateDocument({
format: "pdf",
document: {
metadata: { title: "Q4 Sales Report", author: "Acme Corp" },
page: {
size: { preset: "A4" },
margins: { top_in_pt: 72, right_in_pt: 72, bottom_in_pt: 72, left_in_pt: 72 },
},
styles: { /* text, headline, table, etc. */ },
content: [
{ type: "headline", level: "h1", text: "Q4 2025 Sales Summary" },
{ type: "paragraph", markdown: "Total revenue increased by **18%** compared to Q3." },
{ type: "table",
header: { cells: [{ text: "Region" }, { text: "Revenue" }, { text: "Growth" }] },
rows: [
{ cells: [{ text: "North America" }, { text: "$4.2M" }, { text: "+22%" }] },
{ cells: [{ text: "Europe" }, { text: "$2.8M" }, { text: "+15%" }] },
]},
{ type: "qr-code", value: "https://dashboard.acme.com/q4",
width_in_pt: 80, height_in_pt: 80,
fg_hex_color: "#1a1a1a", bg_hex_color: "#ffffff" },
],
},
});
A headline, a paragraph with inline markdown bold, a table with regional data, and a QR code linking to the live dashboard. The entire document definition is a JSON object. It lives in your codebase, it goes through code review, and you can generate it dynamically from a database query or an analytics pipeline.
What This Changes in Practice
The difference between template-merge and structured content isn’t theoretical. It shows up in daily workflows.
Dynamic document assembly. Your report includes a section per business unit, but the number of units varies by quarter. With Carbone, you either design a template with the maximum possible sections (and hide the empty ones) or maintain multiple templates. With structured content, you build the content array programmatically — one block per unit, generated from a loop over your data. The document adapts to the data instead of the data conforming to the template.
Multi-format delivery. A client wants the report as a PDF. Their compliance team wants the DOCX. The executive team wants the PPTX for a board meeting. With Carbone, PDF comes from LibreOffice conversion, PPTX requires a separate PPTX template, and EPUB isn’t available. With Iteration Layer, you change the format parameter and call the API again. Same content, different output.
Grid layouts. The 12-column grid block lets you place content side by side within a page — a chart description next to a data table, a QR code next to contact information, a two-column layout for a product comparison. Carbone’s layout is whatever the DOCX template’s layout is. Multi-column arrangements require designing them in Word, with all the precision (or lack thereof) that implies.
Embedded codes. QR codes and barcodes are first-class block types. Need a QR code linking to the digital version of a document? Add a qr-code block. Need a Code 128 barcode for a shipping label? Add a barcode block. With Carbone, you’d generate the barcode image separately and insert it as an image placeholder in the template.
Side-by-Side
| Capability | Carbone | Iteration Layer |
|---|---|---|
| Document definition | DOCX/ODT template files | Structured JSON content model |
| Version control | Binary diffs (useless) | JSON diffs (readable) |
| Code review | Not practical | Standard PR workflow |
| Dynamic assembly | Limited — fixed template structure | Full — build content array programmatically |
| Output formats | PDF, DOCX, ODT, PPTX, ODP, HTML, TXT | PDF, DOCX, EPUB, PPTX |
| PDF rendering | LibreOffice conversion | Native — no LibreOffice |
| EPUB support | No | Yes |
| Custom fonts | Via template file | Base64 upload with weight/style control |
| Headers/footers | Via template design | JSON configuration with page numbers |
| Page sizes | Template-defined | 19 presets + custom dimensions |
| QR codes | Not built-in | Block type with size and color control |
| Barcodes | Not built-in | 6 formats |
| Grid layout | Template-defined | 12-column grid block |
| Table of contents | Template-defined | Auto-generated with configurable depth |
| PPTX slide overflow | No auto-creation | Content flows to new slides |
| Data residency | EU-hosted (France) | EU-hosted (Frankfurt) |
When Carbone Makes Sense
Carbone is the right choice when business users — not developers — own the document layout. If your legal team needs to edit contract templates in Word without touching code, the DOCX template model is the feature. The {d.field} syntax is simple enough for non-technical users, and the template-merge workflow keeps layout authoring separate from application development.
But if your documents are generated from data. If the structure varies based on content. If you want the document definition in your codebase — reviewed, tested, and deployed alongside the application that produces it. Then managing template files is overhead that doesn’t need to exist.
Get Started
Check the docs for the full block type reference, style configuration, and SDK guides. The TypeScript and Python SDKs handle authentication and response parsing — define your content, pick a format, and get a document back.
Document Generation is part of a composable suite — chain it with Document Extraction to parse documents and automatically generate formatted output, all through one credit pool.