Skip to content

Modifying Existing PDFs

Dragon edited this page Jun 15, 2026 · 8 revisions

Modifying Existing PDFs

PdfEditor::open() (or PdfEditor::fromBytes()) opens an existing PDF and lets you update its metadata, append new pages, fill its AcroForm fields, and sign it - all without touching the original bytes. Every change is written as an appended incremental revision, so the original content (including any signatures already present) remains byte-for-byte intact.

Both classic xref tables and cross-reference streams (PDF 1.5+) are supported.

use DragonOfMercy\PhpPdf\PdfEditor;

$pdf = PdfEditor::open('invoice.pdf');            // open from file path (unencrypted)
$pdf = PdfEditor::open('protected.pdf', 'pass');  // open an encrypted PDF
// or from bytes:
$pdf = PdfEditor::fromBytes($pdfBytes);
$pdf = PdfEditor::fromBytes($pdfBytes, 'pass');

Editing encrypted PDFs

PdfEditor now supports editing encrypted PDFs. Pass the document password as the second argument to open() or fromBytes(). The password is optional: PDFs protected with only a permissions password (empty user password) open without a password argument; a supplied password is tried as the user password first, then as the owner password.

The appended incremental revision is automatically re-encrypted using the source document's scheme and recovered file key. The original /Encrypt dictionary and /ID pair are forwarded intact so the edited file remains a valid encrypted PDF that opens with the same password.

Supported encryption schemes: RC4 40-bit, RC4 128-bit, AES-128, AES-256.

use DragonOfMercy\PhpPdf\PdfEditor;

// Permissions-only encryption (empty user password) - no password argument needed:
$pdf = PdfEditor::open('readonly.pdf');
$pdf->setTitle('Updated title');
$pdf->save('readonly-updated.pdf');

// User- or owner-password protected:
$pdf = PdfEditor::open('locked.pdf', 'secret');
$pdf->setTitle('Updated title');
$page = $pdf->appendPage();
$page->cell(0, 10, 'New page added to an encrypted document');
$pdf->save('locked-updated.pdf');

Signing limitation. Signing, adding document timestamps, and enabling LTV on an encrypted PDF is not yet supported. PdfEditor throws a PdfException with a clear message if a signature, timestamp, or LTV request is made on an encrypted source. Metadata edits, appended pages, and AcroForm field fills are fully supported.

Updating metadata

Use the fluent setters to update the document's information dictionary. When the file already contains an XMP metadata packet, it is refreshed to stay consistent.

$pdf = PdfEditor::open('report.pdf');

$pdf->setTitle('Annual Report 2025')
    ->setAuthor('Acme Corp')
    ->setSubject('Finance')
    ->setKeywords('annual report finance 2025')
    ->setCreator('ReportGenerator 3.0');

$pdf->save('report-updated.pdf');
// or get the bytes directly:
$bytes = $pdf->output();

All five setters are optional - set only what you need. save() writes to a file; output() returns the PDF bytes as a string.

Appending pages

appendPage() appends a blank page and returns a fully functional Page object. You can draw on it with the complete page API: cells, images, tables, barcodes, SVG, Markdown, and so on.

use DragonOfMercy\PhpPdf\PdfEditor;
use DragonOfMercy\PhpPdf\Font;

$pdf = PdfEditor::open('contract.pdf');

$page = $pdf->appendPage();
$page->setFont(Font::helvetica(), 11);
$page->cell(0, 10, 'Addendum - see attachment A');

$pdf->save('contract-with-addendum.pdf');

The appended page is part of the same incremental revision as any metadata edits you make, so a single save() / output() call writes everything at once.

Filling form fields

Open a PDF that contains an AcroForm, inspect its fields, set values, and save. Each filled field gets a generated appearance stream (/AP) so the value is rendered without relying on viewer-side rendering, and /NeedAppearances false is written.

Listing fields

use DragonOfMercy\PhpPdf\PdfEditor;

$pdf = PdfEditor::open('form.pdf');

