Add, Fill, and Delete PDF Form Fields Using JavaScript in React

Registration forms, sign-up sheets, and questionnaires go out as blank PDFs and come back needing to be filled in by hand, one file at a time. The forms themselves change too — a missing text box, an extra checkbox nobody uses anymore — and every change means opening desktop software like Acrobat. That is endurable for one or two files; in bulk it is nothing but manual clicking.

This article shows how to add, fill, and delete PDF form fields with Spire.PDF for JavaScript. It is built on WebAssembly and loads, modifies, and saves PDF documents directly in the browser. The whole process runs locally and reads and writes files through a virtual file system (VFS), with no backend involved.

This article covers three core features:

For installation and project configuration, see Integrate Spire.PDF for JavaScript into a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Add Form Fields

Spire.PDF for JavaScript provides a complete set of form field classes covering text boxes, check boxes, radio buttons, combo boxes, list boxes, buttons, and signature fields. They all work the same way: create an instance on the page, position it with Bounds, and hand it to doc.Form.Fields.Add(). When you load an existing document, set doc.AllowCreateForm to true first.

Class Description
PdfTextBoxField Text box field
PdfCheckBoxField Check box field
PdfRadioButtonListField Radio button field
PdfComboBoxField Combo box field
PdfListBoxField List box field
PdfButtonField Button field
PdfSignatureField Signature field

Bounds, BorderWidth, BorderStyle, Required, ReadOnly, Visible, and ToolTip are shared by every field type.

function App() {
  const addFormFields = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file into the VFS
    const inputFileName = 'BlankRegistrationForm.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Form creation must be enabled explicitly for an existing document
    doc.AllowCreateForm = true;

    let page = doc.Pages.get_Item(0);
    let uiFont = new pdfModule.PdfFont({
      fontFamily: pdfModule.PdfFontFamily.Helvetica,
      size: 10
    });
    const box = (x, y, width, height) => new pdfModule.RectangleF({ x, y, width, height });
    const border = 0.75;

    // 1. Text box: name
    let nameBox = new pdfModule.PdfTextBoxField(page, 'name');
    nameBox.Bounds = box(178, 168, 210, 20);
    nameBox.BorderWidth = border;
    nameBox.BorderStyle = pdfModule.PdfBorderStyle.Solid;
    nameBox.Font = uiFont;
    doc.Form.Fields.Add(nameBox);

    // 2. Text box: email
    let emailBox = new pdfModule.PdfTextBoxField(page, 'email');
    emailBox.Bounds = box(178, 206, 210, 20);
    emailBox.BorderWidth = border;
    emailBox.BorderStyle = pdfModule.PdfBorderStyle.Solid;
    emailBox.Font = uiFont;
    doc.Form.Fields.Add(emailBox);

    // 3. Combo box: department
    let departmentBox = new pdfModule.PdfComboBoxField(page, 'department');
    departmentBox.Bounds = box(178, 244, 210, 20);
    departmentBox.BorderWidth = border;
    departmentBox.Font = uiFont;
    ['Engineering', 'Marketing', 'Sales', 'Support'].forEach(function (item) {
      departmentBox.Items.Add(new pdfModule.PdfListFieldItem({ text: item, value: item.toLowerCase() }));
    });
    doc.Form.Fields.Add(departmentBox);

    // 4. Radio buttons: gender, each option is a PdfRadioButtonListItem
    let genderBox = new pdfModule.PdfRadioButtonListField(page, 'gender');
    ['male', 'female'].forEach(function (value, index) {
      let item = new pdfModule.PdfRadioButtonListItem();
      item.Bounds = box(185.5 + index * 110, 285.5, 13, 13);
      item.BorderWidth = border;
      item.Value = value;
      genderBox.Items.Add(item);
    });
    doc.Form.Fields.Add(genderBox);

    // 5. List box: education
    let educationBox = new pdfModule.PdfListBoxField(page, 'education');
    educationBox.Bounds = box(178, 320, 210, 52);
    educationBox.BorderWidth = border;
    educationBox.Font = uiFont;
    ['Bachelor', 'Master', 'Doctor'].forEach(function (item) {
      educationBox.Items.Add(new pdfModule.PdfListFieldItem({ text: item, value: item.toLowerCase() }));
    });
    doc.Form.Fields.Add(educationBox);

    // 6. Check box: agree to terms
    let agreeBox = new pdfModule.PdfCheckBoxField(page, 'agree_terms');
    agreeBox.Bounds = box(178, 392, 15, 15);
    agreeBox.BorderWidth = border;
    agreeBox.Style = pdfModule.PdfCheckBoxStyle.Check;
    agreeBox.Required = true;
    doc.Form.Fields.Add(agreeBox);

    // 7. Signature field: reserve a place to sign
    let signatureBox = new pdfModule.PdfSignatureField(page, 'signature');
    signatureBox.Bounds = box(178, 424, 210, 40);
    doc.Form.Fields.Add(signatureBox);

    // 8. Button: submit
    let submitButton = new pdfModule.PdfButtonField(page, 'submit');
    submitButton.Bounds = box(72, 478, 90, 26);
    submitButton.Text = 'Submit';
    submitButton.HighlightMode = pdfModule.PdfHighlightMode.Push;
    doc.Form.Fields.Add(submitButton);

    const outputFileName = 'AddFormFields.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Form Fields</h1>
      <button onClick={addFormFields}>
        Add fields
      </button>
    </div>
  );
}

