Skip to content

Repository files navigation

@ariadng/countries

Tiny, tree-shakeable country data & conversion utilities for TypeScript.

Typed · Tree-shakeable · Zero-dependency · ESM

Convert between ISO 3166-1 alpha-2 ("CN"), alpha-3 ("CHN"), numeric ("156"), and names ("China") — and pull rich per-country data (native names, calling codes, continents, capitals, currencies, languages, flag emoji, inline SVG flags) for all 249 assigned countries. Import one helper and only its data slice ends up in your bundle.

import { getCountryName, getFlagEmoji } from '@ariadng/countries';

getCountryName('CN'); // 'China'
getFlagEmoji('ID');   // '🇮🇩'

Features

  • Literal union typesCountryCode is a union of all 249 alpha-2 codes, so your editor autocompletes 'ID', 'CN', 'US', … (same for CountryCode3, ContinentCode, ContinentName, LanguageCode).
  • Forgiving inputs — every code-accepting function takes alpha-2, alpha-3, or numeric (string or number), case-insensitive and whitespace-tolerant: 'ID', 'idn', ' IDN ', 360, and '360' all resolve to Indonesia.
  • Never throws — even on garbage input — single lookups return undefined on a miss, list lookups return []. Passing null, undefined, an object, an array, or any other non-string/number value is treated as a miss, never an exception.
  • Tree-shakeable by construction — ESM-only, per-field data modules, sideEffects: false. Importing getCountryName does not drag phone / capital / currency data into your bundle.
  • Zero runtime dependencies — data is generated and committed; nothing is fetched or computed at import time.
  • Frozen singletonsgetCountry('ID'), getCountry('IDN'), and getCountry(360) return the same frozen object reference.
  • Inline SVG flags, opt-inimport { ID } from '@ariadng/countries/flags' bundles exactly one flag (~0.2 KB); the main entry stays SVG-free.
  • JSDoc @example on every export — hover docs teach each function without leaving your editor.

Install

npm i @ariadng/countries

Quick start

import {
  getCountryName,
  toAlpha3,
  getCountry,
  getFlagEmoji,
} from '@ariadng/countries';

getCountryName('CN');   // 'China'
toAlpha3('ID');         // 'IDN'
getFlagEmoji('ID');     // '🇮🇩'

const id = getCountry('ID');
id?.capital;            // 'Jakarta'
id?.currencies;         // ['IDR']
id?.continentName;      // 'Asia'

API reference

Every function that takes a country identifier accepts CountryInput — alpha-2 ('ID'), alpha-3 ('IDN'), or numeric (360 / '360'), case-insensitive and whitespace-tolerant. Single-value lookups return undefined on a miss; list lookups return []. Nothing ever throws — even when a JS caller passes null, undefined, or another non-string/number value, every function returns its miss value instead of raising.

Lookup

getCountry(code: CountryInput): Country | undefined — the full country record. Objects are lazily assembled frozen singletons: the same reference is returned for every identifier form of a country, and Object.isFrozen(...) is true.

getCountry('ID');
// {
//   alpha2: 'ID',
//   alpha3: 'IDN',
//   numeric: '360',
//   name: 'Indonesia',
//   nativeName: 'Indonesia',
//   phoneCodes: ['62'],
//   continent: 'AS',
//   continentName: 'Asia',
//   capital: 'Jakarta',
//   currencies: ['IDR'],
//   languages: ['id'],
//   flag: '🇮🇩',
// }

getCountry('CHN') === getCountry('CN'); // true (same frozen singleton)
getCountry(360)   === getCountry('ID'); // true
getCountry('XX');                       // undefined

getAllCountries(): Country[] — all 249 countries in alpha-2 order. Returns a fresh array on each call, reusing the same frozen singleton objects.

getAllCountries().length; // 249

Codes & conversion

toAlpha2(code: CountryInput): CountryCode | undefined — normalize any identifier to its alpha-2 code.

toAlpha2('IDN'); // 'ID'
toAlpha2('id');  // 'ID'
toAlpha2(360);   // 'ID'
toAlpha2('XX');  // undefined

toAlpha3(code: CountryInput): CountryCode3 | undefined — normalize any identifier to its alpha-3 code.

toAlpha3('ID'); // 'IDN'
toAlpha3('cn'); // 'CHN'
toAlpha3(840);  // 'USA'

toNumeric(code: CountryInput): string | undefined — normalize any identifier to its 3-digit numeric code (zero-padded string).

toNumeric('ID');  // '360'
toNumeric('BRA'); // '076'

isCountryCode(value: unknown): value is CountryCodestrict type guard: exactly two uppercase letters that name an assigned country.

isCountryCode('US');  // true
isCountryCode('us');  // false
isCountryCode('USA'); // false

isAlpha3Code(value: unknown): value is CountryCode3strict type guard: exactly three uppercase letters that name an assigned country.

isAlpha3Code('USA'); // true
isAlpha3Code('US');  // false

isValidCountry(value: string | number): booleanloose check: true for any resolvable code form (alpha-2/alpha-3/numeric, any case). Codes only — not names.

