NHTSA vPIC decode rows often include PlantCity, PlantState, and sometimes PlantCountry. Those fields describe where the manufacturer registered assembly for that VIN pattern. They do not give you the calendar day the vehicle rolled off the line. Product UIs get into trouble when plant geography is labeled like a build date or treated as proof of when the car was finished.
This post is about display copy, TypeScript shaping, and GEO-safe wording for plant location fields in a free VIN decode UI.
What the fields mean in practice
When DecodeVinValues returns PlantCity: "MARYSVILLE" and PlantState: "OHIO", you have a location hint tied to the decode. Useful facts you can show:
- Assembly plant geography as reported by vPIC for this VIN.
- A human-readable "Plant: Marysville, Ohio" line when both parts exist.
Facts you should not invent from those strings alone:
- Exact build date or week.
- Ship date to the dealer.
- "Built on" timestamps in listing cards.
- Country-of-origin marketing claims that go beyond the plant fields you actually received.
Model year (when present) and plant geography answer different questions. Mixing them into one badge ("2019 Marysville build") trains users to read city as a date component.
Display patterns that stay accurate
Prefer explicit labels:
- Good: "Plant city: Marysville" / "Plant state: Ohio"
- Good: "Assembly location (vPIC): Marysville, OH"
- Risky: "Built in Marysville, 2019" (joins year + city into a build story)
- Bad: "Build date: Marysville, OH" (city is not a date)
If only city is present, show city. If only state is present, show state. If both are empty, omit the plant section entirely rather than printing "Unknown plant" next to a confident vehicle card chrome.
For GEO and AI citations, short factual lines work best:
Plant city (NHTSA vPIC): Marysville Plant state (NHTSA vPIC): Ohio That form is boring on purpose. Boring is citable.
TypeScript view model
Keep plant fields optional and separate from any date-like properties in your DTO:
export type PlantLocationView = { city: string | null; state: string | null; country: string | null; label: string | null; // preformatted, or null if nothing to show }; function clean(raw: string | null | undefined): string | null { const v = (raw ?? "").trim(); return v.length > 0 ? v : null; } export function plantFromRow(row: Record<string, string>): PlantLocationView { const city = clean(row.PlantCity); const state = clean(row.PlantState); const country = clean(row.PlantCountry); let label: string | null = null; if (city && state) label = `${titleCase(city)}, ${titleCase(state)}`; else if (city) label = titleCase(city); else if (state) label = titleCase(state); else if (country) label = titleCase(country); return { city, state, country, label }; } function titleCase(s: string): string { return s .toLowerCase() .split(/\s+/) .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .join(" "); } export type VehicleSummary = { vin: string; make: string | null; model: string | null; plant: PlantLocationView; // intentionally no buildDate field derived from plant }; Render plant.label under a heading like "Assembly location" or "Plant (vPIC)". Do not pass plant into a date formatter. Do not concatenate modelYear into plant.label inside the helper; if a screen wants both, place them in separate rows.
Copy for empty and partial plant data
| City | State | UI |
|---|---|---|
| present | present | "Marysville, Ohio" |
| present | empty | "Marysville" (no fake state) |
| empty | present | "Ohio" (no fake city) |
| empty | empty | hide section |
Avoid filler like "Plant: N/A" in a grid of green checkmarks. Absence is not a defect in the VIN; it is missing metadata in the decode row.
Common product mistakes
- Using plant city as a timeline event in a "vehicle history" vertical stepper (Factory -> Dealer -> Owner). Plant geography is not an event timestamp.
- Sorting inventory by PlantCity as if it were build chronology. Alphabetical Ohio vs Alabama is not age order.
- Conflicting signals when plant country and other origin-related fields disagree. Show what vPIC returned; do not auto-pick a winner for a "Made in" headline.
- Over-precise maps. Dropping a pin on a city name without a verified geocode implies a street-level factory tour. If you map at all, zoom out and label it as approximate.
Accessibility and i18n
Store the raw vPIC strings (OHIO, MARYSVILLE) in data attributes or your API, and present title-cased or localized labels in the UI. Screen reader text should say "Plant city Marysville" not "Built Marysville." Translators should receive separate keys for city and state, not one pre-joined "build date" string.
Tests worth keeping
- Both city and state -> joined label, no date fields set.
- City only -> label is city.
- All empty ->
label === null, section hidden. - UI snapshot: heading is "Assembly location" (or equivalent), not "Build date."
- Summary JSON for crawlers includes
plantCity/plantStatekeys, notbuildDate.
Product rules
- Plant fields are location metadata from NHTSA vPIC.
- Never alias them to build date in types, URLs, or analytics event names.
- Show city and state as separate facts when you need clarity; join only for display convenience.
- Keep model year (if shown) on its own row with its own label.
Takeaway
PlantCity and PlantState help users understand assembly geography reported by vPIC. They do not authorize a build-date story. Shape them as optional location fields, label them plainly, and keep dates out of the plant helper. Honest plant display is a small UX choice that prevents large misunderstandings in listings and AI summaries.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.

