Forms
AcroForm + static XFA read/write, FDF / XFDF data round-trip, sandboxed field-script engine (calculate / format / validate).
Field types
| Kind | Notes |
|---|---|
text | Single / multi-line; optional regex pattern |
checkbox | Boolean |
radio | Mutually exclusive groups |
choice | Combo (dropdown) and listbox |
pushbutton | Action triggers (next-page, JS — JS is sandboxed) |
signature | Digital-signature placeholder (see Signatures) |
FormManager
import { FormManager } from '@trupdf/viewer';
const m = new FormManager({ strict: true });
m.add({
kind: 'text',
id: 'firstName',
name: 'firstName',
page: 1,
widgets: [{ rect: { x: 100, y: 200, width: 120, height: 20 }, page: 1 }],
value: 'Bharat',
});
m.setValue('firstName', 'Updated');
m.toValueMap(); // { firstName: 'Updated' }
m.fromValueMap({ firstName: 'Loaded' });
m.validate(); // ValidationIssue[]AcroForm I/O
import { readAcroForm, writeAcroForm } from '@trupdf/viewer';
const fields = await readAcroForm(pdfDoc); // the loaded document
const out = await writeAcroForm(srcBytes, fields, {
flatten: false, // true → static PDF
});readAcroForm walks every Widget annotation in the document and
returns FormFieldRecord[]. writeAcroForm back-writes values
directly into the PDF; flattening renders fields as static page
content.
Wire formats
import {
serializeXFDFFormData,
deserializeXFDFFormData,
serializeFDF,
deserializeFDF,
} from '@trupdf/viewer';
const xfdf = serializeXFDFFormData(m.list());
const fdf = serializeFDF(m.list());- XFDF form data — preferred; same wire format as the annotation XFDF; round-trips with Acrobat.
- FDF — legacy systems; works.
- JSON — derived view; not a persistence format.
Static XFA
import { readXFA, unlockStaticXFA, serializeXFADatasets } from '@trupdf/viewer';
const fields = readXFA(rootSubform); // flatten nested subforms
unlockStaticXFA(manager); // lift read-only flag
const xml = serializeXFADatasets(manager); // emit <datasets>Dynamic XFA (with flow markers) is read-only — best-effort render without write-back. Write workflows must convert to AcroForm first.
Field scripts — calculations, formatting, validation
TruPDF executes AcroForm field scripts automatically when a document
loads: /AA /C (calculate), /AA /F (display format) and /AA /V
(validate) actions, honouring the AcroForm /CO calculation order —
the same precedence Acrobat applies. Forms with computed totals,
currency formatting and range checks work out of the box in
DocViewer with zero configuration.
Sandboxed by design
Field scripts arrive inside untrusted PDFs, so they run through a
sandboxed interpreter — never eval, never new Function. Two
execution tiers:
1. Acrobat AF* built-ins, recognised by string match:
| Built-in | Behaviour |
|---|---|
AFSimple_Calculate | SUM / PRD / AVG / MIN / MAX over a field list |
AFRange_Validate | Lower / upper bound check |
AFNumber_Format | Decimals, separators, currency ($1,234.50) |
AFPercent_Format | Percent display (12.5%) |
AFDate_Format / AFDate_FormatEx | Acrobat date pattern table |
AFTime_Format | Acrobat time pattern table |
AFSpecial_Format | Zip, zip+4, phone, SSN |
2. Custom calculation scripts in the common shape
var a = this.getField("Qty").value;
var b = this.getField("Price").value;
event.value = a * b;are compiled by a safe expression parser supporting arithmetic,
comparisons, the ternary ?:, allow-listed Math functions, and
Number / parseInt / String casts.
Anything outside both tiers is reported and never executed — a malicious PDF cannot run JavaScript inside your app. That's a security feature, not a limitation.
Display formatting
Behaves like Acrobat: the formatted value ($1,234.50, 12.5%,
formatted dates) shows when a text field is blurred; focusing the
field reveals the raw value for editing. Raw values — never the
formatted strings — are what gets saved to the PDF /V.
Programmatic access
Inside DocViewer everything above is automatic. For programmatic
control, the engine exposes the script engine:
const js = engine.getFormScriptEngine(); // FormJSEngine | null
js?.formatValue('Total'); // '$1,234.50' — or null (no format / not formattable)
js?.runAllCalculations(); // force-run the whole /CO chain
js?.validateField('Qty'); // ValidationIssue | nullNon-DocViewer hosts wire it up themselves:
import { FormJSEngine, readFormScripts } from '@trupdf/forms';
const scripts = await readFormScripts(pdf); // loaded document → /AA scripts + /CO order
const js = new FormJSEngine(manager, {
onUnsupported: (fieldId, script, phase) => {
console.warn(`Unsupported ${phase} script on ${fieldId} — not executed`);
},
});
js.registerAll(scripts);
js.runAllCalculations();Unrecognised validation scripts emit a ValidationIssue and never
execute:
m.on('form.validation.changed', ({ issues }) => {
issues.forEach((iss) => console.warn(iss.fieldId, iss.code, iss.message));
});Signed documents
On documents that already carry an applied digital signature, the initial recalculation on open is skipped so the signature is not invalidated by a value change the user never made. User edits still recalculate normally (and invalidate the signature, exactly as in Acrobat). See Signatures.
React hook
import { useFormValues } from '@trupdf/react';
function FormHud({ manager }: { manager: FormManager }) {
const [values, setValue, fields] = useFormValues(manager);
return (
<ul>
{fields.map((f) => (
<li key={f.id}>
<label>{f.name}</label>
<input
value={String(values[f.id] ?? '')}
onChange={(e) => setValue(f.id, e.target.value)}
/>
</li>
))}
</ul>
);
}Pre-load values before document arrives
const manager = new FormManager();
manager.fromValueMap({ firstName: 'Bharat', accept: true });
<DocViewer source="…" formManager={manager} licenseManager={license} />;SDK access
TruPDFSDK.getForms() returns a FormsApi (or null before a
document is attached) with list(), getValues(),
setValue(id, value), fromValueMap(map), and on / off for
field.added, field.updated, field.removed, value.changed,
and fields.cleared.
const forms = sdk.getForms();
forms?.setValue('firstName', 'Alice');
forms?.on('value.changed', ({ id, value }) => sync(id, value));Events
| Event | Payload |
|---|---|
form.field.added | { id, kind } |
form.field.updated | { id, kind } |
form.value.changed | { id, value } |
form.validation.changed | { issues: ValidationIssue[] } |