isValidCountry('us');  // true
isValidCountry('USA'); // true
isValidCountry('360'); // true
isValidCountry(360);   // true
isValidCountry('XX');  // false

resolveCountryCode(input: string | number): CountryCode | undefined — the "accept anything" resolver: tries code forms first, then exact (normalized) name, native name, and alias.

resolveCountryCode('CHN');               // 'CN'
resolveCountryCode('china');             // 'CN'
resolveCountryCode('Republic of Korea'); // 'KR'
resolveCountryCode(360);                 // 'ID'

Names & search

getCountryName(code: CountryInput): string | undefined — the common English short name.

getCountryName('CN'); // 'China'

getNativeName(code: CountryInput): string | undefined — the country's name in its own language.

getNativeName('DE'); // 'Deutschland'

getCountryByName(name: string): Country | undefinedexact match (after normalization) against English names, native names, and curated aliases.

getCountryByName('USA');          // Country { alpha2: 'US', … }
getCountryByName('uk');           // Country { alpha2: 'GB', … }
getCountryByName('Deutschland');  // Country { alpha2: 'DE', … }
getCountryByName("Côte d'Ivoire"); // Country { alpha2: 'CI', … }

searchCountries(query: string): Country[] — ranked search across codes, names, native names, and aliases. Six ranks, best first: (1) exact code → (2) exact name/native/alias → (3) English-name prefix → (4) native-name/alias prefix → (5) English-name substring → (6) native-name/alias substring. English-name matches always rank above native/alias matches, and aliases participate in the prefix/substring scans. Deduped, sorted by name within each rank; empty/whitespace query returns [].

searchCountries('indo')[0].alpha2;  // 'ID'
searchCountries('india')[0].alpha2; // 'IN' (exact name beats substring matches)
searchCountries('burm')[0].alpha2;  // 'MM' — Myanmar, found via the 'burma' alias
searchCountries('united').map((c) => c.alpha2); // ['AE', 'GB', 'US', …] (English names first)
searchCountries('');                // []

Phone

getPhoneCode(code: CountryInput): string | undefined — the primary (first) calling code, digits only.

getPhoneCode('ID'); // '62'

getPhoneCodes(code: CountryInput): readonly string[] — all calling codes for a country; [] if unknown.

getPhoneCodes('DO'); // ['1809', '1829', '1849']
getPhoneCodes('XX'); // []

getCountriesByPhoneCode(code: string | number): Country[] — every country using a given calling code. Input is normalized to digits (+, spaces, -, ., (, ) are stripped). Alpha-2 order.

getCountriesByPhoneCode('+62');  // [Indonesia]
getCountriesByPhoneCode(62);     // [Indonesia]
getCountriesByPhoneCode('1');    // [ …, Canada, …, United States, … ]

getCountriesByPhoneNumber(phoneNumber: string): Country[] — match a full phone number: strips non-digits, drops one leading 00 international prefix, then finds the longest calling code that prefixes the number. [] if none.

getCountriesByPhoneNumber('+6281234567890'); // [Indonesia]
getCountriesByPhoneNumber('006281234567890'); // [Indonesia]
getCountriesByPhoneNumber('+14155551212');   // NANP countries with code '1' (incl. US)

Continent

getContinent(code: CountryInput): ContinentName | undefined — the continent's English name.

getContinent('ID'); // 'Asia'

getContinentCode(code: CountryInput): ContinentCode | undefined — the two-letter continent code.

getContinentCode('ID'); // 'AS'

getCountriesByContinent(continent: ContinentCode | ContinentName | string): Country[] — every country on a continent. Accepts code or name, case-insensitive; sorted by English name.

getCountriesByContinent('EU');     // [ …, Germany, …, France, … ]
getCountriesByContinent('Europe'); // same result
getCountriesByContinent('europe'); // same result

Capital, currency, language

getCapital(code: CountryInput): string | undefined — capital city; '' for countries with no capital, undefined only for unknown codes.

getCapital('ID'); // 'Jakarta'
getCapital('AQ'); // '' (Antarctica has no capital)
getCapital('XX'); // undefined

getCurrencies(code: CountryInput): readonly string[] — ISO 4217 currency codes; [] if unknown.

getCurrencies('ID'); // ['IDR']

getCountriesByCurrency(currency: string): Country[] — every country using a currency; case-insensitive, sorted by English name.

getCountriesByCurrency('EUR'); // [ …, Germany, …, France, …, Spain, … ]

getLanguages(code: CountryInput): readonly string[] — ISO 639-1 language codes; [] if unknown.

getLanguages('ID'); // ['id']

getCountriesByLanguage(language: string): Country[] — every country using a language; case-insensitive, sorted by English name.

getCountriesByLanguage('pt'); // [ …, Brazil, …, Portugal, … ]

getLanguageName(language: string): string | undefined — English name of a language code.

getLanguageName('id'); // 'Indonesian'

getLanguageNativeName(language: string): string | undefined — native name of a language code.

getLanguageNativeName('id'); // 'Bahasa Indonesia'

Flags