foreach ($pdf->formFields() as $field) {
    echo $field->name, ' [', $field->type, ']', PHP_EOL;
}

formFields() returns list<FormFieldInfo>. field($name) returns a single FormFieldInfo or throws if the name is not found.

Setting field values

$pdf = PdfEditor::open('application.pdf');

$pdf->setField('FullName', 'Alice Dupont');          // text (single-line or multiline)
$pdf->setField('Subscribed', true);                  // checkbox
$pdf->setField('Gender', 'female');                  // radio (export-name string)
$pdf->setField('Country', 'ch');                     // combobox (export key)
$pdf->setField('Languages', ['en', 'fr']);            // listbox (one or more export keys)

$pdf->save('application-filled.pdf');
Field type Value type Notes
Text (single-line) string Written as-is.
Text (multiline) string Newlines preserved.
Checkbox bool true checks the field.
Radio button string The export name of the button to select.
Combobox string The export key of the chosen option.
Listbox string|list<string> Pass an array to select multiple items.

Font support for appearances. Appearance generation supports Standard-14 /DR fonts and non-Standard-14 simple fonts (Type1 / TrueType) that are embedded in the source /DR. When a field's /DA names an embedded simple font, the library reuses it from the source PDF - the font is referenced, not re-embedded - and uses its /Widths for measurement and its /Encoding (WinAnsiEncoding base, with /Differences) for encoding. Fail-fast limits that throw a PdfException naming the field: Type0 / composite embedded fonts are not yet supported; simple fonts with a base encoding other than WinAnsiEncoding are rejected; a font missing /Widths is rejected; and a character not representable in the font's encoding is rejected (no silent substitution).

Flattening fields

$editor = PdfEditor::open('form.pdf');
$editor->setField('name', 'Jane Doe');
$editor->setField('agree', true);
$editor->flattenFields();            // freeze every value-bearing field
// or: $editor->flattenFields(['name']); // freeze a subset

$editor->save('flattened.pdf');

Flattening burns each widget's appearance into the page content and removes the interactivity. Signature and push-button fields are preserved. It works on encrypted sources and is written as an incremental revision, so the original bytes stay intact.

Signing

PdfEditor::sign(), addSignature(), addDocumentTimestamp(), and enableLtv() work on an opened PDF with full parity to Document. Each signature, document timestamp, or /DSS is written as a stacked incremental revision that leaves the original bytes intact and cryptographically covers every prior byte - including any metadata edits, appended pages, and field fills, which are written as the first revision.

use DragonOfMercy\PhpPdf\PdfEditor;
use DragonOfMercy\PhpPdf\Signature\SigningCertificate;
use DragonOfMercy\PhpPdf\Signature\Tsa;

$pdf = PdfEditor::open('form.pdf');
$pdf->setField('FullName', 'Alice');

$pdf->sign(
    SigningCertificate::fromPkcs12('cert.p12', 'password'),
    field: 'Approval',
    reason: 'I approve this document',
    timestamp: Tsa::http('https://freetsa.org/tsr'),
)->save('signed.pdf');

See the Digital Signatures page for the full API reference: signature formats (adbe.pkcs7.detached / ETSI.CAdES.detached), multiple signers, document timestamps, long-term validation (LTV / PAdES-B-LT / B-LTA), and visible signature appearance.

Limitations

  • Signing, adding document timestamps, and enabling LTV on an encrypted source PDF are not yet supported (PdfEditor throws a PdfException at sign time; metadata edits, appended pages, and form field fills work normally on encrypted sources).
  • Appearance generation (form-fill) supports Standard-14 fonts and non-Standard-14 simple fonts (Type1 / TrueType) embedded in the source /DR. Type0 / composite embedded fonts are not yet supported (the library throws a PdfException naming the field).
  • A source whose /AcroForm is a direct (inline) dictionary is rejected when signing - the /AcroForm must be an indirect reference.
  • The visible signature caption (when using SignatureAppearance) is drawn with Standard-14 Helvetica only.

See also

Clone this wiki locally