Position 10 of a 17-character VIN encodes the model year. It is one of the most useful characters for buyers and one of the easiest for developers to get wrong.
What position 10 means
The model year is a manufacturer designation. It is not always the calendar year the vehicle left the plant. A car built late in 2024 can still carry a 2025 model year. Treat position 10 as "model year code," not "build date."
The 30-year cycle
NHTSA and ISO-aligned practice reuse a set of year codes on a roughly 30-year cycle. Digits 1-9 cover early years in a cycle; letters fill the rest, skipping I, O, Q, U, Z and sometimes other confusing glyphs depending on the era table you implement.
If you hard-code only one decade of mappings, your tool will silently mislabel older or future VINs when the cycle rolls.
A practical TypeScript map
const YEAR_CODES: Record<string, number[]> = {
A: [1980, 2010],
B: [1981, 2011],
// ...extend for full tables you support
"1": [2001, 2031],
"2": [2002, 2032],
};
export function modelYearsForCode(code: string, preferRecent = true): number | null {
const years = YEAR_CODES[code.toUpperCase()];
if (!years?.length) return null;
return preferRecent ? years[years.length - 1] : years[0];
}
Prefer the recent year in consumer apps unless the rest of the decode (WMI, plant, make history) clearly points to the older cycle.
Common pitfalls
- Treating position 10 as build year.
- Accepting I, O, or Q as year codes.
- Using only a 2001-2029 digit/letter subset and failing on classic cars.
- Ignoring that some markets and specialty vehicles have quirks; always cross-check with NHTSA vPIC when available.
How this fits a free decode
A free NHTSA-backed decode should show model year with a short note that calendar build date may differ. That honesty prevents buyer confusion and keeps your UX aligned with what the data actually means.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)