Skip to content

Build and pre-fill a PDF form

An AcroForm is the interactive form inside a PDF. In this recipe, you build one and pre-fill its fields with starting values. The Core HasFormFields trait is a form-authoring API: you create each field and provide its value in the same call. Use the default argument for text fields, selected for choice fields, and checked for checkboxes. The PDF opens with the fields already filled in, and they stay editable in a conforming reader. Anyone who opens the file can still change them. This recipe follows examples/30-form-fields.php.

Scope boundary. Core creates and fills form fields as it builds the document. It does not read a form that already exists in a third-party PDF or merge a value map into it. Here, “fill” means author the form with values, not load and fill an external PDF. Round-tripping an external form is a Premium and server capability, not a Core public API.

Terminal window
composer require nextpdf/core:^3

An AcroForm field stores its current value in the V entry of the field dictionary (ISO 32000-2 §12.7). It can also store an optional default value in DV, which the field returns to when a reset-form action runs. NextPDF sets V from the value you pass to each field constructor. Text rendering uses a default appearance string (DA).

The profile is structural because the document includes a trailer /ID array. The post-pass normalizes that changing identifier before comparison.

NextPDF\Core\Concerns\HasFormFields (mixed into Document):

  • textField(string $name, float $x, float $y, float $w, float $h, string $default = '', array $options = []): static
  • checkBox(string $name, float $x, float $y, float $size, bool $checked = false): static
  • radioButton(string $name, float $x, float $y, float $size, string $value, string $group): static
  • comboBox(string $name, float $x, float $y, float $w, float $h, array $items, string $selected = ''): static
  • listBox(string $name, float $x, float $y, float $w, float $h, array $items, string $selected = ''): static
  • button(string $name, float $x, float $y, float $w, float $h, string $caption, string $action = ''): static

The default, checked, and selected arguments contain the pre-fill values.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Pre-filled Form');
$doc->addPage();
// Text field pre-filled with a value (sets the field dictionary /V entry).
$doc->textField(name: 'full_name', x: 20, y: 30, w: 90, h: 8, default: 'Ada Lovelace');
// Choice field pre-selected.
$doc->comboBox(
name: 'country',
x: 20, y: 45, w: 90, h: 8,
items: ['United Kingdom', 'Taiwan', 'Japan'],
selected: 'Taiwan',
);
// Checkbox pre-checked.
$doc->checkBox(name: 'newsletter', x: 20, y: 60, size: 5, checked: true);
$doc->save(__DIR__ . '/prefilled-form.pdf');
echo "Wrote prefilled-form.pdf\n";

The full example below mirrors examples/30-form-fields.php, a registration form with several sections. It writes to NEXTPDF_COOKBOOK_OUTPUT for the harness.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Customer Registration — Pre-filled');
$doc->addPage();
$doc->setFont('helvetica', 'B', 20);
$doc->cell(0, 14, 'Customer Registration Form', newLine: true);
$doc->ln(4);
$leftMargin = 15.0;
$fieldX = 70.0;
$fieldW = 120.0;
$fieldH = 8.0;
$rowSpacing = 12.0;
// --- Personal information, pre-filled ---
$prefill = [
'full_name' => 'Ada Lovelace',
'email' => '[email protected]',
'phone' => '+44 20 7946 0000',
'company' => 'Analytical Engines Ltd',
];
$y = 40.0;
$doc->setFont('helvetica', '', 10);
foreach ($prefill as $name => $value) {
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, ucwords(str_replace('_', ' ', $name)) . ':');
$doc->textField(
name: $name,
x: $fieldX,
y: $y,
w: $fieldW,
h: $fieldH,
default: $value,
options: ['maxLen' => 80],
);
$y += $rowSpacing;
}
// --- Choice field, pre-selected ---
$y += 6;
$doc->setXY($leftMargin, $y);
$doc->cell(50, $fieldH, 'Country:');
$doc->comboBox(
name: 'country',
x: $fieldX,
y: $y,
w: $fieldW,
h: $fieldH,
items: ['United States', 'United Kingdom', 'Germany', 'Japan', 'Taiwan'],
selected: 'United Kingdom',
);
// --- Checkboxes, pre-set ---
$y += $rowSpacing + 6;
$doc->setXY($leftMargin, $y);
$doc->cell(0, 7, 'Subscribe to newsletter');
$doc->checkBox(name: 'newsletter', x: $leftMargin + 70, y: $y, size: 5, checked: true);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/registration-prefilled.pdf');
echo "Wrote pre-filled registration form\n";

Expected output:

Wrote pre-filled registration form

When you open the PDF, every field is already filled in and remains editable.

  • Field names must be unique. Two fields that share a name become one logical field with a shared value in conforming readers. That is useful for linked fields, and a surprise otherwise.
  • Radio group semantics. radioButton() ties each option to a group. The selected option is the one whose value matches the group’s value. Only one option per group is on at a time.
  • maxLen is a hint. A maxLen option caps input length in conforming readers. It does not limit the stored value you pre-fill.
  • Coordinates are top-left in the API. The trait converts to PDF’s bottom-left origin for you, so pass top-left coordinates, as the example shows.
  • No external-PDF fill. No Core method loads an existing third-party form and applies a value map. See the scope boundary above.

Form authoring scales linearly with the field count. Each field adds one widget annotation and one appearance. A few hundred fields stay well inside the 1500 ms / 64 MB budget.

Pre-filled values are written verbatim into the field dictionary. Escape or validate any value from untrusted input before you place it in a document you distribute. A pre-filled form is not protected: anyone who can open the PDF can read and change the values. When the form content is sensitive, combine this recipe with Encrypt with permissions, and note the reader-cooperative caveat there.

StatementSpecClausereference_id
The field’s current value is stored in the field dictionary V entry.ISO 32000-2§12.7
The default value is stored in the DV entry and is restored on form reset.ISO 32000-2§12.7
Field text formatting uses the default appearance string DA.ISO 32000-2§12.7

NextPDF emits the AcroForm structure described by the cited clauses. It does not claim full ISO 32000-2 conformance.