Forms and XFDF

The form fields of a PDF are real form controls in WebPDF pages: you type into text fields, check boxes and pick from lists, and the values go back into the PDF when you save it. This article shows how to fill forms, save them, and move form data in and out as XFDF, the XML format for PDF form data.

Filling a form

Show a page with a form, and its fields just work. The file holds the values, so pages of the same file stay in sync as you type:

A form

html
<pdf-file id=f src=//new.webpdf.pro/forms.pdf></pdf-file>
<pdf-page of=f scale=0.5></pdf-page>

The fields are <input>, <select>, <textarea> and <button> elements in the page's annotation layer, so the keyboard and assistive technology work with them. With -=anno, a page leaves them out and draws their values into its image instead.

Saving the filled PDF

save() writes the PDF with its form values, and Ctrl S in a page does the same. The values are stored as a new revision of the file, so the original bytes stay intact, as signatures need:

js
await document.querySelector("pdf-file").save();

To send the filled PDF somewhere instead, get its bytes with saveData(). See Saving and exporting.

Form data as XFDF

XFDF holds just the values, one <field> per field name:

xml
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
  <fields>
    <field name="Name">
      <value>John Doe</value>
    </field>
  </fields>
</xfdf>

file.XFDF has the same methods for form data as the file has for the PDF: open(), load(), save(), saveAs(), saveCopy(), download(), saveData() and export(). Alt Ctrl S and Alt Ctrl O save and open it from a page:

js
const file = document.querySelector("pdf-file");
await file.XFDF.save();                      // a .xfdf file of the values
const xml = await file.XFDF.export(String);   // the same, as a string
await file.XFDF.load(xml);                    // fills the fields again

A form filled from XFDF

The xfdf:src attribute fills the form from an XFDF file as soon as the PDF has loaded:

html
<pdf-file id=f src=//pdf.ist/form.pdf xfdf:src=//pdf.ist/form.xfdf></pdf-file>
<pdf-page of=f scale=0.5></pdf-page>

load() and open() also take a PDF and its XFDF together, and dropping an .xfdf file on a page fills its form.

Forms that PDFium holds

With js in the - attribute of a file, or of the <script>, PDFium holds the form instead of PDF.js. Its PDFium pages show PDFium's own drawing of each field, with see-through fields over them to type and click in; a text field shows its HTML while it has the focus. formData then uses field names:

js
const file = document.querySelector("pdf-file");
file.formData = { Name: { value: "John Doe" }, Agree: true };
console.log(file.formData.Name.value); // "John Doe"

XFDF, saving and the form's buttons work as they do with PDF.js. PDF.js and SVG pages of such a file keep PDF.js's own fields, apart from PDFium's, so fill the form on its PDFium pages. See PDFium alone.

Values from JavaScript

file.formData gives every value, keyed by the field's annotation ID, as PDF.js stores them (by field name when PDFium holds the form). XFDF is usually the easier way to set values, since it uses field names; after you change formData yourself, call page.anno.sync() so the pages show the new values:

js
const file = document.querySelector("pdf-file");
for (const [id, { value }] of Object.entries(file.formData)) console.log(id, value);

Styling fields

Each field is a part named after its tag and its field name, so you can style one kind of field, or one field:

css
pdf-page::part(\<input\>) {
  background: oklch(96% 0.04 95);
}
pdf-page::part(\[name\=Name\]) {
  font-weight: bold;
}

Spaces in a field's name become - in its part name.

See also