Extracting Structured Fields From a Voter Registration Form
9 min read · updated August 11, 2026
Written for an election office, a records team or a registration organisation that already holds these forms and has to get the data off them. The structural surprise is that the required fields are not a property of the form. They are a property of the state box the applicant filled in.
One form, fifty required-field sets
The US Election Assistance Commission publishes a National Mail Voter Registration Form that is accepted, with exceptions, across states covered by the National Voter Registration Act. The document is a short application page followed by a substantial appendix of state-specific instructions, and that structure is the whole point: one application layout, fifty-odd sets of rules about what must be completed on it.
The practical consequence for extraction is that validation cannot be written once. The applicant’s state determines which fields are mandatory, what identification number is acceptable, whether a party affiliation choice is required or meaningless, and what additional information the state expects. So the pipeline reads the state field first and then selects a rule set, rather than applying one required list to every document.
// The state field is read before validation, not alongside it.
const record = extractFields(scan); // transcription only
const rules = ruleSetFor(record.state); // per-state configuration
const issues = rules.required
.filter((f) => isBlank(record[f]))
.map((f) => ({ field: f, kind: "required_blank", state: record.state }));
// A record whose state field itself is unreadable cannot be validated at all.
if (!record.state) issues.push({ kind: "state_unresolved", blocks_validation: true });The state_unresolved case is worth calling out because it is the one that quietly produces wrong output. A form whose state box was misread is not a form with one bad field; it is a form validated against the wrong rules, which can pass while missing something the actual state requires. Treat it as blocking rather than as one issue among several.
The attestation boxes are not ordinary fields
The federal form puts two eligibility questions at the top with Yes and No boxes — whether the applicant is a United States citizen, and whether they will be old enough to vote by election day — and instructs an applicant who answers No to either not to complete the form. Lower down, the signature attests to the application’s truth under penalty of perjury.
Three properties make these different from the rest of the form.
- Absence is meaningful and is not a default. An unticked citizenship box is not a No and it is not a Yes. It is an incomplete application, and it is a distinct outcome from either answer. Model the value as an enum with
yes,noandunmarkedmembers, never as a boolean, and never let a schema with a default value fill it in. A default on this field is a fabricated attestation. - Both boxes ticked is a real state. People tick both in error. It is not a No and not a Yes; it is
ambiguous, and it needs a human. A checkbox detector that returns the box with the most ink will pick one and hide the problem. - The mark may not be in the box. As on any hand completed form, the answer may be a circle around the word, a cross through the box, or a tick placed between them. The general treatment of marks that are not ticks is on the mixed handwriting and print page, and it applies here with the additional rule that an ambiguous mark on an attestation is never resolved automatically.
The signature is a separate presence check, and it is separate again from the date beside it. As on a consent form, the useful question about a signature region is whether ink is present rather than what name it spells, and reporting the two checks independently tells whoever clears the queue what is actually missing.
Residence address, mailing address, and no address
The form asks for a residence address — where the applicant physically lives, which determines their precinct — and separately for a mailing address where mail is received. These are different fields for a reason and the reason bites in extraction.
A post office box is a valid mailing address and not a valid residence address. An applicant who writes their PO box in the residence field has made an error your validation can catch by pattern. An applicant who leaves the mailing address blank has said it is the same as the residence, and an applicant who writes “same” or draws an arrow has said the same thing in a way that needs resolving as a reference rather than stored as a literal.
The case that defeats naive address validation is the applicant with no street address at all. Rural and tribal addresses without street numbers are accommodated by a section of the form for describing the location — cross streets, landmarks, a sketch. That content is legitimately not parseable into address components, and a pipeline that requires a street number will reject valid applications from exactly the population the provision exists for. Model residence location as a union: a structured address, or a description with the raw text preserved.
Name fields carry their own load. The form provides for a former name and for a change of address, because a re-registration and a new registration look identical unless you read those fields. Suffixes (Jr, Sr, III) belong in their own field rather than glued to the surname, since matching against existing records is the next step and a suffix in the wrong field breaks it.
The identification number field
Federal law requires an applicant to supply an identification number, with the acceptable number depending on what the applicant has — a state driver’s licence or state identification number, or the last four digits of a social security number where the applicant has no state number. The form asks for one or the other and the distinction is part of the value.
Extract the number together with which kind it is, because they are validated differently and stored differently. Four digits in a field intended for a licence number, or a licence-shaped string in a field meant for four digits, is a transcription error worth catching at the point of extraction. Where the state publishes a licence number format, a pattern check is available and is better than a confidence score, on the same reasoning as any other format-constrained field.
This is also the field with the highest handling obligation on the page. Partial social security numbers are sensitive identifiers; they do not need to travel to a model to extract the rest of the form, and the cheapest way to keep them out of a third-party service is to crop the region out of what you send and handle it separately, or to mask it before the request leaves your network. The redaction page covers the mechanics; the decision to apply them here is not a close call.
What the extraction must not do
Three limits are worth stating explicitly, because they are the ones an otherwise well-built pipeline drifts across.
It must not infer eligibility. The form records attestations; whether somebody is eligible to register is a determination made by an election official under law, not a computation over extracted fields. The output’s job is to say faithfully what the applicant marked, including that they marked nothing.
It must not normalise away a discrepancy. If the date of birth implies an age inconsistent with the age attestation, that is a finding to surface, not a field to correct. The same goes for a name that differs from an existing record: report the mismatch and let the process that owns identity resolution handle it.
And it must not treat completeness as validity. A form where every field was extracted with high confidence can still be an application that cannot be processed, and a form with three unreadable fields can still be a valid application that needs one phone call. Reporting per-field status alongside per-field values — rather than a single document score — is what lets the people downstream tell those two situations apart.