“Signature” means two different things
People asking about signatures usually mean one of two jobs, and a PDF stores them very differently. DartPDF does both, and can combine them.
Your handwriting on the page. Good for everyday sign-and-return. It is ink on the page, so it doesn't prove who drew it.
A cryptographic signature from a certificate. It detects changes to the signed bytes. Establishing who signed also requires trusting the signer's certificate and its issuing authority.
A visible signature box on the page, showing the drawn signature, backed by a real digital signature.
Fillable AcroForm fields
AcroForms are the standard interactive form fields defined by the PDF specification. They are what Acrobat, Word's PDF export, and most form designers produce. DartPDF finds every field in the document, including widgets a broken file left attached to a page but missing from the field tree, and handles each field type as follows:
| Field type | Read & fill | Add new |
|---|---|---|
| Text (single-line & multiline) | Yes: wrapping, auto-size, alignment, RTL text, custom font/size/colour, /MaxLen
limits, comb fields (one character per box), password masking |
Yes |
| Checkbox | Yes | Yes |
| Radio group | Yes | Not yet |
| Combo box / list box | Yes: by export or display value; editable combos accept free text; multi-select list boxes take several values | Not yet |
| Push button (image) | Yes: fill with a PNG/JPEG, e.g. a photo or signature image | Yes |
| Signature field | Detected, signed, and validated (see below) | Created when you sign |
The important detail is appearance generation. In a PDF, a field's value and how it
looks on the page are stored separately. A library that only sets the value leaves a form that looks
empty in some viewers, or that Acrobat redraws with its own layout. DartPDF redraws every field it fills,
including wrapping, auto-sizing, quadding (alignment), borders, backgrounds, and page rotation, and then
clears /NeedAppearances. A value longer than the field's /MaxLen is cut to
fit, a comb field puts one character in each box, and a password field is drawn as asterisks and
edited masked, so the password never appears on the page. Give the editing controller a
PdfFormSecretStore and a password isn't written into the PDF at all, as the PDF
specification asks: the file keeps a fixed-length mask, and the value is kept in the device's
keychain, so it comes back when the same document is reopened on that device. The DartPDF app does
this on desktop and mobile. On the web it keeps passwords only in memory until the page closes. The saved file looks the same in every viewer, and it prints
correctly.
Calculated totals, formats, and validation
Most form scripts are calls to a small set of built-in AF* helpers. DartPDF recognises them
and runs a Dart version of each, so these work with no JavaScript engine:
- Calculations:
AFSimple_Calculate(sum, average, product, min, max) and simplified field notation such asQty * Price. They re-run in the form's calculation order (/CO) after every fill, including read-only total fields. - Formats:
AFNumber_Format(separators, negative styles, currency),AFPercent_Format,AFDate_FormatEx/AFTime_Format, andAFSpecial_Format(zip, zip+4, phone, SSN). The field shows the formatted text and stores the raw value, as Acrobat does. - Keystroke and validation: the matching
*_Keystrokehelpers andAFRange_Validate. In the Flutter editor a number field won't accept letters, and an out-of-range or unreadable value is refused with a message. From code,editor.enterTextValue(field, text)applies the same checks and throws aPdfFieldInputException.setTextValuestays the unchecked programmatic setter.
Scripts that aren't one of these helpers never run. field.scripts lists which of a field's
scripts were recognised and which were skipped, so your app can warn about forms that depend on custom
code.
In a Flutter app
Form filling is on by default. PdfEditorView (the full editor) and PdfReader
(the read-only viewer) both let users tap into fields. Tapping a text field opens an inline editor,
checkboxes and radios toggle, choice fields open a menu, and image buttons open the host's image picker.
To add form filling to a custom viewer, pass it an editing controller:
final session = PdfEditingController(pdfBytes);
PdfViewer(
formController: session, // tap-to-fill, no editing toolbar
formImagePicker: (context, field) => pickImageBytes(context),
);
// Or fill from code, e.g. to prefill from your backend:
session.setFormFieldText('applicant.name', 'Ada Lovelace');
session.toggleFormCheckBox('terms');
session.setFormChoiceValue('country', 'Australia');
final Uint8List filled = session.bytes;
Every change is an incremental revision, so undo and redo work for form input too.
On a keyboard, Tab moves to the next field and Shift+Tab to the previous one, across pages. The
order follows each page's /Tabs setting (row, column, or structure order) and falls
back to the order the fields are stored in. Space toggles a focused checkbox or radio button.
Read-only, hidden, button, and signature fields are skipped.
In pure Dart (server, CLI, tests)
The form engine is in pdf_document, which has no Flutter dependency. That makes it a good
fit for batch-filling a template on a server:
import 'package:pdf_document/pdf_document.dart';
Uint8List fillApplication(Uint8List template) {
final editor = PdfEditor(PdfDocument.open(template));
final form = editor.acroForm!;
for (final field in form.fields) {
print('${field.name}: ${field.type} = ${field.value}');
}
editor.setTextValue(form.fieldNamed('name')!, 'John Doe');
editor.setCheckBoxValue(form.fieldNamed('agree')!, true);
editor.setRadioValue(form.fieldNamed('color')!, 'Blue');
editor.setChoiceValue(form.fieldNamed('size')!, 'Large');
// Optional: burn the values into the page so they can't be edited.
editor.flattenForm();
return editor.save();
}
To add fields, use addTextField, addCheckBoxField,
addPushButtonField, addRadioGroup (one field, one button per option, grown
later with addRadioButton), addComboBoxField and addListBoxField
(options as export/display pairs, edited later with setChoiceOptions), and
addSignatureField (an empty box for someone to sign later). renameField,
removeField, and changeFieldType handle field management. If the document has
no AcroForm yet, the first new field creates one. Every new field gets its appearance generated the
same way filling does. The editor UI has matching tools, so users can draw fields onto a plain PDF and
turn it into a fillable form, then edit a choice field's options or add buttons to a radio group.
final editor = PdfEditor(PdfDocument.open(plainPdf));
editor.addRadioGroup(0, 'size', [
(PdfRect(72, 500, 88, 516), 'S'),
(PdfRect(100, 500, 116, 516), 'M'),
]);
editor.addComboBoxField(0, 'country', PdfRect(72, 450, 272, 472), [
('AU', 'Australia'),
('NZ', 'New Zealand'),
]);
editor.addSignatureField(0, 'Approver', PdfRect(72, 72, 272, 132));
final Uint8List form = editor.save();
// Later, whoever signs fills the placed field by name:
PdfEditor(PdfDocument.open(form)).saveSigned(
privateKey: key, certificates: chain, fieldName: 'Approver');
Drawn (handwritten) signatures
The editor ships a signature pad with pressure-sensitive strokes, pen colour and width, and a saved
signature library. Users draw once, then tap anywhere to place the signature. The library persists on
the device through PdfEditingPreferences, and users can rename, redraw, or delete
entries.
// Show the pad, keep the result in the library, and place it.
final ink = await showPdfSignatureDialog(context);
if (ink != null) {
session.addSavedSignature(ink, name: 'Full signature');
session.placeSignature(0, 300, 200); // page 0, centred on (300, 200) in PDF points
}
A placed signature is a vector ink annotation. It stays crisp at any zoom, can be moved and resized,
and appears in any viewer. To stop it being moved or removed, flatten it into the page content, using the
editor's Flatten action or flattenDocument(). If a form has an image button meant
for a signature, filling that button with a picture of the signature also works.
Digital signatures (PAdES)
DartPDF writes standard adbe.pkcs7.detached / PAdES signatures, and it does the
cryptography (CMS, X.509, RSA, ECDSA) in Dart. It has no OpenSSL dependency and no native plugin. The
output is interoperable: pyHanko rates our PAdES B-LTA files valid and LTV-enabled with no network
access.
| Capability | Support |
|---|---|
| Signing keys | Your own RSA or ECDSA key and certificate chain; an external signer callback for HSMs and platform keystores |
| PAdES levels | B-B, B-T (RFC 3161 timestamp), B-LT (embedded OCSP/CRL in a /DSS), B-LTA (plus a document timestamp) |
| Identities | One-tap self-signed P-256 identities, your own organisation CA that issues member certificates, or keyless Sigstore certificates tied to an email sign-in |
| Appearance | Invisible, or a visible box with the signer's name, date, reason, location, drawn signature, and logo backdrop. The box can be repeated on other pages. |
| Existing fields | Signs into an empty signature field a form designer already placed, including one added with
addSignatureField or the editor's form tool |
| Certification | Certify (DocMDP) signatures with P=1/2/3 change permissions; additional approval signatures are refused after P=1 certification. General editing APIs do not enforce DocMDP permissions. |
| Password-protected PDFs | Signs RC4, AES-128, and AES-256 encrypted files in place. The document keeps its password and the signature validates after reopening. |
| Multiple signers | Each signature is an incremental update, so countersigning leaves earlier signatures valid |
| Validation | Checks the digest, the signature, and whether the whole document is covered; builds the chain against your trust store; checks the signer and each intermediate for revocation (embedded data, plus live OCSP/CRL through a client you supply); reports PAdES level and timestamp |
Sign with a visible box
final identity = PdfSigningIdentity.generate(
name: 'Ada Lovelace',
email: 'ada@example.com',
);
final editor = PdfEditor(PdfDocument.open(filledPdf));
final signed = await editor.saveSelfSignedPades(
identity: identity,
level: PdfPadesLevel.bT, // trusted time from a TSA
timestampClient: myTimestampClient, // you supply the HTTP call
reason: 'Approved',
location: 'Melbourne',
appearance: PdfSignatureAppearance(
page: 0,
rect: const PdfRect(72, 80, 320, 150),
graphic: PdfEmbeddableImage.png(drawnSignaturePng),
),
);
To sign into a field the form already has, pass fieldName: 'ApproverSig'. The box then
takes the field's existing position. For a company certificate, use saveSignedPades with
your RsaPrivateKey and certificate chain. The library does no network I/O of its own:
timestamp, OCSP, and CRL transports are callbacks you provide, so it runs on the web and never contacts
a server you didn't choose.
In the Flutter editor
The editor's signature-box tool lets users drag out a rectangle on the page. Your
onPlaceSignature callback receives the page and rectangle, and the controller does the
signing:
final identity = PdfDigitalSignatureIdentity.fromFiles(
privateKey: privateKeyBytes,
certificates: certificateFileBytes,
);
await session.addDigitalSignature(
identity,
reason: 'Approved',
appearance: PdfSignatureAppearance(page: pageIndex, rect: pageRect),
);
Before committing a signature, the controller re-opens and validates it. So it will never save a
signature that doesn't verify. addSelfSignedSignature and addKeylessSignature
cover the other identity types. The package also ships a “Create signing identity” dialog and secure key
storage. The DartPDF app wires all of this into a single Digitally sign…
dialog.
Verify signatures
for (final sig in PdfSignature.of(PdfDocument.open(bytes))) {
final result = sig.validate(trustStore: PdfTrustStore.trusting([caDer]));
print('${sig.signerName}: intact=${result.intact} '
'trusted=${result.chainTrusted} level=${result.padesLevel}');
}
To also catch a certificate revoked since signing, validate online. The library still makes no
network calls of its own. pdfOnlineRevocationClient asks each certificate's OCSP
responder and falls back to its CRL, using an HTTP function you pass in:
final result = await sig.validateOnline(
trustStore: trustStore,
revocationClient: pdfOnlineRevocationClient(fetch: myHttpFetch),
);
for (final r in result.revocation) {
print('${r.certificate.subjectCommonName}: ${r.status} (${r.source})');
}
A certificate that was revoked makes the signature untrusted, unless a trusted timestamp proves the
document was signed before the revocation. If a status can't be checked (the responder is down, or
its answer is stale or doesn't verify), the result says so and leaves the trust verdict alone.
Trusted roots are opt-in: package:pdf_document/trust_lists.dart downloads and verifies the
EU trusted lists, and can load an Adobe Approved Trust List file you already have.
The editor's annotations panel shows the same result next to each signature: trusted, self-signed, issued by an authority you don't trust, revoked, or revocation unknown. The DartPDF app checks revocation online and trusts the EU trusted lists by default outside the browser.
What isn't supported (yet)
These are the gaps you're most likely to hit, so you know before you build on it:
- Dynamic XFA forms. XFA is the older XML-based Adobe forms format. Pure-XFA
(“dynamic”) forms keep their fields only in the XFA data, so they can't be filled here. The editor
shows a notice for them, and
PdfAcroForm.isDynamicXfalets your app detect them. Hybrid forms fill through their AcroForm fields. The first fill (or a flatten) removes the/XFAentry, so XFA-aware viewers show the AcroForm values rather than the old XFA data. - Custom form JavaScript. There is deliberately no JS engine. The built-in
AF*helpers that most forms use do work without one:AFSimple_Calculateand simplified field notation totals re-run in/COorder after every fill,AFNumber/AFPercent/AFDate/AFTime/AFSpecialformats change what a field shows while keeping its stored value, and their keystroke checks plusAFRange_Validaterefuse invalid entries with a message. Hand-written scripts, including scripted show/hide, are skipped; each field reports which of its scripts ran and which didn't (field.scripts). - Multi-select list boxes take a single value.
- Authoring covers text, checkbox, and image-button fields. You can't yet add radio groups, choice fields, or empty signature fields for someone else to sign later.
- Bundled trust roots. No certificate authorities ship inside the library. You can load the EU trusted lists at run time (the DartPDF app does this outside the browser), your organisation's CA, or Adobe's Approved Trust List. We never bundle or redistribute the AATL. Instead, the DartPDF app has a one-click, opt-in "Trust Adobe Approved Trust List" option (in Settings, or under an unknown signer in the signature panel) that downloads it from Adobe on your device. Nothing about your documents is sent. A self-signed signature validates as intact, but Acrobat shows its signer as unknown until the certificate is trusted.
- Revocation in the browser. Live OCSP/CRL checks need a transport that can reach the CA's servers, which browsers usually block (CORS), so the web app only uses revocation data embedded in the document.
If one of these blocks you, open an issue with a sample PDF. Real forms decide what we work on next.
Common questions
Will filled forms look right in Adobe Acrobat?
Yes. DartPDF writes each field's value and redraws its appearance stream, then turns off
/NeedAppearances so Acrobat doesn't redraw the field with its own layout. The field
stays editable in Acrobat unless you flatten the form.
Does filling a form break an existing signature?
Form fills are saved as incremental updates, so the signed bytes stay untouched and their cryptographic signature can remain intact. Validation reports that the signature no longer covers the whole document. A certification signature may still forbid the change: DocMDP P=1 forbids form filling, while P=2 and P=3 permit it. The editing APIs do not enforce those permissions, so check the document's certification policy before filling it.
Can I sign a password-protected PDF?
Yes. Open it with its password, fill it, and sign it. The new revision is encrypted like the rest of the file. The one exception is the signature value itself, which the PDF standard leaves unencrypted so any reader can verify it. The file keeps its original protection. Every signing path works this way: plain signatures, all PAdES levels, document timestamps, and certification.
Can I fill forms without Flutter?
Yes. pdf_document is pure Dart with no dart:io or Flutter imports, so the
same filling, flattening, and signing code runs on a server, in a CLI, or on the web.
Is a drawn signature legally binding?
That depends on your jurisdiction and use case, not on the library. Many everyday agreements accept a drawn signature. Some regulated workflows need an advanced or qualified signature. For those, sign with a certificate from a provider your regulator recognises. DartPDF produces the PAdES formats those schemes build on.
See it on a real form
The web demo's showcase PDF includes fillable fields and a signature area. Fill it in, draw a signature, and save it to check the output in your own viewer.