A bar of 120 PDF tiles with 30 marked as declaring an AcroForm, only 5 of those filled in green as actually containing a field, and 4 tiles blacked out as having no extractable text.

I was auditing which of our own one-dollar utilities have no way in from a search engine, and one of them turns a flat PDF into a fillable form. Before writing a word about it I wanted the thing a reader needs to know first: how do you tell a fillable PDF from a flat one without opening it?

The advice you find is to look for /AcroForm. So I ran that over every PDF on this machine, 120 files, all of them parsed cleanly by pypdf 6.12.2. Thirty declare an /AcroForm. Five contain a field. Twenty-five of those declarations are empty, and the worst offender is an 835-page programming book.

What /AcroForm actually promises

Nothing you can type into. /AcroForm is one dictionary in the document catalog, and the fields it would describe live in its /Fields array. A producer can write that dictionary with the array empty or absent, and plenty of them do: export a book from a layout tool and the /AcroForm can come along for free because the toolchain emits one.

pdfinfo, which is the proper tool rather than a grep, repeats the declaration without checking it. On the 835-page book it prints Form: AcroForm. The book has no fields. That line is a claim the file makes about itself.

The field count is the only honest test

Count the fields and the number stops lying:

python3 -c 'import pypdf,sys; print(len(pypdf.PdfReader(sys.argv[1]).get_fields() or {}))' form.pdf

Across the same 120 files that returns non-zero five times. The strings version of the same idea, grepping the raw bytes for /Widget, the annotation that carries one field, found only four of the five. The one it missed keeps its form inside a compressed object stream, where the name never appears as plain text anywhere in the file. A grep over PDF bytes is a lower bound, not a count. It was wrong a second way about the one genuinely fillable form in the set, a two-page salvage-title application: zero raw-byte hits for /XFA, while pdfinfo reads the same file and reports Form: XFA correctly.

The case no tool can fix

Four of the 120 return zero characters of extractable text. They are pictures of pages. This is the case worth knowing before you pay anybody, because every blank-detecting tool, ours included, works by reading the text and finding where the blanks are. With no text there is nothing to find. One command settles it:

pdftotext form.pdf - | tr -d '[:space:]' | wc -c

If that prints a number near zero, no field detector can help you yet. Put the file through OCR first, then try again.

Three outcomes

Field countText charactersWhat you have
> 0anyAlready fillable. Open it and type. If it shows a “please wait” placeholder forever, it is an XFA form and wants Adobe.
0about 0A scan. OCR it before any tool can see a blank.
0plentyA flat text form. The only case where blank detection has anything to work with.

Two commands, run before you pay anyone, us included.

The one-dollar shortcut

For that third row we built PDF → fillable: upload the flat form, pay $1 by card, get back a PDF with real interactive fields. No account, and we keep no copy of either file. Two limits worth stating plainly: it refuses scanned PDFs, for the reason above, and to locate the blanks it sends the first 12,000 characters of the document’s text to OpenRouter, an outside AI service. Upload the blank form, not one you have already filled in. The same endpoint answers AI agents over HTTP 402.

What I don’t know

This is one disk, and a programmer’s disk: the 25 empty declarations are mostly technical ebooks, so treat thirty-and-five as the shape of the problem rather than a rate to expect on yours. get_fields() reads the AcroForm field tree, so a pure XFA form with no AcroForm fallback could in principle count zero while still being fillable in Adobe; every form here had both. And none of this tells you whether the fields a file does have sit in the right places. That takes opening it.