FormsApi
Form filling, headless — the same rules as typing into the fields. fields() and getValues() read the form and need the extract feature; setValues() writes it and needs forms (without forms, a viewer shows fields read-only). Keys are fully qualified field names, as fields()[].name gives them (topmostSubform[0].Page1[0].f1_01[0]).
With a viewer open on the document, the values go through it — shown at once, on its undo stack, in doc.save() and in viewer.createExporter()'s output alike, and formFieldChanged fires once per applied field. Without one they are written into the document itself, as a reader that typed them would: each field's value and its appearance, a formatted field shown formatted ($1,234.50) with its value as entered (1234.5), and calculated fields recalculated, so doc.save() and a viewer opened later both have them. A static XFA form, once filled, is saved as the AcroForm it is laid over.
Not available: resetting fields to their defaults, a flatten of the form alone (doc.annotations.flatten() and the export's flatten flatten everything), and FDF import. Field values travel as the <fields> of an XFDF file through exportXFDF / importXFDF. A field's rect is a Rect in page space.
Usage
const doc = await PDFDocument.fromUrl('/forms/w9.pdf'); const { rejected } = await doc.forms.setValues({ 'topmostSubform[0].Page1[0].f1_01[0]': 'Ada Lovelace', 'topmostSubform[0].Page1[0].c1_1[0]': '1', // a check box, by its export value }); if (rejected.length > 0) console.warn(rejected); const filled = await doc.save();
Reading
FormType. Only AcroForm fields are interactive. Needs no feature.Promise<FormType> 'none', 'acroform', 'xfa' or 'xfa-foreground'.DOCUMENT_CLOSED— the document has been closed.
if ((await doc.forms.type()) === 'xfa') showNotice('Dynamic XFA forms are not interactive');
'Off' when off, as getValues() speaks it) — on one page, or on every page when the index is omitted. For what a viewer shows now, see getValues(). A document is read as at most 16 384 widgets: past that many, the rest of the form is left as the pages paint it, and setValues() reports those fields as unknown_field. Needs the extract feature.| Parameter | Type | Description |
|---|---|---|
| pageIndex? | number | Zero-based page index. Omit for every page. |
Promise<FormFieldInfo[]> Each with name, type, value, pageIndex, rect, readOnly, required, noExport, multiline, password, comb, multiSelect, hidden and, where set, options, maxLength, exportValue, dateFormat, signature, format, range, calculation, look and rotation.INVALID_ARGUMENT— a page index out of range.FEATURE_NOT_LICENSED— the license lacksextract.DOCUMENT_CLOSED— the document has been closed.
const required = (await doc.forms.fields()).filter((f) => f.required && f.value === '');
'Off' when none is, a choice field's option (a multi-select list box's choices as an array). Signature fields are left out. With a viewer open, what the viewer holds, typed values included. What it returns can be given back to setValues(); only read-only fields are refused. Needs the extract feature.Promise<Record<string, FormValue>> By fully qualified name; a FormValue is a string, or an array of strings for a multi-select list box.FEATURE_NOT_LICENSED— the license lacksextract.DOCUMENT_CLOSED— the document has been closed.
const values = await doc.forms.getValues(); await fetch('/submit', { method: 'POST', body: JSON.stringify(values) });
Writing
Sets fields by fully qualified name and resolves to a SetFieldResult; nothing is thrown for a field, a value of the wrong kind included. Every field is read_only when the document forbids filling it in — by its permissions, or by a certification that allows no changes. Needs the forms feature.
The rules, which a viewer open on the document applies through its fields: a text field keeps as many characters as its length limit allows; a formatted field (number, percentage, date, time, phone…) takes what its format accepts when typed, a date also as YYYY-MM-DD; a check box or radio group takes the export value of the widget to turn on, 'Off' or '' turning all off (a radio group that cannot be turned off refuses 'Off'); a dropdown or list box takes one of its options — a multi-select list box its choices as an array ([] clears it; '' clears any list box), and '' is taken for a dropdown only when it holds no choice (a dropdown cannot be emptied); an array of more than one for a field that takes one is invalid_value; a comb field distributes characters as typing would, up to its length limit. Without a viewer a password field is refused (unsupported_type): a file never keeps a password field's text.
| Parameter | Type | Description |
|---|---|---|
| values | Record<string, FormValue | boolean> | By fully qualified name — a string, an array of strings for a multi-select list box, or a boolean: true turns a check box on (its export value), false turns a check box or radio group off ('Off'); true for a radio group is invalid_value, since a group has no one "on". |
Promise<SetFieldResult> applied names and rejected ones with a reason: unknown_field, read_only, invalid_value or unsupported_type (exported as SetFieldRejection.UNKNOWN_FIELD, .READ_ONLY, …).FEATURE_NOT_LICENSED— the license lacksforms.DOCUMENT_CLOSED— the document has been closed.EXPORT_FAILED— with no viewer open, the document could not be saved to keep a formatted field's value as entered, or, on the form's first write, to name a font every reader has for its fields.INVALID_STATE— a progressive document still downloading.
const { applied, rejected } = await doc.forms.setValues({ 'topmostSubform[0].Page1[0].f1_01[0]': 'Ada Lovelace', 'topmostSubform[0].Page1[0].c1_1[0]': true, // a check box, on }); for (const { name, reason } of rejected) console.warn(name, reason);