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'); // '🇮🇩'- Literal union types —
CountryCodeis a union of all 249 alpha-2 codes, so your editor autocompletes'ID','CN','US', … (same forCountryCode3,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
undefinedon a miss, list lookups return[]. Passingnull,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. ImportinggetCountryNamedoes 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 singletons —
getCountry('ID'),getCountry('IDN'), andgetCountry(360)return the same frozen object reference. - Inline SVG flags, opt-in —
import { ID } from '@ariadng/countries/flags'bundles exactly one flag (~0.2 KB); the main entry stays SVG-free. - JSDoc
@exampleon every export — hover docs teach each function without leaving your editor.
npm i @ariadng/countriesimport {
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'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.
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'); // undefinedgetAllCountries(): Country[] — all 249 countries in alpha-2 order. Returns a fresh array on each call, reusing the same frozen singleton objects.
getAllCountries().length; // 249toAlpha2(code: CountryInput): CountryCode | undefined — normalize any identifier to its alpha-2 code.
toAlpha2('IDN'); // 'ID'
toAlpha2('id'); // 'ID'
toAlpha2(360); // 'ID'
toAlpha2('XX'); // undefinedtoAlpha3(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 CountryCode — strict type guard: exactly two uppercase letters that name an assigned country.
isCountryCode('US'); // true
isCountryCode('us'); // false
isCountryCode('USA'); // falseisAlpha3Code(value: unknown): value is CountryCode3 — strict type guard: exactly three uppercase letters that name an assigned country.
isAlpha3Code('USA'); // true
isAlpha3Code('US'); // falseisValidCountry(value: string | number): boolean — loose 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'); // falseresolveCountryCode(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'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 | undefined — exact 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(''); // []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)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 resultgetCapital(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'); // undefinedgetCurrencies(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'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'); // undefinedReal 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'); // undefinedgetFlagSvgDataUri(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 …>'.
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; // 249Exported types: Country, CountryInput, CountryCode, CountryCode3, ContinentCode, ContinentName, LanguageCode.
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 touch — getFlagEmoji 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.)
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:dataSources, used at build time only and gratefully attributed:
countries-list— MIT © Annexareiso-3166— MIT © Titus Wormercountry-flag-icons— MIT © catamphetamine (flag SVG artwork)
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.
MIT © 2026 ariadng