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-checker

v2.1.0

Published

A package for validating postal codes from various countries.

Readme

📮 postal-code-checker

Validate postal codes and ZIP codes for 249 countries. TypeScript-first. Zero dependencies.

npm version npm downloads bundle size types zero dependencies license

Prefer to try before installing?

Browse the full 249-country dataset, run single and batch validations, and edit live code right in the browser — no install required.

Powered by Google's libaddressinput — the same dataset behind Chromium, Android, and Google Pay address forms.


⚡ Quick start

npm install postal-code-checker
import { validatePostalCode, validatePostalCodes } from "postal-code-checker";

validatePostalCode("US", "90210");          // → true
validatePostalCode("CA", "k1a 0t6");         // → true  (case + whitespace tolerant)
validatePostalCode("GBR", "SW1A 1AA");       // → true  (alpha-3 works too)

validatePostalCodes("US", ["12345", "oops"]); // → [true, false]

Works in React, Next.js, Vue, Svelte, Angular, Node.js, Deno, Bun, and plain browser JS — no framework assumptions.


💡 Why postal-code-checker?

  • 🌍 249 countries — the full ISO 3166-1 list, sourced live from Google's libaddressinput. Regenerated per release so the regexes never drift from upstream.
  • 📋 Batch APIvalidatePostalCodes(country, codes[]) returns an index-aligned boolean[]. Designed for CSV imports, bulk address uploads, and form arrays.
  • 🔤 Alpha-2 and alpha-3 both work — call with "US" or "USA", "GB" or "GBR". No branching at your call sites.
  • Forgiving input — case-insensitive and whitespace-tolerant. "k1a 0t6", " K1A 0T6 ", and "K1A0T6" all validate equivalently where the country allows it.
  • 🪶 Zero runtime dependencies — one install, nothing else pulled in. Keeps node_modules small and supply-chain surface minimal.
  • 🧷 TypeScript-first.d.ts bundled. No @types/* package to install.
  • 📦 Dual ESM + CommonJS — modern import and legacy require() both work out of the box.
  • 🌐 Framework-agnostic — React, Next.js, Vue, Svelte, Angular, Node.js, Deno, Bun, plain browser JS.

🧩 Use cases

import { validatePostalCode } from "postal-code-checker";

function onZipBlur(country: string, zip: string) {
  if (!validatePostalCode(country, zip)) {
    return "Please check your postal code.";
  }
  // safe to POST to /address-lookup
}
import { useForm } from "react-hook-form";
import { validatePostalCode } from "postal-code-checker";

const { register } = useForm<{ country: string; postal: string }>();

<input
  {...register("postal", {
    validate: (value, { country }) =>
      validatePostalCode(country, value) || "Invalid postal code",
  })}
/>
import { validatePostalCodes } from "postal-code-checker";

const rows = await parseCsv("./addresses.csv");
const postals = rows.map((r) => r.postal);
const results = validatePostalCodes("US", postals);

const bad = rows.filter((_, i) => !results[i]);
console.log(`${bad.length} rows need review`);

Batch calls avoid N regex compiles — one country lookup, N matches. Index-aligned output so you can zip results back to input rows.

"use server";
import { validatePostalCode } from "postal-code-checker";

export async function submitAddress(data: FormData) {
  const country = String(data.get("country"));
  const postal = String(data.get("postal"));
  if (!validatePostalCode(country, postal)) {
    return { error: "Invalid postal code for that country" };
  }
  // persist…
}
import { getCountryByCode } from "postal-code-checker";

const country = getCountryByCode("DE");
// country.examplePostalCodes → ["10115"]  ← use as placeholder
// country.postalCodePatterns → anchored regex strings, safe to render

📚 API Reference

validatePostalCode(countryCode, postalCode): boolean

Validates a single postal code against a country. Accepts both alpha-2 and alpha-3 ISO 3166-1 codes. Input is trimmed and uppercased before matching, so "k1a 0t6", " K1A 0T6 ", and "K1A0T6" all validate equivalently where the country allows it.

validatePostalCodes(countryCode, postalCodes): boolean[]

Validates an array of postal codes against one country and returns an index-aligned array of results. Unique to this package — use it for CSV imports, bulk address uploads, and form arrays.

format(countryCode, postalCode): string | nullnew in 2.1

Returns the canonical form of a valid postal code (trimmed, uppercased), ready to store in a database. Returns null when the input doesn't match the country's pattern or the country has no postal code system. Accepts alpha-2 and alpha-3.

format("CA", "k1a 0t6"); // → "K1A 0T6"
format("US", "  12345 "); // → "12345"
format("US", "ABC12");    // → null

guessCountries(postalCode): CountryOption[]new in 2.1

Given a postal code with no country context, returns every country whose pattern accepts the input. Sorted alphabetically by countryName — ready to render as a picker. Countries with no postal code system are excluded.

guessCountries("K1A 0T6");
// → [{ countryName: "Canada", countryCode: "CA" }]

guessCountries("12345");
// → [{ countryName: "Algeria", ... }, { countryName: "Germany", ... }, ...]

getCountryByCode(countryCode): Country | null

Returns the full country record (patterns, example codes, name, 2-letter code) or null if unknown. Accepts alpha-2 or alpha-3. postalCodePatterns is a string[] — most countries have one entry, some (e.g. GB with BFPO) have several.

getAllCountries(): CountryOption[]

Returns { countryName, countryCode }[] for every supported country, sorted alphabetically.

configure(config): voidnew in 2.1

Registers a user-supplied country dataset in one call, typically at app boot. Each entry either replaces a built-in country (when the alpha-2 key matches) or adds a brand-new country. Every utility in the package honors the override immediately — no call-site changes needed.

import { configure, validatePostalCode } from "postal-code-checker";

configure({
  countries: {
    // Kosovo — not in ISO 3166-1, so not in the bundled dataset
    XK: {
      patterns: ["/^(?:[1-7]\\d{4})$/"],
      example: ["10000", "20000"],
      country: "Kosovo",
      alpha3: "XKX",
    },
  },
});

validatePostalCode("XK", "10000");  // → true
validatePostalCode("XKX", "10000"); // → true (alpha-3 works)

Replace semantics per country, idempotent across calls, and a fail-fast ConfigurationError on bad input. Full guide: docs/CONFIGURATION.md.

SSR / multi-tenant: configure() is a module-level singleton and isn't designed for per-request overrides. If you need that, open an issue describing your use case.

resetConfig(): voidnew in 2.1

Discards any active configure() override and restores the bundled defaults. Primary use cases: test teardown (afterEach(resetConfig)), scenario switching, HMR.

afterEach(() => {
  resetConfig();
});

usePostalCodeValidation()deprecated since 1.1.0

Retained for backward compatibility; delegates to the top-level validatePostalCode. Kept functional in 2.x; scheduled for removal in 3.0. Prefer the top-level exports in new code.


🏷️ Types

type CountryCode = string; // ISO 3166-1 alpha-2 ("US") or alpha-3 ("USA")

type AnyCountryCode = CountryCode | (string & {});
// preserves autocomplete for known codes while accepting runtime-added ones

type Country = {
  postalCodePatterns: string[];  // regex strings wrapped in slashes, e.g. "/^\\d{5}$/"
  examplePostalCodes: string[];
  isGenericRegex: boolean;
  countryName: string;
  countryCode: AnyCountryCode;
};

// Shape accepted by configure()
type PostalCodeConfig = {
  countries: {
    [countryCode: string]: {
      patterns: string[];   // each entry wrapped in slashes, e.g. "/^\\d{5}$/"
      example: string[];
      country: string;
      alpha3?: string;      // optional 3-letter uppercase alpha-3 code
    };
  };
};

🔄 Migration Guide

Coming from a different postal-code library? See docs/SWITCHING.md — covers the common one-line equivalents, batch API migration, and the argument-order gotcha that trips most swaps.

From v1.x → v2.0 (data-shape breaking change)

v2.0 switches to Google's libaddressinput dataset and renames the regex field on the Country record. The runtime API (validatePostalCode, validatePostalCodes, getCountryByCode, getAllCountries) is unchanged — but anything reading country.postalCodeRegex directly needs an update.

Before (v1.x):

const country = getCountryByCode("US");
const regex = new RegExp(country.postalCodeRegex.slice(1, -1));
regex.test("12345");

After (v2.0):

const country = getCountryByCode("US");
const ok = country.postalCodePatterns.some((wrapped) =>
  new RegExp(wrapped.slice(1, -1)).test("12345")
);

If you were only calling validatePostalCode / validatePostalCodes, nothing changes — normalization and return types are identical.

Also note:

  • country.countryName values follow Google's canonical spelling — e.g. "United States" (was "United States of America"), "Russia" (was "Russian Federation"). Update any string pins in tests or UI copy.
  • A handful of previously-orphan alpha-3 codes (Åland, Martinique, Réunion, Puerto Rico, etc.) now resolve correctly through getCountryByCode.

From usePostalCodeValidation (v1.0.x) → validatePostalCode (v1.1.0+)

// Before
import { usePostalCodeValidation } from "postal-code-checker";
const { validatePostalCode } = usePostalCodeValidation();

// After
import { validatePostalCode } from "postal-code-checker";

The usePostalCodeValidation name followed React's hook naming convention, which confused non-React users and tripped the react-hooks/rules-of-hooks lint rule even though it's not an actual hook. The new direct API works identically in React, Node.js, Vue, Angular, Svelte, or plain JavaScript.

usePostalCodeValidation still exists in 2.x and delegates to the new implementation. Scheduled for removal in 3.0.


🗺️ Roadmap

✅ Shipped

  • configure() + resetConfig() — user-supplied country overrides and brand-new countries via a single-place config (2.1.0)
  • format() — canonical storable form, or null if invalid (2.1.0)
  • guessCountries() — countries whose pattern accepts an input (2.1.0)
  • Swap data source to Google libaddressinput (2.0.0)
  • Reproducible data pipeline — sync:data + sync:check guard against upstream drift (2.0.0)
  • postalCodePatterns: string[] — support countries with multiple valid patterns (2.0.0)
  • Interactive demo site at sashiksu.github.io/postal-code-checker (2.0.0)
  • Full ISO 3166-1 coverage (249 countries, zero generic fallbacks) (2.0.0)
  • Batch validation (validatePostalCodes) (1.1.0)
  • Case + whitespace tolerant input (1.1.0)
  • ISO 3166-1 alpha-3 support (1.1.0)
  • Full unit-test coverage of utility functions (1.1.0)

🔜 Planned

  • Subdivision-level validation (Google's sub_zips prefix data)
  • createValidator() factory for SSR / multi-tenant use cases (if demand shows up)
  • Removal of deprecated usePostalCodeValidation (3.0.0)

📊 Data Sources

Postal code patterns, country names, and example codes come from Google's libaddressinput project (Apache-2.0), fetched from https://chromium-i18n.appspot.com/ssl-aggregate-address/data/<CC>. The same dataset powers address forms in Chromium, Android, and Google Pay.

scripts/sync-postal-data.ts regenerates src/assets/index.ts from upstream; npm run sync:check runs in CI and in prepublishOnly to block releases whose on-disk data has drifted from the script's output.

See NOTICE for the upstream Apache-2.0 attribution.

Prior to v2.0.0, data was sourced from the European Central Bank (ECB), retrieved 4 Aug 2024.


🤝 Contributing

Pull requests welcome. See CONTRIBUTING.md for local setup, the country-data workflow (upstream-first via libaddressinput), and maintainer sync steps.

Quick start:

  1. Branch from master: git checkout -b feature/<short-desc> or bugfix/<short-desc>.
  2. Add or update tests under src/__tests__/ for any behavior change.
  3. Follow existing style — npm run prettier and npm run lint ship configs.
  4. Open a PR targeting master.

Found a bug, missing country, or want to propose a feature? Use the structured issue templates. For security issues, see SECURITY.md.


📄 License

MIT