Skip to content

Interactive

PDF interactivity is built from form fields (TextField, ButtonField, ChoiceField, SignatureField), WidgetAnnotations that expose those fields on the page, actions (URI, JavaScript, GoTo, SubmitForm…), and the AcroForm that wires everything together.

Text fields, checkboxes, combo boxes, and multiline notes — all wrapped in a single AcroForm. Setting needAppearances = true lets the viewer render the field appearances on the fly.

use Phpdftk\Pdf\Core\Annotation\WidgetAnnotation;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Interactive\Form\AcroForm;
use Phpdftk\Pdf\Core\Interactive\Form\ButtonField;
use Phpdftk\Pdf\Core\Interactive\Form\ChoiceField;
use Phpdftk\Pdf\Core\Interactive\Form\TextField;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$writer->addFont(new Type1Font(StandardFont::Helvetica)); // becomes F1
$writer->addFont(new Type1Font(StandardFont::HelveticaBold)); // becomes F2
// Page labels for each field.
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont('F2', 22)->moveTextPosition(72, 740)
->showText('Interactive Form Widgets')->endText();
$labels = [
[690, 'Full name:'],
[630, 'Email address:'],
[570, 'Subscribe to newsletter:'],
[510, 'Country:'],
[450, 'Notes:'],
];
foreach ($labels as [$y, $text]) {
$cs->beginText()->setFont('F1', 12)->moveTextPosition(72, $y)
->showText($text)->endText();
}
// Helper: register a field plus its widget and attach to the page.
$fields = [];
$attachField = function (object $field, float $x1, float $y1, float $x2, float $y2)
use ($writer, $page, &$fields): void
{
$writer->register($field);
$widget = new WidgetAnnotation(new PdfArray([
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y2),
]));
$widget->parent = new PdfReference($field->objectNumber);
$writer->register($widget);
$page->corePage()->annots[] = new PdfReference($widget->objectNumber);
$fields[] = new PdfReference($field->objectNumber);
};
// 1) Name — single-line text field
$name = new TextField();
$name->t = new PdfString('name');
$name->tu = new PdfString('Full Name');
$name->maxLen = 100;
$attachField($name, 220, 685, 500, 705);
// 2) Email — single-line text field with default value
$email = new TextField();
$email->t = new PdfString('email');
$email->tu = new PdfString('Email Address');
$email->v = new PdfString('you@example.com');
$attachField($email, 220, 625, 500, 645);
// 3) Subscribe — checkbox (button field with no bits set)
$subscribe = new ButtonField();
$subscribe->t = new PdfString('subscribe');
$subscribe->tu = new PdfString('Subscribe to newsletter');
$subscribe->ff = 0;
$attachField($subscribe, 220, 568, 235, 583);
// 4) Country — combo-box choice field
$country = new ChoiceField();
$country->t = new PdfString('country');
$country->tu = new PdfString('Country of residence');
$country->ff = 1 << 17; // combo flag
$country->opt = new PdfArray([
new PdfString('United States'),
new PdfString('Canada'),
new PdfString('United Kingdom'),
new PdfString('Australia'),
new PdfString('Other'),
]);
$attachField($country, 220, 505, 500, 525);
// 5) Notes — multiline text field (bit 13 = multiline)
$notes = new TextField();
$notes->t = new PdfString('notes');
$notes->tu = new PdfString('Free-form notes');
$notes->ff = 1 << 12; // multiline flag
$attachField($notes, 220, 360, 500, 460);
// Wire all fields into a single AcroForm.
$form = new AcroForm();
$form->fields = $fields;
$form->needAppearances = true;
$form->da = new PdfString('/F1 12 Tf 0 g');
$writer->register($form);
$writer->getCatalog()->acroForm = new PdfReference($form->objectNumber);
$writer->save('form-widgets.pdf');

A signable PDF needs a SignatureField plus a placeholder SignatureValue plus an AcroForm with sigFlags = 3. A signing client patches the placeholder bytes; the structural graph below is what those bytes attach to.

use Phpdftk\Pdf\Core\Annotation\WidgetAnnotation;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Interactive\Form\AcroForm;
use Phpdftk\Pdf\Core\Interactive\Form\SignatureField;
use Phpdftk\Pdf\Core\Interactive\Signature\SignatureValue;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
// This example builds the structural object graph for a signable PDF — a signature
// field, a widget that exposes it on the page, and an AcroForm that flags the file
// as ready for signing. The placeholder /Contents will be patched by a real signer.
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Signable PDF')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 700)
->showText('Below is an unsigned signature widget. A signing client patches the')
->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 684)
->showText('placeholder bytes to certify the document.')->endText();
// Placeholder SignatureValue — empty ByteRange and zeroed /Contents.
$sigValue = new SignatureValue();
$sigValue->name = new PdfString('Sample Signer');
$sigValue->reason = new PdfString('Showcase example');
$sigValue->location = new PdfString('phpdftk');
$sigValue->m = new PdfString('D:20260512000000Z');
$sigValue->byteRange = new PdfArray([
new PdfNumber(0), new PdfNumber(0), new PdfNumber(0), new PdfNumber(0),
]);
$sigValueRef = $writer->register($sigValue);
// Signature field bound to the placeholder.
$field = new SignatureField();
$field->t = new PdfString('Signature1');
$field->tu = new PdfString('Author signature');
$field->setSignatureValue($sigValueRef);
$fieldRef = $writer->register($field);
// Widget that draws the signable box on the page.
$widget = new WidgetAnnotation(new PdfArray([
new PdfNumber(72), new PdfNumber(560),
new PdfNumber(340), new PdfNumber(640),
]));
$widget->parent = $fieldRef;
$page->corePage()->annots[] = $writer->register($widget);
// Wire the form into the document and flag it as expecting a signature.
$form = new AcroForm();
$form->fields = [$fieldRef];
$form->sigFlags = 3; // SignaturesExist (1) | AppendOnly (2)
$writer->getCatalog()->acroForm = $writer->register($form);
$writer->save('signature-field.pdf');

