How to Decode a VIN in JavaScript

If you're building anything automotive-related in JavaScript, you'll eventually need to decode VINs. Whether you're building an inventory management system, a valuation tool, a compliance checker, or a marketplace, extracting make, model, year, and specifications from a 17-character VIN is foundational functionality.
This guide covers three approaches to VIN decoding in JavaScript, each with different trade-offs. You'll learn when to use each one and see working code examples you can drop into your project today.
What is a VIN and Why Decode It?
A Vehicle Identification Number (VIN) is a 17-character identifier assigned to every vehicle manufactured since 1981. It encodes the manufacturer, vehicle specifications, model year, assembly plant, and production sequence. For a deep dive into VIN structure, see our guide to VIN decoding.
Common use cases for VIN decoding:
- Inventory management: Auto-populate vehicle details when adding listings
- Valuation tools: Get exact specifications for accurate pricing
- Compliance checks: Verify recall status and safety information
- Parts matching: Ensure correct components for specific configurations
- Fleet management: Track vehicle specifications across your fleet
- Mobile apps: Let users scan VINs and get instant vehicle info
Let's look at your options.
Option 1: Offline Decoding with @cardog/corgi
The fastest and most reliable approach is offline decoding. The @cardog/corgi package bundles an optimized version of the NHTSA VPIC database, giving you sub-15ms lookups with no network dependency.
Installation
npm install @cardog/corgiBasic Usage (Node.js)
import { createDecoder } from "@cardog/corgi";
// Initialize the decoder (downloads ~20MB database on first run)
const decoder = await createDecoder();
// Decode a VIN
const result = await decoder.decode("1HGCM82633A123456");
if (result.valid) {
const vehicle = result.components.vehicle;
console.log(vehicle.make); // Honda
console.log(vehicle.model); // Accord
console.log(vehicle.year); // 2003
console.log(vehicle.bodyStyle); // Sedan
console.log(vehicle.fuelType); // Gasoline
}
// Clean up when done
await decoder.close();Browser Usage
For browser environments, use the browser-specific import:
import { createDecoder } from "@cardog/corgi/browser";
const decoder = await createDecoder({
databasePath: "https://corgi.cardog.io/vpic.lite.db.gz",
runtime: "browser",
});
const result = await decoder.decode("5YJ3E1EA5NF123456");
console.log(result.components.vehicle);
// { make: 'Tesla', model: 'Model 3', year: 2022, fuelType: 'Electric', ... }Cloudflare Workers (D1)
For edge deployments, Corgi supports Cloudflare D1:
import { createDecoder, initD1Adapter } from "@cardog/corgi";
export default {
async fetch(request, env) {
initD1Adapter(env.VPIC_DB);
const decoder = await createDecoder({
databasePath: "D1",
runtime: "cloudflare",
});
const url = new URL(request.url);
const vin = url.pathname.split("/").pop();
const result = await decoder.decode(vin);
return Response.json(result);
}
}Handling Errors
The decoder returns structured errors for invalid VINs:
import { createDecoder, ErrorCode } from "@cardog/corgi";
const decoder = await createDecoder();
const result = await decoder.decode("INVALID_VIN_HERE");
if (!result.valid) {
for (const error of result.errors) {
switch (error.code) {
case ErrorCode.INVALID_LENGTH:
console.log("VIN must be exactly 17 characters");
break;
case ErrorCode.INVALID_CHECK_DIGIT:
console.log("VIN failed mathematical validation");
break;
case ErrorCode.WMI_NOT_FOUND:
console.log("Unknown manufacturer code");
break;
}
}
}When to Use Corgi
Best for:
- High-volume decoding (thousands of VINs per day)
- Offline-capable applications
- Low-latency requirements (sub-15ms)
- Privacy-sensitive applications (no external API calls)
- Edge deployments (Cloudflare Workers, Vercel Edge)
Trade-offs:
- Initial ~20MB database download
- Data updates monthly (not real-time)
- Core specs only (no market data, recalls, or enrichment)
Option 2: Online Decoding with the Cardog API
When you need enriched data beyond basic specifications, the Cardog API provides market intelligence, recall status, and valuation data alongside VIN decoding.
Installation
npm install @cardog/apiBasic Usage
import { CardogClient } from "@cardog/api";
const client = new CardogClient({
apiKey: process.env.CARDOG_API_KEY,
});
// Decode with enriched data
const vehicle = await client.vin.decode("1HGCM82633A123456");
console.log(vehicle.make); // Honda
console.log(vehicle.model); // Accord
console.log(vehicle.year); // 2003
console.log(vehicle.trim); // EX
console.log(vehicle.engine); // 2.4L 4-Cylinder
console.log(vehicle.transmission); // AutomaticChecking Recalls
The API integrates recall data from both NHTSA and Transport Canada:
const recalls = await client.recalls.byVin("1HGCM82633A123456");
for (const recall of recalls) {
console.log(recall.campaign); // Recall campaign number
console.log(recall.component); // Affected component
console.log(recall.description); // Issue description
console.log(recall.remedy); // Required fix
}React Query Integration
The client includes React Query hooks for seamless frontend integration:
import { useVinDecode } from "@cardog/api/react";
function VehicleCard({ vin }: { vin: string }) {
const { data, isLoading, error } = useVinDecode(vin);
if (isLoading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return (
<div>
<h2>{data.year} {data.make} {data.model}</h2>
<p>{data.engine} - {data.transmission}</p>
</div>
);
}When to Use the Cardog API
Best for:
- Applications needing market data and valuations
- Recall checking and compliance
- Always-current data requirements
- Lower-volume applications (free tier available)
- Quick prototyping without database setup
Trade-offs:
- Network latency (~100-300ms per request)
- API key required
- Rate limits on free tier
Option 3: Raw NHTSA API
The NHTSA provides a free public API for VIN decoding. It's the authoritative data source, but the interface is dated and response times are slow.
Basic Usage
async function decodeVIN(vin: string) {
const url = `https://vpic.nhtsa.dot.gov/api/vehicles/decodevin/${vin}?format=json`;
const response = await fetch(url);
const data = await response.json();
// The response is an array of variable/value pairs
const results = data.Results.reduce((acc, item) => {
if (item.Value && item.Value !== "Not Applicable") {
acc[item.Variable] = item.Value;
}
return acc;
}, {});
return {
make: results["Make"],
model: results["Model"],
year: parseInt(results["Model Year"]),
bodyClass: results["Body Class"],
engineCylinders: results["Engine Number of Cylinders"],
engineDisplacement: results["Displacement (L)"],
fuelType: results["Fuel Type - Primary"],
driveType: results["Drive Type"],
transmissionStyle: results["Transmission Style"],
};
}
// Usage
const vehicle = await decodeVIN("1HGCM82633A123456");
console.log(vehicle);The Problems with Raw NHTSA
The code above looks simple, but real-world usage reveals significant issues:
1. Slow response times: Average 2-3 seconds per request. For batch operations, this becomes painful.
2. Inconsistent field names: The API returns 140+ fields with verbose names like "Engine Number of Cylinders" that you'll need to map to your data model.
3. Unpredictable "Not Applicable" values: Many fields return "Not Applicable" instead of null, requiring filtering logic.
4. No TypeScript support: The response structure requires manual type definitions.
5. Rate limiting: Aggressive rate limits can block your application during high-traffic periods.
6. No caching: Every request hits NHTSA servers, even for the same VIN.
When to Use Raw NHTSA
Best for:
- One-off scripts or debugging
- When you can't install dependencies
- Verifying data from other sources
- Learning how VIN decoding works
Avoid for:
- Production applications
- High-volume decoding
- User-facing features (too slow)
Comparison: Which Should You Use?
| Feature | @cardog/corgi | Cardog API | Raw NHTSA |
|---|---|---|---|
| Response time | 8-15ms | 100-300ms | 2-3 seconds |
| Network required | No (after setup) | Yes | Yes |
| Database size | ~20MB | 0 (cloud) | 0 (cloud) |
| Rate limits | None | Tiered | Aggressive |
| Data freshness | Monthly | Real-time | Real-time |
| Market data | No | Yes | No |
| Recall data | No | Yes | Separate API |
| TypeScript | Full support | Full support | Manual types |
| Cost | Free (open source) | Free tier + paid | Free |
| Best for | High volume, offline | Enriched data, recalls | Scripts, debugging |
Practical Example: Building a VIN Scanner
Here's a complete example combining Corgi for fast offline decoding with the Cardog API for enrichment when online:
import { createDecoder } from "@cardog/corgi";
import { CardogClient } from "@cardog/api";
class VINScanner {
private decoder: Awaited<ReturnType<typeof createDecoder>> | null = null;
private client: CardogClient;
constructor(apiKey?: string) {
if (apiKey) {
this.client = new CardogClient({ apiKey });
}
}
async initialize() {
this.decoder = await createDecoder();
}
async decode(vin: string, options?: { enriched?: boolean }) {
// Always start with fast offline decode
const basic = await this.decoder?.decode(vin);
if (!basic?.valid) {
return { error: "Invalid VIN", details: basic?.errors };
}
// Return basic data if enrichment not requested or no API key
if (!options?.enriched || !this.client) {
return {
...basic.components.vehicle,
source: "offline",
};
}
// Fetch enriched data from API
try {
const enriched = await this.client.vin.decode(vin);
const recalls = await this.client.recalls.byVin(vin);
return {
...enriched,
recalls: recalls.length,
hasOpenRecalls: recalls.some(r => !r.completed),
source: "enriched",
};
} catch (error) {
// Fall back to offline data if API fails
return {
...basic.components.vehicle,
source: "offline-fallback",
};
}
}
async close() {
await this.decoder?.close();
}
}
// Usage
const scanner = new VINScanner(process.env.CARDOG_API_KEY);
await scanner.initialize();
// Fast offline decode
const basic = await scanner.decode("1HGCM82633A123456");
// Enriched decode with market data and recalls
const enriched = await scanner.decode("1HGCM82633A123456", {
enriched: true
});
await scanner.close();Performance Benchmarks
We tested all three approaches decoding 1,000 VINs:
| Method | Total Time | Avg per VIN | Memory |
|---|---|---|---|
| Corgi (Node.js) | 8.2s | 8.2ms | 45MB |
| Corgi (Browser) | 12.1s | 12.1ms | 38MB |
| Cardog API (parallel) | 45s | 45ms | 12MB |
| NHTSA API (parallel) | 890s | 890ms | 8MB |
Corgi is roughly 100x faster than the raw NHTSA API for batch operations. For user-facing features where every millisecond of latency matters, the difference is even more significant.
Getting Started
For most JavaScript projects, we recommend starting with Corgi:
npm install @cardog/corgiThe package is open source, requires no API key, and works offline after the initial database download. When you need enriched data like market valuations or recall status, add the Cardog API for those specific features.
For a deeper understanding of how VIN decoding works under the hood, read our technical deep dive on building a VIN decoder or learn about the NHTSA database optimizations that make Corgi fast.