getFlagEmoji(code: CountryInput): string | undefined — the flag emoji, computed on the fly from the alpha-2 code via Unicode regional indicators (no flag data is stored). undefined unless the input resolves to an assigned code.

getFlagEmoji('ID');  // '🇮🇩'
getFlagEmoji('IDN'); // '🇮🇩'
getFlagEmoji(360);   // '🇮🇩'
getFlagEmoji('XX');  // undefined

SVG flags (@ariadng/countries/flags)

Real flag artwork as inline SVG strings (3×2 aspect ratio, optimized artwork from country-flag-icons). Lives in its own subpath entry so the main entry stays SVG-free — importing @ariadng/countries never parses or bundles a single flag.

Per-country named exports — the tree-shakeable way. Each country's flag is exported under its alpha-2 code; importing one bundles ~0.2 KB, not the whole set:

import { ID, US, JP } from '@ariadng/countries/flags';

ID; // '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342">…</svg>'

// React: render inline
<span dangerouslySetInnerHTML={{ __html: ID }} />

getFlagSvg(code: CountryInput): string | undefined — dynamic lookup by any code form (alpha-2/alpha-3/numeric, case-insensitive). Because the code is only known at runtime, this pulls all 249 flags into your bundle (~174 KB min, ~49 KB gzip) — fine for a country picker, wasteful for a fixed handful of flags.

import { getFlagSvg } from '@ariadng/countries/flags';

getFlagSvg('ID');  // '<svg …>…</svg>'
getFlagSvg('idn'); // same
getFlagSvg(360);   // same
getFlagSvg('XX');  // undefined

getFlagSvgDataUri(code: CountryInput): string | undefined — the same flag as a data:image/svg+xml URI, ready for <img src> or CSS url(). Same bundle caveat as getFlagSvg.

import { getFlagSvgDataUri } from '@ariadng/countries/flags';

<img src={getFlagSvgDataUri('ID')} alt="Flag of Indonesia" width={30} height={20} />

flagSvgs — the raw frozen record, keyed by alpha-2 code: flagSvgs.ID // '<svg …>'.

Raw data access

Advanced: the underlying lookup tables are exported directly. Each is a deeply frozen singleton keyed by alpha-2 code (unless noted), with keys sorted alphabetically. The freeze is genuinely deep — nested array/object values are frozen too, so Object.isFrozen(countryPhoneCodes.ID) and Object.isFrozen(languageNames.id) are both true.

import {
  countryCodes,        // readonly ['AD', 'AE', …] — all 249 alpha-2 codes
  countryAlpha3Codes,  // { AD: 'AND', … }
  countryNumericCodes, // { AD: '020', … }
  countryNames,        // { AD: 'Andorra', … }
  countryNativeNames,  // { DE: 'Deutschland', JP: '日本', … }
  countryPhoneCodes,   // { ID: ['62'], DO: ['1809','1829','1849'], … }
  countryContinents,   // { ID: 'AS', … }
  countryCapitals,     // { ID: 'Jakarta', AQ: '', … }
  countryCurrencies,   // { ID: ['IDR'], … }
  countryLanguages,    // { ID: ['id'], … }
  continentNames,      // { AF: 'Africa', AS: 'Asia', … }
  languageNames,       // { id: { name: 'Indonesian', native: 'Bahasa Indonesia' }, … }
} from '@ariadng/countries';

countryNames.CN;      // 'China'
countryCodes.length;  // 249

Exported types: Country, CountryInput, CountryCode, CountryCode3, ContinentCode, ContinentName, LanguageCode.

Bundle size & tree-shaking

Data is split into one module per field (names, native names, phone, capitals, currencies, languages, …), and the package is marked sideEffects: false with per-file ESM output. A bundler therefore keeps only the data slices your imports actually touchgetFlagEmoji pulls in no data at all, while getCountryName pulls in names but not phone/capital/currency/alias data.

Measured with the tree-shaking audit (npm run check:treeshaking), a single-function import bundles to:

Scenario Imports Minified Gzipped
S1 getCountryName 10.4 KB 5.5 KB
S2 toAlpha3 6.4 KB 3.3 KB
S3 getFlagEmoji 6.5 KB 3.4 KB
S4 getPhoneCode 9.3 KB 4.6 KB
S5 getCountry 31.3 KB 13.8 KB
S7 ID (one SVG flag, from /flags) 0.18 KB 0.16 KB
S8 getFlagSvg (all 249 SVG flags) 173.9 KB 48.9 KB

(Byte figures are the measured npm run check:treeshaking output; KB = 1000 bytes.)

Data

Covers all 249 ISO 3166-1 assigned country codes — the canonical, unambiguous set. Kosovo (XK) is intentionally excluded because it is not an ISO-assigned code.

Data is generated and committed (no network or build steps for consumers), regenerable with:

npm run build:data

Sources, used at build time only and gratefully attributed:

ESM

This package is ESM-only ("type": "module"). Import it with import in ESM projects; on Node ≥ 20.19 you can also require() it from CommonJS thanks to Node's built-in require() of ES modules. TypeScript declaration files (with declaration maps for "go to definition") are included.

License

MIT © 2026 ariadng

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages