npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chilean-plate-validator

v2.0.0

Published

Lightweight, zero-dependency validator for Chilean license plates (PPU)

Readme

chilean-plate-validator

npm version install size npm downloads License: MIT Quake-free days Faltan pa'l 18

🇨🇱 Description

A lightweight, zero-dependency utility for Chilean license plate (PPU) validation and identification, built for LPR/OCR pipelines.

Features

  • Validated against the official series table. Old-format plates are checked against the 582 two-letter series published by the Registro Civil, not just a LL·nnnn shape — so the 94 combinations that were never issued are rejected.
  • OCR-tolerant (fuzzy mode). Corrects visually confusable characters (8↔B, 5↔S, 6↔G, 7↔T, 2↔Z, 0↔O/D, 1↔I) and returns every valid plate the input could have been.
  • Regional detection (optional). A separate entry point tells you whether a string looks like a plate from Chile or a neighbouring country.
  • Dual build. ESM (import) and CommonJS (require), with types for both.
  • Zero runtime dependencies.

Supported formats

Format notation: L = letter, 1 = digit.

| Type | Format | Example | Description | |----------------|----------|----------|------------------------------------------------| | New Vehicle | LLLL11 | BCDF12 | Vehicles, 4+ wheels (LLLL·nn) | | New Motorcycle | LLL11 | BJH61 | Motorcycles, since 2014 (LLL·nn) | | Old Vehicle | LL1111 | AR1240 | Pre-2007 standard (LL·nnnn), series-checked | | Special | LL1111 | CD1202 | Diplomatic, consular, police, etc. | | Police | L1111 | Z1234 | Carabineros de Chile | | Ambulance | A1111 | A6709 | Ambulance plates |

Letter sets differ by format. Vehicle series omit vowels and M, N, Q; motorcycle series omit only vowels, so plates like MMN01 or ZBQ31 are valid motorcycles. The old format has no letter rule — it has a published table of 582 issued series, which this library checks against.

Trailers (remolques) share the motorcycle format exactly and cannot be told apart from the plate string alone; distinguishing them needs registration data this library does not have. plateType() reports them as new_motorcycle_plate.

Heads up — 2025 format change. A 2025 decree (D.O. 16-01-2025) moves vehicles to 5 letters + 1 digit and motorcycles to 4 letters + 1 digit, rolling out gradually (~2027 for motos, ~2029 for cars), plus a green plate for EVs/hybrids. These new formats are not yet validated by this library and will be added as they enter circulation.

🚀 Installation

npm install chilean-plate-validator

Requires Node 18 or newer.

🛠 Usage

Using functions

import { normalize, plateValid, plateType, specialPlateInfo } from 'chilean-plate-validator'

// Strict validation — input must be pre-normalized
plateValid('BCDF12') // true
plateValid('BC-DF12') // false

// Normalize first if the input may contain separators
plateValid(normalize('BC-DF12')) // true

// Get plate category
plateType('BCDF12') // 'new_vehicle_plate'
plateType('AA1234') // 'old_plate'
plateType('Z1234')  // 'police'
plateType('GG1234') // 'invalid' — 'GG' was never issued as a series

// Get special plate metadata (old format only)
specialPlateInfo('CD1234') // { prefix: 'CD', label: 'Cuerpo diplomático', authority: 'governmental', ... }
specialPlateInfo('AA1234') // null

Non-string input is treated as "not a plate" rather than raising, since a dropped OCR read arrives as null more often than as '':

normalize(null)       // ''
plateValid(undefined) // false
plateType(null)       // 'invalid'

The old-format series table

The old LL·nnnn format is the only Chilean format whose valid letters are a published list rather than a rule. The Registro Civil allocated 582 of the 676 possible two-letter combinations, and this library ships that table:

import { isOldSeries, OLD_SERIES, plateType } from 'chilean-plate-validator'

OLD_SERIES.size      // 582
isOldSeries('AR')    // true  — an issued series
isOldSeries('GG')    // false — never allocated
isOldSeries('PR')    // true  — reserved prefix (patente provisoria), outside the series table