export default App;

The blank registration form now carries a text box, check box, radio buttons, combo box, list box, button, and signature field:

The blank registration form now carries a text box, check box, radio buttons, combo box, list box, button, and signature field


Fill Form Fields

Filling an existing form means working from the widget side: PdfFormWidget wraps the document's form, FieldsWidget hands out the field instances one by one, and after checking the type you cast to the matching subclass to write a value — Text for text boxes, Checked for check boxes, SelectedIndex for combo boxes. Fields are told apart by Name, so a single pass fills the whole form.

function App() {
  const fillFormFields = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to be filled into the VFS
    const inputFileName = 'EmployeeRegistrationForm.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Get the widget collection from the form
    let formWidget = new pdfModule.PdfFormWidget(doc.Form.H);

    for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
      let field = formWidget.FieldsWidget.get_Item({ index: i });

      // Text box: assign Text directly
      if (field instanceof pdfModule.PdfTextBoxFieldWidget) {
        switch (field.Name) {
          case 'name':
            field.Text = 'Jane Doe';
            break;
          case 'email':
            field.Text = 'jane.doe@example.com';
            break;
        }
      }

      // Combo box: SelectedIndex takes an array of indices
      if (field instanceof pdfModule.PdfComboBoxWidgetFieldWidget) {
        if (field.Name === 'department') {
          field.SelectedIndex = [1];
        }
      }

      // Check box: set Checked to true to tick it
      if (field instanceof pdfModule.PdfCheckBoxWidgetFieldWidget) {
        if (field.Name === 'agree_terms') {
          field.Checked = true;
        }
      }
    }

    const outputFileName = 'FillFormFields.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Fill Form Fields</h1>
      <button onClick={fillFormFields}>
        Fill form
      </button>
    </div>
  );
}

export default App;

The text box, combo box, and check box have each been given their values:

The text box, combo box, and check box have each been given their values


Delete Form Fields

Deleting also starts from FieldsWidget: locate the target instance by Name, then call Remove() to take it out of the field collection. Locating by field name is safer than by index — a document whose layout has changed will not lose the wrong field.

function App() {
  const deleteFormField = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file into the VFS
    const inputFileName = 'EmployeeRegistrationForm.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    let form = doc.Form;
    if (form != null) {
      let formWidget = new pdfModule.PdfFormWidget(form.H);

      // Locate the target field by name and remove it
      for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
        let field = formWidget.FieldsWidget.get_Item({ index: i });
        if (field.Name === 'name') {
          formWidget.FieldsWidget.Remove(field);
          break;
        }
      }
    }

    const outputFileName = 'DeleteFormField.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete Form Fields</h1>
      <button onClick={deleteFormField}>
        Delete field
      </button>
    </div>
  );
}

export default App;

The Name text box has been removed from the form:

The Name text box has been removed from the form


FAQ

New form fields cannot be clicked in the reader

Cause: AllowCreateForm defaults to false. When you open an existing document with LoadFromFile, Spire.PDF keeps the document's original form structure and does not allow fields to be appended, so doc.Form.Fields.Add() throws no error but the saved document has no new fields.

Fix: Turn the property on after loading the document and before adding fields:

doc.LoadFromFile(inputFileName);
doc.AllowCreateForm = true;

Fields are still empty after filling

Cause: The field name in the case branch is not character-for-character identical to the real name in the document. PDF field names are case-sensitive and keep leading and trailing spaces, so a name such as company_name (with a trailing space) never matches company_name written in code. Another common mistake is assigning values to objects taken from doc.Form.Fields — those fields have no widget properties such as Text.

Fix: Print every field name before filling and copy from the output. Assignments must land on the *FieldWidget instances that PdfFormWidget returns:

let formWidget = new pdfModule.PdfFormWidget(doc.Form.H);
for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
  console.log(formWidget.FieldsWidget.get_Item({ index: i }).Name);
}

Deleting one field makes the fields after it disappear too

Cause: Remove() changes FieldsWidget.Count immediately, and every field after the removed element shifts up by one position. If you iterate forward and remove inside the loop, the next iteration has already skipped past the element that moved into that slot.

Fix: break after a single removal; to delete several, collect the targets by name and process them one by one, or iterate backwards from the end:

// Iterate backwards and remove every field whose name starts with temp_
for (let i = formWidget.FieldsWidget.Count - 1; i >= 0; i--) {
  let field = formWidget.FieldsWidget.get_Item({ index: i });
  if (field.Name.startsWith('temp_')) {
    formWidget.FieldsWidget.Remove(field);
  }
}

Get a Free License

If you want to remove the evaluation message from the result documents, or to get rid of the feature limitations, contact sales for a temporary license valid for 30 days.