country-codes puts every ISO 3166-1 entry and its neighbours in one CSV
Comprehensive country code information, including ISO 3166 codes, ITU dialing codes, ISO 4217 currency codes, and many others
At a glance
- What is it?
- country-codes is a single CSV with one row per country or territory, joining ISO 3166 alpha-2, alpha-3 and numeric codes with ITU, FIPS, IOC, FIFA and WMO codes, dialling codes, ISO 4217 currencies, M49 regions, country names in six languages and identifiers from GeoNames, EDGAR and Wikidata. The interesting part is the known limitations section, which tells you exactly how the file will mislead a careless reader.
- Who is it for?
- country-codes suits anyone building a country picker, a currency table or a phone number normaliser who wants one download instead of five, and it suits teams that care about provenance, since the source table states the licence of every column and the repository ships an attribution file.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One row per country, with the columns you would otherwise assemble by hand
The dataset is a single CSV, `data/country-codes.csv`, with one row per country or territory that has an ISO 3166-1 entry. What it joins is the part that saves work. The ISO 3166 alpha-2, alpha-3 and numeric codes come first, and then the other code systems: ITU, FIPS, IOC, FIFA, WMO and more. Alongside them sit telephone dialling codes, the ISO 4217 currency codes, the UN M49 region groupings, country names in six languages, and identifiers from GeoNames, the SEC's EDGAR system and Wikidata. The alpha-2 and alpha-3 columns are each unique, so they work as join keys against your own tables. The file is published as a Tabular Data Package, with the column definitions and types in `datapackage.yml`, which means a tool that understands that specification can read the types instead of guessing them. Rows are sorted by `official_name_en`, but the sort order comes from the machine that builds the file, so a few names can change places between builds, which is a small reminder that a generated dataset has a build order as well as a schema.
NA is real data, and pandas will turn it into a hole
The first known limitation is the one that will bite first. The continent code for North America is `NA`, the alpha-2 code of Namibia is `NA`, and the `DS` value for Bonaire, Sint Eustatius and Saba is also `NA`. `pandas.read_csv` converts `NA` into a missing value by default, so those cells come back empty and the data looks like a gap where there is a legitimate code. The fix is to read everything as text and switch off the default NA handling:
import pandas as pd
df = pd.read_csv("data/country-codes.csv", dtype=str, keep_default_na=False)Reading as text is the right default for this file in general, because several numeric columns are stored as numbers with their formatting stripped, and because a postal code, a currency numeric code and a region code are identifiers rather than quantities. The same instinct applies to the currency numeric code, which keeps its leading zeros, for example `008`, and is therefore expected to arrive as a string.
The numeric codes have lost their leading zeros
Three numeric columns are stored as plain numbers: `ISO3166-1-numeric`, `M49` and the UN region codes. The documentation uses Afghanistan as the example, where ISO writes `004` and the file holds `4`. That is not a rounding error, it is a change in the identifier, and a system that stores these as integers cannot tell `4` from `004` once the value has been through a database column. The three digit form is one call away:
df["ISO3166-1-numeric"] = df["ISO3166-1-numeric"].str.zfill(3)The same treatment is needed for the M49 and UN region columns, and it is worth deciding at the start of a project which representation you store, because re-padding later means re-reading the file or migrating every row you already have. Note that `ISO4217-currency_numeric_code` is the exception: it keeps its leading zeros, for example `008`, so it should be read as text and left alone.
Empty cells that are not empty, and cells holding several values
Two more parsing traps are documented, and they pull in opposite directions. Where Statoids has no code, the cell holds a single non-breaking space, U+00A0, rather than nothing. That happens in the `FIFA`, `Dial`, `MARC`, `FIPS`, `WMO`, `ITU`, `IOC` and `DS` columns, and a few other cells, `Capital` for instance, carry stray spaces. A naive emptiness check therefore reports data where there is none. The documented fix strips after reading:
df = df.apply(lambda col: col.str.strip())Then there are cells that hold several values separated by commas. The `Dial` column for the Dominican Republic is `1-809,1-829,1-849`, the `Languages` column lists several language codes, and a country with two currencies has two values in each `ISO4217-currency_*` column in the same order, so `BT` carries `INR,BTN` and `356,064`. Separating them is a `.str.split(",")` call, but the ordering rule is what makes that safe to do rather than a guess.
A licence column, because the sources do not share terms
The documentation includes a table of data sources, and the last column of it is the interesting one: it records what was found about reuse for each source. The UN Protocol and Liaison Service names are marked restricted, because the UN terms of use allow downloading and copying for personal, non-commercial use, without any right to resell or redistribute them or to compile derivative works, and no separate licence was found for the file. The M49 page states no licence, and the same UN terms probably apply but could not be confirmed. Statoids, the SIX Group currency list and the FIFA member associations pages are all recorded as unclear for the same reason. The ISO codes themselves are marked unclear, because ISO allows free of charge use of its country, currency and language codes while also stating that any use, including reproduction, requires written permission. GeoNames is the clean case, under CC BY 4.0, with credit required, which is why the repository ships an `ATTRIBUTION.txt`. The practical consequence is that the file as a whole has no single licence, and the columns you use decide what you may do with them.
The build is a chain of scripts joined on the alpha codes
The Makefile shows how the file is assembled, and it is a pipeline rather than a script. The default target runs the validation, which is a single command:
frictionless validate datapackage.ymlThe data target then walks a dependency graph. A custom `scripts/iso3166.py` produces the ISO data, `scripts/iso4217.py` and `scripts/format_json.py` add the currency codes, `scripts/statoids.py` adds the code systems, and `scripts/flatten.py` produces a flat JSON file. The auxiliary tables are built in parallel by `scripts/geoname.py`, `scripts/edgar.py`, `scripts/cldr.py` and `scripts/unterm_names.py`, and the final CSV is produced by `in2csv` and then joined with the `csvjoin` utility, once on the alpha-3 code to attach GeoNames, once on the alpha-2 code to attach CLDR, and once on the currency country name to attach EDGAR. A `diff` target compares the result against `previous-country-codes.csv` with `daff` and renders an HTML diff, which is how a change to the data is reviewed before it lands.
What the file does not contain, and the gaps to code around
The limitations list is worth reading as a specification of the data's edges. Only ISO 3166-1 entries have a row, so Kosovo, coded `XK` elsewhere, has none. The `UNTERM` name columns are empty for many territories and for some countries, with `AQ`, `HK` and `CI` given as examples. The `EDGAR` column is matched by country name, so it is empty wherever the SEC's name differs from the one used here, which the documentation names as `CA` and `US`. The region columns are empty for Antarctica. The optional UN groupings for least developed countries, land locked developing countries and small island developing states are empty unless the country belongs to that group. A few `FIFA` values are not three letter codes at all, `1` for `GB` and `ROS3` for `AQ`, because the pipeline leaves a value alone when neither it nor the country's alpha-3 code appears on the FIFA list. And `wikidata_id` is a full URL, for example `https://www.wikidata.org/wiki/Q889`, not the bare `Q` number. The repository itself lists no licence file, so the column level terms in the documentation are the only licensing information available.
Editorial conclusion
country-codes suits anyone building a country picker, a currency table or a phone number normaliser who wants one download instead of five, and it suits teams that care about provenance, since the source table states the licence of every column and the repository ships an attribution file. It does not suit a project that needs a single authoritative source per field, because several columns are merged from sources with unclear or restricted terms, and it does not suit code that expects country codes to be a closed set, since only ISO 3166-1 entries have a row. Before you depend on the file, check three things: that you read it as text with default NA handling turned off, that the columns you use are the ones whose licence permits your use, and the row count against your own expectations, with the last push to the repository on 2026-10-15 and no tagged releases to pin a specific build to.
Frequently asked questions
How do I read country-codes.csv without losing the NA values?
Read it as text and turn off the default missing value handling, for example with `pd.read_csv("data/country-codes.csv", dtype=str, keep_default_na=False)`. The string `NA` is a real continent code, an alpha-2 code for Namibia and a value in the DS column.
Why do the numeric country codes look wrong in country-codes?
ISO3166-1-numeric, M49 and the UN region codes are stored as plain numbers, so Afghanistan's 004 appears as 4. Pad them with `str.zfill(3)` to get the three digit form, and note that ISO4217-currency_numeric_code already keeps its leading zeros.
Can I redistribute the country-codes data?
It depends on the column. The UN name columns are marked restricted to personal, non-commercial use, the M49, Statoids, SIX and FIFA sources are recorded as unclear, and the ISO codes are free of charge to use but the documentation also notes ISO requires written permission for reproduction. The GeoNames columns are under CC BY 4.0 with credit required.
Does country-codes include Kosovo?
No. Only entries in ISO 3166-1 get a row, and Kosovo, coded XK elsewhere, is not one of them. Other partial coverage is documented too, with empty UNTERM columns for some territories and an empty EDGAR column wherever the SEC name differs from the name used in the file.
How is the country-codes CSV file validated?
The Makefile default target runs `frictionless validate datapackage.yml`, which checks the file against the Tabular Data Package definition that carries the column types. A separate diff target compares the built file against a previous copy using daff and renders an HTML diff for review.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/datasets-country-codes)Was this useful?