Skip to content

Instantly share code, notes, and snippets.

@kevboutin
Last active April 5, 2024 16:26
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save kevboutin/3ac029e336fc7cafd20c05adda42ffa5 to your computer and use it in GitHub Desktop.
Save kevboutin/3ac029e336fc7cafd20c05adda42ffa5 to your computer and use it in GitHub Desktop.
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)}.`);
}
}
@kevboutin
Copy link
Author

@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
Copy link

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

@zielot
Copy link

zielot commented Jan 24, 2023

@kevboutin - very nice work; I appreciate you sharing it.

Unfortunately, it didn't work with my use case - e.g. vehicle descriptor (from https://vpic.nhtsa.dot.gov/decoder/Decoder) 4V4NC9EHON*. I have complete VINs but they are for commercial vehicles. Since we plan to use an external service to decode the VINs this was a nice-to-have.

@zielot
Copy link

zielot commented Jan 24, 2023

Unfortunately, it didn't work with my use case - e.g. vehicle descriptor (from https://vpic.nhtsa.dot.gov/decoder/Decoder) 4V4NC9EHON*. I have complete VINs but they are for commercial vehicles.

I made a couple of changes to your code and it is now working:

  • I incorporated the countries & manufacturers data from Frankely's [VIN decoder](https://github.com/frankely/vin-decoder/] along with his lookup functions.
  • I modified lines 124 to use only the 3-character string: const country = getCountry(vin.substring(0, 2)); This hint was also from Frankely's decoder.

Regards,
Justin

@kevboutin
Copy link
Author

Unfortunately, it didn't work with my use case - e.g. vehicle descriptor (from https://vpic.nhtsa.dot.gov/decoder/Decoder) 4V4NC9EHON*. I have complete VINs but they are for commercial vehicles.

I made a couple of changes to your code and it is now working:

  • I incorporated the countries & manufacturers data from Frankely's [VIN decoder]([https://github.com/frankely/vin-decoder/]](https://github.com/frankely/vin-decoder/%5D) along with his lookup functions.
  • I modified lines 124 to use only the 2-character string: const country = getCountry(vin.substring(0, 2)); This hint was also from Frankely's decoder.

Regards, Justin

Why would you change line 124? The country lookup can be 2-3 characters, which my getCountry function accounts for. It looks for the three characters first and, if not found, it will use the first two characters. Even Frankely's decoder has 3-character country codes in his lookup.

@zielot
Copy link

zielot commented Jan 29, 2023

@kevboutin - sorry; off-by-1 error in my comment. The country code lookup function is exactly as I wrote it const country = getCountry(vin.substring(0, 2)); but I said 2-characters instead of including the 0-based index character in my comment.
The two big changes were to use Frankely's manufacturer and country datasets and to use his lookup functions to determine the country and manufacturer. His manufacturer DB seemed to be more up-to-date - though I have no idea how anyone will update the data from the US DOT site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment