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

postal-code-formats

v0.1.0

Published

Postal code formats, validation regexes and 9,000 real sample codes for 31 countries. Every regex verified against the samples in CI. Zero dependencies.

Readme

postal-code-formats

Postal code formats, validation regexes and 9,000 real sample codes for 31 countries. Every regex is checked against every sample code in CI. Zero dependencies, no network calls.

test npm license


Why another postal code list

Most of the postal code regexes circulating today trace back to one of two places: a Wikipedia table scraped years ago, or Google's libaddressinput metadata, which is still published but no longer maintained as a standalone dataset. Both are plausible. Neither comes with anything that would tell you if a pattern had quietly gone wrong.

This dataset takes one position: a regex that has never been run against a real postal code is a guess. So every pattern here is tested against real codes for that country — 9,000 of them — and the test runs in CI on every push. If a pattern and the reality disagree, the build goes red rather than the data going quietly stale.

It also answers two questions the usual tables skip:

  • Which countries have no postal code at all? Hong Kong is in this dataset with has_postal_code: false, because a form that requires a postal code cannot be completed honestly there. Treating "no code" as "missing data" is a bug, not an edge case.
  • Is the sample a whole code or half of one? Canada, the UK, Ireland and the Netherlands publish their codes in two halves, and open data usually carries only the geographic half. Entries say which, in sample_kind, instead of pretending a Forward Sortation Area is a full postal code.

Install

npm install postal-code-formats

Or take the data directly — data/postal-formats.json, data/postal-formats.csv, and one file of real codes per country under data/samples/. The data is plain and has no runtime; the npm package is a convenience, not a dependency you are stuck with.

Use

import { validate, mask, localName, get } from "postal-code-formats";

validate("1012 AB", "NL");   // true
validate("1012ab", "NL");    // true  — separator and case are normalized
validate("7030", "US");      // false — the leading-zero trap: 07030 is not 7030
validate("D4A 1B1", "CA");   // false — Canada Post never issues D, F, I, O, Q or U

mask("DE");                  // "NNNNN"        — N is a digit, A is a letter
localName("BR");             // "CEP"          — label the field what the user calls it
localName("IE");             // "Eircode"

get("HK").has_postal_code;   // false — Hong Kong has no postal codes
validate("", "HK");          // true  — so empty is the correct value

validate("12345", "ZZ");     // undefined — NOT false. See below.

undefined is not false

An unknown country returns undefined, deliberately. The most common way a validator hurts a real user is by rejecting a correct address for a country the developer never listed — so this library refuses to guess. Branch on it:

const ok = validate(code, country);
if (ok === false) return "That does not look like a valid postal code.";
// undefined → we have nothing to say about this country. Accept the input.

What "valid" means here

Structure, and only structure. These patterns answer "could the postal service read this?" — not "does this place exist?". A code can be perfectly well-formed and completely unassigned. Nothing here is a delivery-point lookup, and none of it should gate anything that matters on its own.

Coverage

| | Country | Local name | Format | Example | Regex | Real codes checked | |---|---|---|---|---|---|---:| | 🇦🇺 | Australia (AU) | Postcode | NNNN | 4110 | ^\d{4}$ | 127 | | 🇦🇹 | Austria (AT) | Postleitzahl (PLZ) | NNNN | 6029 | ^\d{4}$ | 150 | | 🇧🇪 | Belgium (BE) | Postcode / Code postal | NNNN | 6000 | ^\d{4}$ | 147 | | 🇧🇷 | Brazil (BR) | CEP | NNNNN-NNN | 33400-000 | ^\d{5}-?\d{3}$ | 50 | | 🇨🇦 | Canada (CA) | Postal code | ANA NAN | B2V 1B1 | ^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z] ?\d[ABCEGHJ-NPRSTV-Z]\d$ | 149 (prefix) | | 🇩🇰 | Denmark (DK) | Postnummer | NNNN | 5290 | ^\d{4}$ | 90 | | 🇫🇮 | Finland (FI) | Postinumero | NNNNN | 45150 | ^\d{5}$ | 148 | | 🇫🇷 | France (FR) | Code postal | NNNNN | 51430 | ^\d{5}$ | 113 | | 🇩🇪 | Germany (DE) | Postleitzahl (PLZ) | NNNNN | 42275 | ^\d{5}$ | 150 | | 🇭🇰 | Hong Kong (HK) | — | no postal code system | — | — | — | | 🇭🇺 | Hungary (HU) | Irányítószám | NNNN | 6727 | ^\d{4}$ | 126 | | 🇮🇳 | India (IN) | PIN code | NNNNNN | 400018 | ^\d{6}$ | 148 | | 🇮🇩 | Indonesia (ID) | Kode pos | NNNNN | 30253 | ^\d{5}$ | 145 | | 🇮🇪 | Ireland (IE) | Eircode | ANN AAAA | D01 F4E2 | ^[AC-FHKNPRTV-Y]\d[0-9W] ?[0-9AC-FHKNPRTV-Y]{4}$ | 12 (prefix) | | 🇮🇹 | Italy (IT) | CAP | NNNNN | 37130 | ^\d{5}$ | 150 | | 🇯🇵 | Japan (JP) | 郵便番号 (yūbin bangō) | NNN-NNNN | 100-0001 | ^\d{3}-?\d{4}$ | — (none) | | 🇲🇽 | Mexico (MX) | Código postal | NNNNN | 52985 | ^\d{5}$ | 134 | | 🇳🇱 | Netherlands (NL) | Postcode | NNNN AA | 1011 AB | ^\d{4} ?[A-Z]{2}$ | 150 (prefix) | | 🇳🇴 | Norway (NO) | Postnummer | NNNN | 4634 | ^\d{4}$ | 150 | | 🇵🇱 | Poland (PL) | Kod pocztowy | NN-NNN | 44-114 | ^\d{2}-?\d{3}$ | 150 | | 🇵🇹 | Portugal (PT) | Código postal | NNNN-NNN | 3810-066 | ^\d{4}-?\d{3}$ | 150 | | 🇷🇴 | Romania (RO) | Cod poștal | NNNNNN | 410320 | ^\d{6}$ | 150 | | 🇷🇺 | Russia (RU) | Почтовый индекс | NNNNNN | 425411 | ^\d{6}$ | 145 | | 🇰🇷 | South Korea (KR) | 우편번호 | NNNNN | 37727 | ^\d{5}$ | 150 | | 🇪🇸 | Spain (ES) | Código postal | NNNNN | 33205 | ^\d{5}$ | 150 | | 🇸🇪 | Sweden (SE) | Postnummer | NNN NN | 582 56 | ^\d{3} ?\d{2}$ | 150 | | 🇨🇭 | Switzerland (CH) | Postleitzahl / NPA | NNNN | 4002 | ^\d{4}$ | 130 | | 🇹🇷 | Turkey (TR) | Posta kodu | NNNNN | 27410 | ^\d{5}$ | 120 | | 🇺🇦 | Ukraine (UA) | Поштовий індекс | NNNNN | 49112 | ^\d{5}$ | 143 | | 🇬🇧 | United Kingdom (GB) | Postcode | AN NAA / ANN NAA / AAN NAA / AANN NAA / ANA NAA / AANA NAA | B1 1BB | ^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$ | 131 (prefix) | | 🇺🇸 | United States (US) | ZIP Code | NNNNN or NNNNN-NNNN | 55810 | ^\d{5}(-\d{4})?$ | 5292 |

