Address fields are where forms go to die
Ask anyone who has ever cleaned a customer database what the worst field is and they will say the same thing: the city. Not the email, not the phone number — the city, because it is the field people get wrong without ever realising they got it wrong.
This is a long post about a small plugin. It covers why address entry fails, what the data actually looks like underneath, the three approaches we tried before settling on the fourth, and what it costs to do it properly. If you have ever exported a customer list and found “Springfeild”, this is for you.
The problem, concretely
A typical checkout or quote form asks for street, city, state and ZIP. Four free-text fields, four opportunities for a typo, and no relationship between them enforced anywhere. What comes out the other side looks like this:
- Springfield, OH 45503 — correct.
- Springfeild, OH 45503 — one transposition, and now your city grouping has two Springfields.
- Springfield, OK 45503 — a mis-selected state. The ZIP says Ohio. Your shipping calculator says Oklahoma.
- Springfield, OH 45053 — transposed ZIP. That is a different county, a different tax jurisdiction, and possibly a different service area.
Every one of those submits cleanly. No validation catches them, because each field is individually plausible. You find out weeks later when something is delivered to the wrong place or a tax return does not reconcile.
The second-order costs
The wrong address is only the beginning. Downstream, you get:
- Broken segmentation. “Customers in Clark County” quietly misses everyone whose county was inferred from a bad ZIP.
- Tax errors. In states with local sales tax, the jurisdiction is determined by address. A wrong ZIP is a wrong rate.
- Service-area mistakes. If you quote differently inside and outside a radius, a bad postcode quotes the wrong price.
- Duplicate records. Two spellings of one city become two customers.
- Form abandonment. Every extra field is friction, and address blocks are the longest part of most forms.
What the data actually looks like
The useful insight is that the ZIP code already contains everything else. Five digits determine, unambiguously, the city, the state, the county, the approximate coordinates and the timezone. If you have the ZIP, asking for the city is asking the user to re-enter information you already have — and giving them a chance to get it wrong.
There are roughly 41,000 US ZIP codes in active use. The full dataset with city, state, county, latitude, longitude and timezone is a few megabytes. That is small enough to sit in a database table on any host you are likely to be using.
The awkward edges
It is not quite as clean as that paragraph implies, and this is where most implementations fall over:
- One ZIP, several place names. A ZIP often has one official city plus several acceptable alternates. Pick the wrong one as your default and locals will think your form is broken.
- ZIPs that cross county lines. Uncommon but real, and it matters for tax.
- ZIPs that cross state lines. Rare, but they exist.
- PO box and unique ZIPs. Some ZIPs belong to a single large organisation and have no residential meaning at all.
- Timezone is not derivable from the state. Ask anyone in Indiana.
Three approaches that did not survive
1. Call a third-party API
The obvious answer, and the one we shipped first. It works beautifully until one of four things happens: the free tier runs out, the vendor rate-limits you mid-checkout, the vendor has an outage and takes your form down with it, or someone in legal asks why customer addresses are being sent to a company nobody has a contract with.
The latency is the quiet problem, though. A round trip to an external API is 100–400ms. Fire that on every keystroke and the field feels laggy; fire it on blur and the user has already tabbed past.
2. Ship a flat file
A JSON file of every ZIP, loaded into memory. Simple, no dependencies — and a multi-megabyte parse on every request, which is exactly the kind of thing that makes people blame WordPress for being slow.
3. Autocomplete the city instead
Let people type the city and suggest matches. This fixes spelling but not the relationship between fields — you can still pick Springfield, Ohio and then type an Illinois ZIP underneath it, and nothing objects.
What we settled on
An indexed database table, queried locally, triggered on the ZIP field.
The plugin creates one table at activation and imports the dataset once. The lookup is a single indexed query on a five-character primary key — sub-millisecond, on any host, every time. The ZIP field fires the lookup at five digits, and the city, state, county and timezone fields fill themselves in.
Where a ZIP has multiple acceptable city names, the official one is filled in and the alternates are offered in a dropdown rather than hidden. Where a ZIP crosses a county line, the primary county is returned and the alternate is exposed to code that cares. Nothing is guessed silently.
Working with forms that were not built for it
The genuinely fiddly part was never the data. It was that every form plugin names its fields differently, and half of them re-render the DOM after you have bound to it. The plugin binds by event delegation rather than direct listeners, so fields that appear after page load — conditional sections, repeatable groups, multi-step forms — behave the same as fields that were there from the start.
Field mapping is one filter:
add_filter( 'zyzx_zip_field_map', function ( $map ) {
$map['city'] = 'billing_city';
$map['state'] = 'billing_state';
$map['county'] = 'billing_county';
$map['timezone'] = 'appointment_tz';
return $map;
} );
What it is worth
The honest case for this is not that typing a city is hard. It is that:
- Four fields become one for the user, which measurably reduces abandonment on long forms.
- The city, state and county can no longer disagree with the ZIP, because they are derived from it.
- You get county and timezone, which almost nobody asks for and plenty of businesses need.
- There is no API key, no per-lookup cost, no rate limit and no third party in the path of your checkout.
- It keeps working when the internet does not.
Where it is going
Canadian postal codes are the most requested addition and are in progress. UK postcodes are harder — the data licensing is genuinely complicated — but they are on the list. Address-line autocomplete is not planned, because that genuinely does need a live service, and the entire point of this plugin is not needing one.
ZIP Autofill is in the catalog now. One-time price, local data, quarterly refreshes included.