Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Decode a VIN
/**
* VIN decoder.
*
* kevinboutin on 3/11/18.
*
* My VIN for testing is WBA3A5G59DNP26082 so use the following command to invoke:
* node vindecoder WBA3A5G59DNP26082
*
* Examples:
* KM8JM12D56U303366
* 5NMSH73E88H134350
* 1GTHK29U74E341306
* JTLKT324X64085338
* 2HGEJ6446YH110899
* JN8AS5MT2DW022816
* 3FADP4BJ2FM195587
* 1G6AF5SX6D0125409
*
* VINs are made up of sections. The first three characters make up the World Manufacturer Identifier (WIN).
* Position 1 represents the nation of origin with position 2 identifying the manufacturer. Position 3, when
* combined with the first two characters, indicates the vehicle's type or manufacturing division.
* The next section encompasses characters 4-9 and is the Vehicle Descriptor Section (VDS). Positions 4-8
* describe the car with such information as the model, body type, restraint system, transmission type and
* engine code. Position 9, the check digit, is used to detect VINs that are not valid.
* The final section is named the Vehicle Identifier Section (VIS). Position 10 indicates the model year with
* the help of position 7. Position 11 indicates the manufacturing plant in which the vehicle was assembled.
* Each automaker has its own set of plant codes. The last six positions (positions 12-17) are the production
* sequence numbers.
*
* For more information on decoding the VDS, use https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)
*/
'use strict';
/**
* Transliterate VIN characters. This is useful in validating VINs.
*
* @param {string} c The character to transliterate.
* @return {number} The result of transliterated.
*/
const transliterate = (c) => {
return '0123456789.ABCDEFGH..JKLMN.P.R..STUVWXYZ'.indexOf(c) % 10;
};
/**
* Determine the check digit, which is needed to validate the VIN.
*
* @param {string} vin The entire VIN.
* @return {string} The check digit character.
*/
const getCheckDigit = (vin) => {
const map = '0123456789X';
const weights = '8765432X098765432';
let sum = 0;
for (let i = 0; i < 17; ++i) {
sum += transliterate(vin.charAt(i)) * map.indexOf(weights.charAt(i));
}
return map.charAt(sum % 11);
};
/**
* Validate the VIN.
*
* @param {string} vin The entire VIN.
* @return {boolean} True if valid.
*/
const validate = (vin) => {
if (vin.length !== 17) return false;
return getCheckDigit(vin) === vin.charAt(8);
};
/**
* Determine the year of the vehicle.
*
* @param {string} pos7 The seventh position character of the VIN.
* @param {string} pos10 The tenth position character of the VIN.
* @return {number} The year.
*/
const getYear = (pos7, pos10) => {
const years = [1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039];
const values = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let i = 0;
if (isNaN(pos7)) {
i = values.lastIndexOf(pos10);
} else {
i = values.indexOf(pos10);
}
return years[i];
};
/**
* Get the manufacturing country of vehicle.
*
* @param {string} wmi The World Manufacturer Identifier.
* @return {string} The manufacturing country.
*/
const getCountry = (wmi) => {
const codes = ['AAV', 'AHT', 'AFA', 'CL9', 'JA', 'JC1', 'JF', 'JHL', 'JHM', 'JMB', 'JM6', 'JN', 'JS', 'JT', 'JY', 'KL', 'KM', 'KN', 'KPT', 'L6T', 'LBE', 'LBV', 'LDC', 'LE4', 'LFM', 'LFP', 'LFV', 'LGB', 'LGJ', 'LGW', 'LGX', 'LH1', 'LHG', 'LJ1', 'LJD', 'LLV', 'LMG', 'LPA', 'LS5', 'LSG', 'LSJ', 'LSV', 'LTV', 'LVG', 'LVH', 'LVR', 'LVS', 'LVV', 'LWV', 'LZW', 'MS0', 'NMT', 'NMO', 'PL1', 'SAJ', 'SAL', 'SAR', 'SAT', 'SB1', 'SCC', 'SCF', 'SCE', 'SFD', 'SHH', 'SHS', 'SJN', 'TCC', 'TMA', 'TMB', 'TRU', 'TSM', 'U5Y', 'UU9', 'VA0', 'VF1', 'VF2', 'VF3', 'VF4', 'VF5', 'VF6', 'VF7', 'VF8', 'VF9', 'VFE', 'VNK', 'VSS', 'VV9', 'WAG', 'WAU', 'WAP', 'WBA', 'WBS', 'WBX', 'WDB', 'WDC', 'WDD', 'WMX', 'WEB', 'WF0', 'WJM', 'WJR', 'WKK', 'WMA', 'WME', 'WMW', 'WP0', 'WP1', 'WUA', 'WVG', 'WVW', 'WV1', 'WV2', 'W09', 'W0L', 'W0SV', 'XLR', 'YK1', 'YS2', 'YS3', 'YS4', 'YTN', 'YV1', 'YV2', 'YV3', 'ZA9', 'ZAM', 'ZAR', 'ZCF', 'ZFA', 'ZFF', 'ZGA', 'ZHW', 'ZLA', '1B', '1C', '1F', '1G', '1G1', '1G3', '1G9', '1GC', '1GM', '1HG', '1J', '1L', '1M', '1N', '1VW', '1YV', '2DG', '2F', '2G', '2G1', '2G2', '2HG', '2HH', '2HJ', '2HK', '2HM', '2M', '2T', '3F', '3G', '3HG', '3HM', '3KP', '3N', '3VW', '4F', '4J', '4M', '4S3', '4S4', '4S6', '4T', '4US', '5FN', '5J6', '5L', '5N', '5T', '5U', '5X', '5YJ', '55', '6F', '6G', '6G1', '6G2', '6H', '6MM', '6T1', '7A3', '8AP', '8AF', '8AG', '8AW', '8AJ', '8A1', '8AC', '8BC', '8AD', '8C3', '8AT', '9BD', '9BG', '9BW', '9BF', '93H', '9BR', '936', '935', '93Y', '93X', '9BH', '95P', '94D', '98R', '988', '98M', '9BM', '99A', '99J', '9C2', '9C6', '9CD', '93W', '93Z', '953', '9BS', '9BV', '9UJ', '9UK', '9UW'];
const countries = ['South Africa', 'South Africa', 'South Africa', 'Tunisia', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'Japan', 'South Korea', 'South Korea', 'South Korea', 'South Korea', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China','China','China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'China', 'Myanmar', 'Turkey', 'Turkey', 'Malaysia', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'United Kingdom', 'Switzerland', 'Czech Republic', 'Czech Republic', 'Hungary', 'Hungary', 'Slovakia', 'Romania', 'Austria', 'France', 'France', 'France', 'France', 'France', 'France', 'France', 'France', 'France', 'France', 'France', 'Spain', 'Spain', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Netherlands', 'Finland', 'Sweden', 'Sweden', 'Sweden', 'Sweden', 'Sweden', 'Sweden', 'Sweden', 'Italy', 'Italy', 'Italy', 'Italy', 'Italy', 'Italy', 'Italy', 'Italy', 'Italy', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Mexico', 'Mexico', 'Mexico', 'Mexico', 'Mexico', 'Mexico', 'Mexico', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'United States', 'Australia', 'Australia', 'Australia', 'Australia', 'Australia', 'Australia', 'Australia', 'New Zealand', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Argentina', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil','Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'Uruguay', 'Uruguay', 'Uruguay'];
let i = codes.indexOf(wmi);
if (i < 0) {
i = codes.indexOf(wmi.substring(0, 2));
}
return countries[i];
};
/**
* Get the manufacturer of the vehicle.
*
* @param {string} wmi The World Manufacturer Identifier.
* @return {string} The manufacturer.
*/
const getManufacturer = (wmi) => {
const codes = ['AAV', 'AHT', 'AFA', 'CL9', 'JA', 'JC1', 'JF', 'JHL', 'JHM', 'JMB', 'JM6', 'JN', 'JS', 'JT', 'JY', 'KL', 'KM', 'KN', 'KPT', 'L6T', 'LBE', 'LBV', 'LDC', 'LE4', 'LFM', 'LFP', 'LFV', 'LGB', 'LGJ', 'LGW', 'LGX', 'LH1', 'LHG', 'LJ1', 'LJD', 'LLV', 'LMG', 'LPA', 'LS5', 'LSG', 'LSJ', 'LSV', 'LTV', 'LVG', 'LVH', 'LVR', 'LVS', 'LVV', 'LWV', 'LZW', 'MS0', 'NMT', 'NMO', 'PL1', 'SAJ', 'SAL', 'SAR', 'SAT', 'SB1', 'SCC', 'SCF', 'SCE', 'SFD', 'SHH', 'SHS', 'SJN', 'TCC', 'TMA', 'TMB', 'TRU', 'TSM', 'U5Y', 'UU9', 'VA0', 'VF1', 'VF2', 'VF3', 'VF4', 'VF5', 'VF6', 'VF7', 'VF8', 'VF9', 'VFE', 'VNK', 'VSS', 'VV9', 'WAG', 'WAU', 'WAP', 'WBA', 'WBS', 'WBX', 'WDB', 'WDC', 'WDD', 'WMX', 'WEB', 'WF0', 'WJM', 'WJR', 'WKK', 'WMA', 'WME', 'WMW', 'WP0', 'WP1', 'WUA', 'WVG', 'WVW', 'WV1', 'WV2', 'W09', 'W0L', 'W0SV', 'XLR', 'YK1', 'YS2', 'YS3', 'YS4', 'YTN', 'YV1', 'YV2', 'YV3', 'ZA9', 'ZAM', 'ZAR', 'ZCF', 'ZFA', 'ZFF', 'ZGA', 'ZHW', 'ZLA', '1B', '1C', '1F', '1G', '1G1', '1G3', '1G9', '1GC', '1GM', '1HG', '1J', '1L', '1M', '1N', '1VW', '1YV', '2DG', '2F', '2G', '2G1', '2G2', '2HG', '2HH', '2HJ', '2HK', '2HM', '2M', '2T', '3F', '3G', '3HG', '3HM', '3KP', '3N', '3VW', '4F', '4J', '4M', '4S3', '4S4', '4S6', '4T', '4US', '5FN', '5J6', '5L', '5N', '5T', '5U', '5X', '5YJ', '55', '6F', '6G', '6G1', '6G2', '6H', '6MM', '6T1', '7A3', '8AP', '8AF', '8AG', '8AW', '8AJ', '8A1', '8AC', '8BC', '8AD', '8C3', '8AT', '9BD', '9BG', '9BW', '9BF', '93H', '9BR', '936', '935', '93Y', '93X', '9BH', '95P', '94D', '98R', '988', '98M', '9BM', '99A', '99J', '9C2', '9C6', '9CD', '93W', '93Z', '953', '9BS', '9BV', '9UJ', '9UK', '9UW'];
const manufacturers = ['Volkswagen', 'Toyota', 'Ford', 'Wallyscar', 'Isuzu', 'Fiat Automobiles/Mazda', 'Fuji Heavy Industries', 'Honda', 'Honda', 'Mitsubishi', 'Mazda', 'Nissan', 'Suzuki', 'Toyota', 'Yamaha', 'Daewoo/GM Korea', 'Hyundai', 'Kia', 'SsangYong', 'Geely', 'Beijing Hyundai', 'BMW Brilliance', 'Dongfeng Peugeot-Citroen', 'Beijing Benz', 'FAW Toyota (Sichuan)', 'FAW Car', 'FAW-Volkswagen', 'Dongfeng Nissan', 'Dongfeng Fengshen', 'Great Wall (Havel)', 'BYD Auto', 'FAW Haima', 'Guangzhou Honda', 'JAC', 'Dongfeng Yueda Kia', 'Lifan', 'GAC Trumpchi', 'Changan PSA (DS Automobiles)', 'Changan Suzuki', 'SAIC General Motors', 'SAIC MG', 'SAIC Volkswagen', 'FAW Toyota (Tianjin)', 'GAC Toyota', 'Dongfeng Honda', 'Changan Mazda', 'Changan Ford', 'Chery', 'GAC Fiat', 'SAIC GM Wuling', 'KIA Myanmar', 'Toyota', 'Ford Otosan', 'Proton', 'Jaguar', 'Land Rover', 'Rover', 'Triumph', 'Toyota', 'Lotus Cars', 'Aston Martin Lagonda Limited', 'DeLorean', 'Alexander Dennis', 'Honda', 'Honda', 'Nissan', 'Micro Compact Car AG (SMART 1998-1999)', 'Hyundai', 'Skoda', 'Audi', 'Suzuki', 'Kia', 'Dacia', 'OAF', 'Renault', 'Renault', 'Peugeot', 'Talbot', 'Iveco Unic SA', 'Renault Trucks/Volvo', 'Citroen', 'Matra/Talbot/Simca', 'Bugatti', 'IvecoBus', 'Toyota', 'SEAT', 'Tauro Sport Auto', 'Neoplan', 'Audi', 'Alpina', 'BMW', 'BMW M', 'BMW', 'Mercedes-Benz', 'DaimlerChrysler AG/Daimler AG', 'DaimlerChrysler AG/Daimler AG', 'DaimlerChrysler AG/Daimler AG', 'EvoBus', 'Ford of Europe', 'Iveco', 'Irmscher', 'Karl Kassbohrer Fahrzeugwerke', 'MAN', 'Smart', 'Mini', 'Porsche car', 'Porsche SUV', 'Quattro', 'Volkswagen', 'Volkswagen', 'Volkswagen Commercial Vehicles', 'Volkswagen Commercial Vehicles', 'Ruf Automobile', 'Opel/Vauxhall', 'Opel Special Vehicles', 'DAF Trucks', 'Saab', 'Scania, Sodertalje', 'Saab', 'Scania, Katrineholm', 'Saab NEVS', 'Volvo Cars', 'Volvo Trucks', 'Volvo Buses', 'Bugatti', 'Maserati', 'Alfa Romeo', 'Iveco', 'Fiat Automobiles', 'Ferrari', 'IvecoBus', 'Lamborghini', 'Lancia', 'Dodge', 'Chrysler', 'Ford', 'General Motors', 'Chevrolet', 'Oldsmobile', 'Google', 'Chevrolet', 'Pontiac', 'Honda', 'Jeep', 'Lincoln', 'Mercury', 'Nissan', 'Volkswagen', 'Mazda', 'Ontario Drive & Gear', 'Ford', 'General Motors', 'Chevrolet', 'Pontiac', 'Honda', 'Acura', 'Honda', 'Honda', 'Hyundai', 'Mercury', 'Toyota', 'Ford', 'General Motors', 'Honda', 'Honda', 'Kia', 'Nissan', 'Volkswagen', 'Mazda', 'Mercedes-Benz', 'Mercury', 'Subaru', 'Subaru', 'Honda', 'Toyota', 'BMW', 'Honda', 'Honda', 'Lincoln', 'Hyundai', 'Toyota', 'BMW', 'Hyundai/Kia', 'Tesla', 'Mercedes-Benz', 'Ford', 'General Motors', 'Chevrolet', 'Pontiac', 'Holden', 'Mitsubishi', 'Toyota', 'Honda', 'Fiat', 'Ford', 'General Motors', 'Volkswagen', 'Toyota', 'Renault', 'Mercedes-Benz', 'Citroen', 'Peugeot', 'Honda', 'Iveco', 'Fiat Automoveis', 'General Motors', 'Volkswagen', 'Ford', 'Honda', 'Toyota', 'Peugeot', 'Citroen', 'Renault', 'Souza Ramos - Mitsubishi/Suzuki', 'Hyundai Motor Company/Hyundai', 'CAOA/Hyundai', 'Nissan', 'Chery', 'Jeep', 'BMW', 'Mercedes-Benz', 'Audi', 'JLR Jaguar Land Rover', 'Honda Motorcycles', 'Yamaha', 'Suzuki Motorcycles', 'Fiat Professional', 'Iveco', 'VW Trucks/MAN', 'Scania', 'Volvo Trucks', 'Chery', 'Lifan', 'Kia'];
let i = codes.indexOf(wmi);
if (i < 0) {
i = codes.indexOf(wmi.substring(0, 2));
}
return manufacturers[i];
};
const vin = process.argv[2].toUpperCase();
const valid = validate(vin);
const country = getCountry(vin.substring(0, 3));
console.log(`VIN is valid? ${valid}.`);
if (valid) {
console.log(`Year is ${getYear(vin.charAt(6), vin.charAt(9))}.`);
console.log(`Manufacturer is ${getManufacturer(vin.substring(0, 3))} in ${country}.`);
if (country === 'United States' || country === 'Canada' || country === 'Mexico') {
if (country === 'United States' && vin.charAt(2) === '9') {
console.log(`Sequential number is ${vin.substring(14)}.`);
} else {
console.log(`Sequential number is ${vin.substring(11)}.`);
}
} else {
console.log(`Sequential number is ${vin.substring(12)}.`);
}
}
@jamneck

This comment has been minimized.

Copy link

jamneck commented May 11, 2020

Great work! Where did you find this rule in the getYear function?

if (isNaN(pos7)) {
    i = values.lastIndexOf(pos10);
} else {
    i = values.indexOf(pos10);
}
@kevboutin

This comment has been minimized.

Copy link
Owner Author

kevboutin commented May 11, 2020

Great work! Where did you find this rule in the getYear function?

if (isNaN(pos7)) {
    i = values.lastIndexOf(pos10);
} else {
    i = values.indexOf(pos10);
}

I honestly do not remember where I found that rule but it certainly took some digging.

@EpicVoyage

This comment has been minimized.

Copy link

EpicVoyage commented Jun 13, 2020

Thanks for putting this together. It is really helpful, but it does not cover all use-cases. Here are a few edge cases that I was able to find.

The UK may not use check digits: https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/Check_digit

This may not work for vehicles produced before 1980 because VINs had not been standardized until then. I know, that is 40 years ago: https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/Overview

Commercial vehicles from Daimler-Benz use digit 18 as their check digit. This would mean some VINs longer than 17 digits may be valid, and position 9 is not the only place to look for check digits: https://en.wikibooks.org/wiki/Vehicle_Identification_Numbers_(VIN_codes)/Mercedes-Benz/VIN_Codes

These issues aren't really important for me right now, but they will affect how I use this. Thanks again for the work that you put into it.

@kevboutin

This comment has been minimized.

Copy link
Owner Author

kevboutin commented Jun 13, 2020

@EpicVoyage Thanks for this information and I am glad to hear that this is useful to you. I did not want to get into commercial vehicles as I felt it may be too much of a nuisance with many nuances. That may be wrong given the information in your link.

I had a quick look at the UK comments in that wikibooks link. It did not specify how that position is used if it is not a check digit. I will have to do some more research there.

Regarding vehicles before 1980: This would greatly increase the scope given that specific and custom logic would have to be used based on each make. I certainly did not want to follow that rabbit hole. If someone else has, then I would look to incorporate it.

@EpicVoyage

This comment has been minimized.

Copy link

EpicVoyage commented Jun 13, 2020

That is completely fair. It is possible that I will have to work with commercial vehicles. If that happens, I will share my changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.