plateType('AR1240')  // 'old_plate'
plateType('GG1234')  // 'invalid'

This also sharpens regional detection: LL·nnnn is shared with Perú, so a prefix that Chile never issued now resolves to Perú alone.

Fuzzy validation (OCR input)

When the plate string comes from an image recognition pipeline, fuzzy mode corrects visually confusable characters before validating:

import { plateValid, fuzzyPlateValid, fuzzyCorrect } from 'chilean-plate-validator'

// Strict mode — fails on OCR errors
plateValid('8CDF12') // false

// Option 1 — config flag
plateValid('8CDF12', { fuzzy: true }) // true — '8' corrected to 'B'

// Option 2 — shorthand
fuzzyPlateValid('8CDF12') // true

// Get the corrected plate(s)
fuzzyCorrect('8CDF12') // ['BCDF12']
fuzzyCorrect('6CDF12') // ['GCDF12'] — 6 read as G
fuzzyCorrect('BCDF1Z') // ['BCDF12'] — Z read as 2
fuzzyCorrect('BCDF12') // ['BCDF12'] — already valid
fuzzyCorrect('AEIOU9') // []

fuzzyCorrect returns every valid plate the input could have been, so callers must handle more than one result. A character can be confusable with several others, and a 6-character string can satisfy both the old and the new format:

fuzzyCorrect('BCDGT2') // ['BCDG72', 'BC0672'] — new-format and old-format readings

Which corrections pay off depends on the format's letter set. 0→O and 1→I can only ever produce a valid old-format plate, because the new formats exclude both letters — 0 is corrected to D for new-format candidates.

Using the PlateType object

Avoid hardcoding type strings — use PlateType for comparisons:

import { plateType, PlateType } from 'chilean-plate-validator'

if (plateType('BCDF12') === PlateType.NewVehicle) {
  // ...
}

Using the CLPlate class

The class normalizes input automatically and computes its results once:

import { CLPlate } from 'chilean-plate-validator'

const plate = new CLPlate('bc-df12')

plate.clean       // 'BCDF12'
plate.isValid     // true
plate.type        // 'new_vehicle_plate'
plate.formatted   // 'BCDF-12'
plate.specialInfo // null

const special = new CLPlate('CD-1234')

special.isValid     // true
special.type        // 'old_plate'
special.formatted   // 'CD-1234'
special.specialInfo // { prefix: 'CD', label: 'Cuerpo diplomático', authority: 'governmental', ... }

For OCR input, use the fuzzy getters on the instance or the static helpers:

// Instance — fuzzy validity is a superset of strict validity
const plate = new CLPlate('8CDF12')

plate.isValid          // false — strict validation fails
plate.isFuzzyValid     // true  — recoverable via OCR correction
plate.fuzzyCorrections // ['BCDF12']

// Already-valid plates are also fuzzy-valid and correct to themselves
new CLPlate('BCDF12').isFuzzyValid     // true
new CLPlate('BCDF12').fuzzyCorrections // ['BCDF12']

// Unrecoverable input
new CLPlate('AEIOU9').isFuzzyValid     // false
new CLPlate('AEIOU9').fuzzyCorrections // []
// Static helpers — no instantiation needed
CLPlate.normalize('BC-DF 12')   // 'BCDF12'
CLPlate.fuzzyCorrect('8CDF12')  // ['BCDF12']
CLPlate.isValidFuzzy('8CDF12')  // true
// toString() — coercion-friendly
String(new CLPlate('BCDF12'))     // 'BCDF-12'
`Plate: ${new CLPlate('AA1234')}` // 'Plate: AA-1234'

🌎 Regional detection (optional)

A separate entry point answers a different question: is this string a plate from Chile or a neighbouring country? Covers Chile, Argentina, Bolivia, Brasil, Paraguay, Perú and Uruguay.

import { detectOrigin, normalize } from 'chilean-plate-validator/regional';