sample_kind is full unless marked. (prefix) means the sample file holds the published geographic half of the code — a Canadian FSA, a UK outward code, an Eircode routing key, a Dutch four-digit block — and the example column shows one of those prefixes completed into a full code. (none) means no sample file: Japan's open postal data is romanised, and this project's Japanese addresses are native script, so the two were never joined honestly enough to publish.

The data

Each entry:

{
  "iso2": "IE",
  "country": "Ireland",
  "local_name": "Eircode",
  "has_postal_code": true,
  "mask": "ANN AAAA",
  "regex": "^[AC-FHKNPRTV-Y]\\d[0-9W] ?[0-9AC-FHKNPRTV-Y]{4}$",
  "example": "D01 F4E2",
  "samples": 12,
  "sample_kind": "prefix",
  "observed_masks": ["ANN"],
  "note": "Introduced in 2015. The three-character routing key is geographic; the four-character identifier resolves to ONE delivery point, so two neighbouring houses have different Eircodes. The samples are routing keys."
}

observed_masks is the shape of the sample codes as measured, not as asserted — it is there so you can see the data disagreeing with the mask field if it ever does.

Testing your own forms against this

The sample files are the useful part for test suites: real codes, one per line, data/samples/<ISO2>.txt. They make good fixtures precisely because they are not uniform — Sweden's carry a space, Poland's a hyphen, Portugal's four digits then three.

import { readFileSync } from "node:fs";
const codes = readFileSync("node_modules/postal-code-formats/data/samples/SE.txt", "utf8")
  .split("\n").filter(Boolean);
// → ["100 04", "103 16", "105 47", ...]

Provenance and limits

The sample codes come from GeoNames postal data (CC BY 4.0), joined to its population-ranked city list, as used in production by Fakenamely. Patterns and the per-country notes were written against the national postal authorities' own published standards (USPS Publication 28, Canada Post, Royal Mail, An Post, PostNL and others).

Honest limits, so you can decide whether this is enough for your case:

  • 31 countries, not ~250. These are the ones with sample data good enough to test against. A short list that is verified beats a long one that is not — but if you need full ISO coverage, this is not that.
  • A snapshot, not a feed. Postal systems change: Germany renumbered in 1993, Japan in 1998, Korea in 2015, Ireland went from no codes at all to per-door codes. This data is dated in data/postal-formats.json and will drift.
  • Samples are a slice. ~150 codes per country (5,292 for the US), not a directory. They exist to test the patterns, not to enumerate a country.
  • Not affiliated with any postal authority.

Contributing

Corrections are very welcome, especially from people who live with a postal system day to day. A pull request that changes a pattern should come with sample codes it accepts that the old one rejected (or vice versa) — npm test will hold you to it. Adding a country means adding data/samples/<ISO2>.txt and one entry in data/postal-formats.json; the test suite checks the rest.

License

MIT for the code and the compiled dataset. The underlying GeoNames postal data is CC BY 4.0 — attribution to GeoNames carries over to anything derived from data/samples/.