Using Dynamic Data in PDF Templates
Populate reusable PDF templates from JSON or databases, handle AcroForm and XFA compatibility, validate fields, and ship reliable generated documents.
Dynamic PDF templating separates a document’s layout from its changing values. A reusable PDF contains named fields or merge placeholders; your application validates JSON or database data, writes the values into the template, recalculates fields, and optionally flattens the result for distribution.
The first decision is the form technology. Adobe’s documented JSON import path supports interactive AcroForm and Static XFA PDFs; Dynamic XFA is not supported and returns an error. Detect the form type before choosing a library or API.
1. Choose the right template technology
| Technology | Best for | Layout behavior | Compatibility notes |
|---|---|---|---|
| AcroForm | Invoices, applications, contracts with named fields | Fixed fields, calculations, validation | Broad viewer and library support; supported by Adobe JSON import |
| Static XFA | Existing enterprise forms with fixed XFA structure | Fixed XFA fields | Supported by Adobe JSON import |
| Dynamic XFA | Forms that reflow or repeat content at runtime | Dynamic XML-driven layout | Adobe’s JSON import API does not support it |
| Document-generation template | Contracts, proposals, invoices, NDAs with flowing text or repeated rows | Merge tags and repeating sections | Use a document-generation engine when fixed fields cannot express the layout |
Acrobat’s authoring tools provide text boxes, drop-downs, radio buttons, checkboxes, list boxes, date fields, calculations, validation, and submit buttons. Acrobat Sign field templates add reusable fields while preserving recipient assignments already present in a signing template.
2. Design a stable data contract
Field names are the contract between application data and the PDF. Give every field a unique, stable name and keep presentation concerns out of your source records.
{
"invoice_number": "INV-2026-0042",
"issue_date": "2026-10-01",
"due_date": "2026-10-31",
"customer": {
"name": "Ada Lovelace",
"email": "ada@example.com",
"address": "12 Analytical Engine Way\\nLondon"
},
"items": [
{"description": "Implementation", "quantity": 2, "unit_price": 850.00},
{"description": "Support", "quantity": 1, "unit_price": 150.00}
],
"currency": "USD",
"tax_rate": 0.20
}
- Use machine-stable keys such as
invoice_number, not labels that may change. - Define required, optional, maximum-length, type, and formatting rules.
- Represent dates as ISO 8601 values and format them only at the rendering boundary.
- Decide how missing values appear: empty, “N/A”, or a validation error.
- Version the template and data contract together.
3. Create and inspect the template
- Author the form in Acrobat or your chosen PDF editor.
- Assign unique field names and avoid accidental duplicates.
- Set field types, default appearance, font size, alignment, maximum length, and multiline behavior.
- Add calculations and validation rules where appropriate.
- Save a template version identifier, such as
invoice-v3. - Inspect the file programmatically before building the production pipeline.
Apache PDFBox exposes AcroForm field iteration, FDF import/export, and a method for detecting dynamic XFA. The equivalent check in any language should happen before data import.
4. Fill an AcroForm with Python
The following script validates input, fills fields, updates appearances, and writes a flattened copy. Install dependencies with pip install pypdf.
from pathlib import Path
from datetime import date
from decimal import Decimal
import json
from pypdf import PdfReader, PdfWriter
TEMPLATE = Path("invoice-template.pdf")
OUTPUT = Path("invoice-INV-2026-0042.pdf")
payload = {
"invoice_number": "INV-2026-0042",
"issue_date": "2026-10-01",
"due_date": "2026-10-31",
"customer_name": "Ada Lovelace",
"customer_email": "ada@example.com",
"customer_address": "12 Analytical Engine Way\\nLondon",
"subtotal": "1,850.00",
"tax": "370.00",
"total": "2,220.00",
}
required = ["invoice_number", "issue_date", "due_date", "customer_name", "total"]
missing = [key for key in required if not payload.get(key)]
if missing:
raise ValueError(f"Missing required fields: {', '.join(missing)}")
for key in ("issue_date", "due_date"):
date.fromisoformat(payload[key])
reader = PdfReader(str(TEMPLATE))
if reader.get_fields() is None:
raise ValueError("No AcroForm fields found; check whether this is XFA or a flattened PDF")
writer = PdfWriter()
writer.clone_document_from_reader(reader)
writer.set_need_appearances_writer()
field_map = {
"invoice_number": payload["invoice_number"],
"issue_date": payload["issue_date"],
"due_date": payload["due_date"],
"customer_name": payload["customer_name"],
"customer_email": payload["customer_email"],
"customer_address": payload["customer_address"],
"subtotal": payload["subtotal"],
"tax": payload["tax"],
"total": payload["total"],
}
for page in writer.pages:
writer.update_page_form_field_values(page, field_map, auto_regenerate=True)
with OUTPUT.open("wb") as handle:
writer.write(handle)
print(f"Wrote {OUTPUT}")
Keep an interactive copy when recipients must edit or sign the form. Flatten a separate distribution copy when you need to prevent edits and preserve a controlled rendering.
5. Fill an AcroForm with Node.js
pdf-lib can load a PDF, set common AcroForm field types, and save the result. Install it with npm install pdf-lib.
import { readFile, writeFile } from 'node:fs/promises';
import { PDFDocument } from 'pdf-lib';
const data = {
invoice_number: 'INV-2026-0042',
issue_date: '2026-10-01',
due_date: '2026-10-31',
customer_name: 'Ada Lovelace',
customer_email: 'ada@example.com',
customer_address: '12 Analytical Engine Way\\nLondon',
subtotal: '1,850.00',
tax: '370.00',
total: '2,220.00'
};
for (const key of ['invoice_number', 'issue_date', 'due_date', 'customer_name', 'total']) {
if (!data[key]) throw new Error(`Missing required field: ${key}`);
}
const pdf = await PDFDocument.load(await readFile('invoice-template.pdf'));
const form = pdf.getForm();
const setText = (name, value) => form.getTextField(name).setText(String(value));
for (const [name, value] of Object.entries(data)) setText(name, value);
form.updateFieldAppearances();
// Keep fields interactive. For a locked distribution copy, call form.flatten().
await writeFile('invoice-INV-2026-0042.pdf', await pdf.save());
console.log('Wrote invoice-INV-2026-0042.pdf');
6. Import JSON through an API
Adobe PDF Services’ Document Generation workflow merges dynamic data with custom templates for contracts, proposals, invoices, NDAs, and similar documents. Adobe’s Import PDF Form Data API accepts JSON for AcroForm or Static XFA files. Dynamic XFA is rejected, so perform the form-type check first. Follow the current authentication, upload, and job-polling steps in the Document Generation documentation and Import PDF Form Data documentation.
7. Handle repeating rows and flowing content
AcroForm fields are fixed in position. If an invoice can contain an arbitrary number of line items, reserve enough rows and define an overflow policy, or use a document-generation template with repeating sections. Do not silently truncate descriptions or rows. A reliable policy is:
- Validate the maximum supported row count.
- Wrap long descriptions within a bounded cell.
- Move overflow to a continuation page, or reject the request with a clear validation error.
- Recalculate subtotal, tax, and total from trusted numeric inputs rather than accepting client-supplied totals.
8. Calculations, appearances, and flattening
- Calculations: update dependent fields after setting source values. Some viewers calculate on open; server pipelines should calculate explicitly where the library supports it.
- Appearance streams: a field value can exist in the PDF data while appearing blank in a viewer if its appearance stream was not regenerated. Enable appearance regeneration and inspect the output in more than one viewer.
- Fonts: choose an embedded font that covers names and addresses you expect. Test accented and non-Latin characters.
- Flattening: flatten only after validation and visual inspection. Flattening removes ordinary field interactivity and can affect signing or later edits.
- Signatures: fill first, then sign. Any later field update can invalidate a digital signature.
9. Reliability and auditability checklist
- Store template version, renderer version, input-data version, request ID, and output hash.
- Make generation idempotent using a stable document ID and template version.
- Keep the original input and generated output for reproducibility, subject to your retention policy.
- Reject unknown or misspelled keys when strict contracts are required.
- Use deterministic locale, timezone, currency, decimal rounding, and date formatting.
- Run visual regression checks on representative data: short text, long text, missing optional values, multiple rows, accented names, and boundary totals.
- Separate interactive, signed, and flattened output paths.
10. Performance and cost considerations
Rendering cost depends on PDF size, font embedding, image processing, field count, and whether a remote document-generation API is used. Reuse loaded templates when your process model permits it, avoid repeatedly embedding large assets, and process independent documents concurrently within provider rate limits. Cache immutable templates, not personalized output containing sensitive data unless your retention and access controls allow it.
11. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| “No fields found” | PDF is flattened, uses Dynamic XFA, or fields are not exposed as AcroForm fields | Inspect the form type; obtain an AcroForm or Static XFA template, or use a document-generation workflow |
| Adobe import rejects the file | Dynamic XFA is unsupported | Convert or recreate the template as AcroForm or Static XFA |
| Value exists but appears blank | Appearance stream was not regenerated | Update field appearances and open the output in multiple viewers |
| Only some fields populate | Data keys do not exactly match field names, or duplicate names point to unexpected widgets | Export and inspect field names; create a strict mapping and make names unique |
| Text is clipped | Field is single-line, too small, or has a maximum length | Enable multiline, resize the field, reduce font size, or validate length before rendering |
| Checkbox or radio value is ignored | Value does not match the widget’s export value | Inspect export values and set the exact value defined by the template |
| Totals are wrong | Rounding, locale, or stale calculated fields | Use decimal arithmetic, define rounding rules, and recalculate after setting source fields |
| Signature becomes invalid | PDF changed after signing | Complete all field updates and flattening decisions before signing |
12. Preview generated PDFs without maintaining browser automation
If your workflow publishes a generated PDF or an HTML invoice page for review, ScreenshotNeo can capture the rendered URL. Its API accepts one GET request and supports PNG, JPEG, WebP, or PDF output. The API documentation lists options for full-page capture, waiting, custom headers and cookies, viewport and device settings, CSS or JavaScript, and PDF page settings.
Or skip the browser setup
Host the document or preview page at a URL, then call ScreenshotNeo:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/invoice/INV-2026-0042 -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/invoice/INV-2026-0042"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/invoice/INV-2026-0042' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
13. FAQ
Can I populate a PDF directly from a database?
Yes. Read the record, map it to the template’s stable field names, validate it, render, and record the template and data versions.
Should I use JSON or XML?
Use the format required by your chosen API or template engine. Keep an internal data contract independent from that transport format.
When should I flatten?
Flatten after validation when recipients only need a fixed document. Keep fields interactive for editing, signing preparation, or later workflow steps.
How do I support unlimited invoice items?
Use a flowing document-generation template or implement explicit continuation pages. Fixed AcroForm rows alone cannot grow indefinitely.
Why does a PDF look different in different viewers?
Viewer support for XFA, fonts, calculations, and appearance streams varies. Prefer AcroForm for broad compatibility and test the exact viewers your recipients use.


