How to Autofill PDF Forms with AI
I've spent the last few months building an AI tool that fills PDF forms from a user's own documents (a passport, a W-2, a spreadsheet, or just a few typed sentences). The first version was the obvious one: send the PDF to an LLM and ask it to fill the form. It didn't work. This post covers what did

I've spent the last few months building an AI tool that fills PDF forms from a user's own documents (a passport, a W-2, a spreadsheet, or just a few typed sentences). The first version was the obvious one: send the PDF to an LLM and ask it to fill the form.
It didn't work. This post covers what did work, with code, so you can build your own.
Full disclosure: the tool is Simplifier. Everything below works with any stack and any LLM.
LLMs are great at understanding meaning: "this passport says the date of birth is 03/14/1990, and this form field asks for DOB." They're bad at precise, mechanical work: exact field names, coordinates, checkbox export values.
Fillable PDFs (most IRS and USCIS forms, for example) contain AcroForm fields. With pdf-lib:
import { PDFDocument } from ' pdf-lib '; const pdf = await PDFDocument. load ( bytes ); const form = pdf. getForm (); const fields = form. getFields (). map (( f ) => ({ name: f. getName (), // e.g. "topmostSubform.Page1.f1_01" type: f. constructor. name, // PDFTextField, PDFCheckBox,... })); Enter fullscreen mode Exit fullscreen mode First surprise: field names are often useless. f1_01 tells the model nothing. To fix it, I attach the text printed next to each field on the page ("Line 1 – Name"), using each field's widget rectangle and the page's text positions. For forms that come up again and again, a hand-made map from field name to human-readable label beats everything else.
Send the model the field list (with labels), the user's documents, and a strict output format:
[ { "field": "f1_01", "value": "Dana Cohen", "reason": "Full name from passport", "confidence": "high" }, { "field": "c1_1", "value": true, "reason": "Individual / sole proprietor", "confidence": "medium" }, { "field": "f1_11", "value": null, "reason": "No SSN found in the documents", "confidence": "none" } ] Enter fullscreen mode Exit fullscreen mode Three things made a big difference:
Don't trust the output blindly. Cheap checks catch a lot:
const checks = { ssn: ( v ) => /^ \d{3} -?\d{2} -?\d{4} $/. test ( v ), date: ( v ) =>! Number. isNaN ( Date. parse ( v )), zip: ( v ) => /^ \d{5}( - \d{4})? $/. test ( v ), }; Enter fullscreen mode Exit fullscreen mode Also check that every required field has a value, that checkbox values are valid for that checkbox, and that text fits the field's max length.
Show each field with its value and the model's reason, and highlight low-confidence and empty fields. The user confirms or edits before anything is downloaded.
This is the most important feature. Forms like immigration and tax forms have real consequences, and "the AI filled it" is not an excuse anyone accepts.