JavaScriptAction can be wired to the catalog’s /OpenAction to run on document load, or to a button widget’s /A to run on click. The button below rolls a random number into a text field.

use Phpdftk\Pdf\Core\Action\JavaScriptAction;
use Phpdftk\Pdf\Core\Annotation\WidgetAnnotation;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\Interactive\Form\AcroForm;
use Phpdftk\Pdf\Core\Interactive\Form\ButtonField;
use Phpdftk\Pdf\Core\Interactive\Form\TextField;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('JavaScript Actions')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 706)
->showText('When opened, this PDF runs a script that greets the reader.')
->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 690)
->showText('Click the button below to run another script on demand.')
->endText();
// Page text labelling the interactive regions.
$cs->beginText()->setFont($bold, 12)->moveTextPosition(96, 632)
->showText('Roll the dice')->endText();
$cs->beginText()->setFont($body, 10)->moveTextPosition(246, 632)
->showText('Result appears here ->')->endText();
// 1) /OpenAction — a script that fires when the document is opened.
$openJs = new JavaScriptAction(new PdfString(
'app.alert({cMsg: "Welcome — generated by phpdftk.", cTitle: "phpdftk"});',
));
$writer->getCatalog()->openAction = $writer->register($openJs);
// 2) A pushbutton that runs JavaScript on click.
$clickJs = new JavaScriptAction(new PdfString(
'var n = Math.floor(Math.random() * 100); ' .
'this.getField("result").value = "Random number: " + n;',
));
$clickJsRef = $writer->register($clickJs);
$button = new ButtonField();
$button->t = new PdfString('roll');
$button->tu = new PdfString('Roll the dice');
$button->ff = 1 << 16; // pushbutton flag
$buttonRef = $writer->register($button);
$buttonWidget = new WidgetAnnotation(new PdfArray([
new PdfNumber(72), new PdfNumber(620),
new PdfNumber(220), new PdfNumber(650),
]));
$buttonWidget->parent = $buttonRef;
$buttonWidget->a = $clickJsRef;
$page->corePage()->annots[] = $writer->register($buttonWidget);
// 3) A text field where the button script writes its result.
$result = new TextField();
$result->t = new PdfString('result');
$result->tu = new PdfString('Result');
$result->v = new PdfString('(click the button)');
$resultRef = $writer->register($result);
$resultWidget = new WidgetAnnotation(new PdfArray([
new PdfNumber(240), new PdfNumber(620),
new PdfNumber(500), new PdfNumber(650),
]));
$resultWidget->parent = $resultRef;
$page->corePage()->annots[] = $writer->register($resultWidget);
$form = new AcroForm();
$form->fields = [$buttonRef, $resultRef];
$form->needAppearances = true;
$form->da = new PdfString('/F1 12 Tf 0 g');
$writer->getCatalog()->acroForm = $writer->register($form);
$writer->save('javascript-actions.pdf');

Named destinations decouple link targets from page positions — links reference a stable name like "introduction", and the catalog’s name tree resolves each name to a page rectangle.

use Phpdftk\Pdf\Core\Annotation\LinkAnnotation;
use Phpdftk\Pdf\Core\Document\Destination;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfDictionary;
use Phpdftk\Pdf\Core\PdfName;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
// Build three target pages, each with a heading.
$chapters = [
'introduction' => 'Introduction',
'methodology' => 'Methodology',
'results' => 'Results',
];
$pageRefs = [];
foreach ($chapters as $key => $title) {
$page = $writer->addPage();
$pageRefs[$key] = new PdfReference($page->corePage()->objectNumber);
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 24)->moveTextPosition(72, 720)
->showText($title)->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 690)
->showText('You jumped here via a named destination.')->endText();
}
// Register a named destination per chapter — all use FitH (fit width to top of page).
$destinations = [];
foreach ($pageRefs as $key => $ref) {
$destinations[$key] = Destination::fitH($ref, 792.0);
}
$writer->setNamedDestinations($destinations);
// Insert a table-of-contents page at position 0 (move it before the others).
$toc = $writer->addPage();
// addPage appends — re-order so the TOC is first.
$pageTree = $writer->getPageTree();
$tocRef = array_pop($pageTree->kids);
array_unshift($pageTree->kids, $tocRef);
$cs = $writer->addContentStream($toc);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Table of Contents')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 706)
->showText('Each link below jumps to a named destination.')->endText();
$y = 660;
foreach ($chapters as $key => $title) {
$cs->beginText()->setFont($body, 14)->moveTextPosition(72, $y)
->showText('-> ' . $title)->endText();
$link = new LinkAnnotation(new PdfArray([
new PdfNumber(72), new PdfNumber($y - 4),
new PdfNumber(300), new PdfNumber($y + 14),
]));
$link->h = new PdfName('I');
// /A action with /D pointing at the named destination string.
$link->a = new PdfDictionary([
'Type' => new PdfName('Action'),
'S' => new PdfName('GoTo'),
'D' => new \Phpdftk\Pdf\Core\PdfString($key),
]);
$writer->register($link);
$toc->corePage()->annots[] = new PdfReference($link->objectNumber);
$y -= 36;
}
$writer->save('named-destinations.pdf');