-
Notifications
You must be signed in to change notification settings - Fork 0
Forms
modern-pdf-lib v0.15.1
modern-pdf-lib provides a complete AcroForm API. You can read, fill, create,
and flatten PDF interactive forms. All seven AcroForm field types from the PDF
specification are supported.
| Type | Class | PDF /FT
|
/Ff flags |
|---|---|---|---|
| Text field | PdfTextField |
Tx |
— |
| Checkbox | PdfCheckboxField |
Btn |
Pushbutton=0, Radio=0 |
| Radio group | PdfRadioGroup |
Btn |
Radio=1 |
| Dropdown (combobox) | PdfDropdownField |
Ch |
Combo=1 |
| Listbox | PdfListboxField |
Ch |
Combo=0 |
| Button (push-button) | PdfButtonField |
Btn |
Pushbutton=1 |
| Signature field | PdfSignatureField |
Sig |
— |
import { loadPdf } from 'modern-pdf-lib';
const doc = await loadPdf(pdfBytes);
const form = doc.getForm();getForm() returns a PdfForm instance. If the document has no /AcroForm
entry an empty form is returned (all create* methods still work).
// Typed accessors — throw if the field is missing or the wrong type
const name = form.getTextField('customer.name').getText();
const agreed = form.getCheckbox('terms.agree').isChecked();
const country = form.getDropdown('address.country').getSelected();
const sizes = form.getListbox('sizes').getSelected(); // string[]
const group = form.getRadioGroup('payment').getSelected(); // option value
// Generic accessor — returns PdfField | undefined
const field = form.getField('myField');
if (field !== undefined) {
console.log(field.fieldType, field.getFullName());
}
// All fields
const allFields = form.getFields();// Text
form.getTextField('name').setText('Jane Doe');
// Checkbox
form.getCheckbox('subscribe').check();
form.getCheckbox('subscribe').uncheck();
// Radio group — pass the export value of the option to select
form.getRadioGroup('payment').select('credit-card');
// Dropdown — pass the display text of the option
form.getDropdown('country').select('Canada');
// Listbox — single selection (or multiple if multiselect is enabled)
form.getListbox('interests').select(['music', 'travel']);
// Button — set an image (for image buttons)
const image = await doc.embedPng(pngBytes);
form.getButton('logo').setImage(image);Fill multiple fields in one call using a plain object:
form.fill({
'customer.name': 'Jane Doe',
'customer.email': 'jane@example.com',
'address.country': 'Canada',
'terms.agree': true,
});Values are coerced by field type: string values are applied to text,
dropdown, listbox, and radio fields; boolean values are applied to
checkboxes.
Flattening converts an interactive form to static page content. After flattening, field widgets are no longer interactive — their appearance streams become ordinary page content.
// Fill then flatten
form.getTextField('name').setText('Jane Doe');
form.getCheckbox('agree').check();
form.flatten();
const flatBytes = await doc.save();flatten() internally:
- Calls
generateAppearance()on every field to ensure all fields have up-to-date appearance streams. - Clears the
/Fieldsarray in the/AcroFormdictionary so the document is no longer interactive. - Removes the
/NeedAppearancesflag.
Use the createTextField(), createCheckbox(), and similar methods on
PdfForm to add new fields to an existing document:
const page0 = 0; // zero-based page index
// Text field at [x1, y1, x2, y2] in page points
const nameField = form.createTextField(
'recipient.name', // field name
page0,
[50, 700, 250, 720],
);
nameField.setText('Default value');
nameField.setFontSize(12);
// Checkbox
const agreeBox = form.createCheckbox('terms.agree', page0, [50, 680, 65, 695]);
agreeBox.check();
// Dropdown with option list
const dropdown = form.createDropdown(
'country',
page0,
[50, 650, 200, 668],
['Canada', 'France', 'Germany', 'Japan', 'United States'],
);
dropdown.select('Canada');Individual field instances can also be placed on a page after creation using
addToPage():
const field = form.createTextField('notes', 0, [50, 500, 400, 560]);
field.setMultiline(true);
field.addToPage(doc.getPage(1), { x: 50, y: 300, width: 400, height: 60 });This places an additional widget annotation for the same logical field on page 1 (one field, multiple widgets — valid per the PDF specification).
By default, modern-pdf-lib generates standard appearance streams for all
field types using the font and size from the field's /DA (default appearance)
string. You can override this with a custom appearance provider.
import type { AppearanceProviderFor } from 'modern-pdf-lib';
import type { PdfTextField } from 'modern-pdf-lib';
const myProvider: AppearanceProviderFor<PdfTextField> = (field, widget, doc) => {
// widget is the PdfDict for the widget annotation
// Return an array of PDF content stream operators as strings, or
// return undefined to fall back to the default appearance.
const text = field.getText() ?? '';
return [
'q',
'0.9 0.9 0.9 rg', // light grey background
`0 0 ${widget.width} ${widget.height} re f`,
'Q',
`BT /Helv 11 Tf 0 g 4 4 Td (${text}) Tj ET`,
];
};
// Apply the provider
form.getTextField('notes').setAppearanceProvider(myProvider);AppearanceProviderFor<T> is a generic type alias:
type AppearanceProviderFor<T extends PdfField> =
(field: T, widget: PdfDict, doc: PdfDocument) => string[] | undefined;Individual fields can be marked as exported or non-exported. Non-exported fields are excluded when the browser or PDF viewer submits the form:
const field = form.getTextField('internal-notes');
field.isExported(); // boolean
field.disableExporting(); // sets the NoExport flag
field.enableExporting(); // clears the NoExport flagSome older PDF forms use XFA (XML Forms Architecture) alongside or instead of AcroForm. XFA causes PDF viewers to use an entirely different rendering engine that ignores the AcroForm fields.
// Check for XFA
if (form.hasXFA()) {
console.warn('This form uses XFA — deleting to force AcroForm rendering.');
form.deleteXFA();
// deleteXFA() also sets /NeedAppearances to true so the viewer
// knows it must regenerate appearances for the AcroForm fields.
}After deleteXFA(), the document behaves as a standard AcroForm document.
| Method | Description |
|---|---|
getText() |
Get the current text value |
setText(v) |
Set the text value |
setMaxLength(n) |
Set /MaxLen
|
setMultiline(b) |
Enable/disable multiline (Multiline flag) |
setPassword(b) |
Enable/disable password masking (Password flag) |
setFileSelect(b) |
Enable/disable file-select mode |
setFontSize(n) |
Set the font size in the /DA string |
| Method | Description |
|---|---|
isChecked() |
Returns true if the checkbox is checked |
check() |
Set value to the on-state (/Yes or export value) |
uncheck() |
Set value to /Off
|
| Method | Description |
|---|---|
getSelected() |
Export value of the selected option |
select(value) |
Select the option with this export value |
getOptions() |
All export values in the group |
| Method | Description |
|---|---|
getSelected() |
Currently selected option display text |
select(text) |
Select by display text |
getOptions() |
All { display, value } option pairs |
setOptions(opts) |
Replace the option list |
| Method | Description |
|---|---|
setImage(image) |
Embed an image as the button face |
click() |
Simulate a click (triggers JavaScript if present) |
| Method | Description |
|---|---|
getName() |
Partial field name (/T) |
getFullName() |
Dot-separated fully qualified name |
fieldType |
Discriminant: 'text' | 'checkbox' | 'radio' | …
|
isReadOnly() |
ReadOnly flag |
isRequired() |
Required flag |
isExported() |
Not set: exported; NoExport set: not exported |
enableExporting() |
Clear NoExport flag |
disableExporting() |
Set NoExport flag |
generateAppearance() |
Force appearance stream regeneration |
addToPage(page, rect) |
Add a widget annotation to a page |
setValue(v) |
Generic setter (string or boolean) |