detectOrigin('AB123CD')            // ['AR'] — Mercosur car, unambiguous
detectOrigin('ABC1D23')            // ['BR'] — Mercosur
detectOrigin('ABCD123')            // ['PY'] — Mercosur car
detectOrigin('1234ABC')            // ['BO']
detectOrigin('A1B234')             // ['PE'] — taxi / urban bus
detectOrigin('BCDF12')             // ['CL']
detectOrigin('AB1234')             // ['CL', 'PE'] — shared shape, Chile first
detectOrigin('GG1234')             // ['PE'] — 'GG' is not a Chilean series
detectOrigin('ABC1234')            // ['BR', 'UY']
detectOrigin('HOLA')               // []  — not a plate in any supported country
detectOrigin(normalize('ab-123-cd')) // ['AR']

An empty array means the string matches no known format. Several codes mean the shape is used by more than one country and cannot be resolved from the string alone; results are ranked with Chile first, so taking [0] biases toward the local reading.

detectOriginDetailed returns the matching table entries instead of bare codes, so you can show why a string was attributed:

import { detectOriginDetailed } from 'chilean-plate-validator/regional';

detectOriginDetailed('ABC1234').map((m) => `${m.country}: ${m.label}`)
// ['BR: Patente brasileña de formato antiguo (1990-2018)',
//  'UY: Patente uruguaya de automóvil o motocicleta (desde 2001)']

This detects shapes, not valid plates. A match means the string looks like a plate from that country — no foreign registry, series or check digit is verified. Only Chilean plates are truly validated. This limit is deliberate: tracking seven legislations is unmaintainable, while shapes change roughly once a decade.

Every Mercosur-era format is unambiguous; the overlaps are all between older national formats.

| Shape | Countries | |---|---| | LLLLDD, LLLDD, LDDDD | 🇨🇱 Chile only | | LLDDDLL, LDDDLLL | 🇦🇷 Argentina (Mercosur, car / motorcycle) | | LLLDLDD | 🇧🇷 Brasil (Mercosur) | | LLLLDDD, DDDLLLL | 🇵🇾 Paraguay (Mercosur, car / motorcycle) | | DDDDLLL | 🇧🇴 Bolivia | | LDLDDD | 🇵🇪 Perú (taxi / bus) | | LLDDDD | 🇨🇱 Chile (old, series-checked) · 🇵🇪 Perú | | LLLDDDD | 🇧🇷 Brasil (1990–2018) · 🇺🇾 Uruguay | | LLLDDD | 🇦🇷 Argentina (1995–2016) · 🇵🇪 Perú · 🇵🇾 Paraguay (2000–2019) | | DDDLLL | 🇦🇷 Argentina (motorcycle) · 🇧🇴 Bolivia (old) · 🇵🇾 Paraguay (motorcycle) |

Uruguay's LLLDDDD is a strict subset of Brazil's: Uruguay uses A–S in the first position for its 19 departments, and Brazil's range covers that band. So a Uruguayan-looking plate always reports ['BR', 'UY'], and only a first letter past S resolves to Brazil alone.

OCR note: don't pass fuzzyCorrect() output to detectOrigin(). Those corrections assume Chilean character positions and will push foreign plates toward Chilean shapes. Detect on the raw normalized read instead — a clean foreign match is a good reason to skip fuzzy correction altogether.

🧪 Development

nvm use          # Node version from .nvmrc
npm ci
npm run verify   # lint + typecheck + tests + build + smoke test

| Script | What it does | |---|---| | npm test | Jest suite, including every documented example | | npm run test:coverage | Same, with a coverage report | | npm run lint | ESLint over src and tests | | npm run typecheck | tsc over src and tests | | npm run build | Emits dist/ via tsdown (ESM + CJS + types) | | npm run smoke | Resolves and exercises the built package, as a consumer would | | npm run verify | All of the above, in order. Also runs on prepublishOnly |

Every @example in the JSDoc and every fenced block in this README is executed by tests/examples.test.ts and compared against real output, so a documented example cannot drift from behaviour.

📚 Legal basis & documentation

This module follows the official specifications from the Servicio de Registro Civil e Identificación de Chile.

📄 License

This project is licensed under the MIT License.

👤 Author

Gabriel Galilea