COD-AB Specification & Validator
Validate UN OCHA administrative boundary files against the COD-AB specification — directly in your browser. No account, no upload, no server. Your files stay on your machine.
To validate with an LLM:
Hide specification View specification
COD-AB Specification
The Common Operational Dataset – Administrative Boundaries (COD-AB) is a collection of administrative boundary datasets published by the United Nations Office for the Coordination of Humanitarian Affairs (UN OCHA). This specification defines the format and schema for the distribution of these datasets.
COD-ABs are maintained on a country-by-country basis, versioned over time, and cover up to six administrative levels (Admin 0–5). A valid COD-AB MUST include at least one subnational layer (Admin 1 or below); a dataset containing only an Admin 0 layer is not valid.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Administrative Levels
| Level | Typical concept | Required |
|---|---|---|
| Admin 0 (ADM0) | Country / Territory | Yes |
| Admin 1 (ADM1) | Province, Region, State | Yes |
| Admin 2 (ADM2) | District, Department | If available |
| Admin 3 (ADM3) | Sub-district, County | If available |
| Admin 4 (ADM4) | Ward, Sub-county | If available |
| Admin 5 (ADM5) | Village, Locality | If available |
Admin levels MUST be contiguous: if level N is present, levels 0 through N−1 MUST also be present. The local concept name for each level (e.g. "Province", "District") varies by country and is recorded in the dataset metadata.
Admin Boundary Layers ({iso3}_admin{N})
Each admin boundary file represents one administrative level for one country version. Every row in the file is a single administrative unit (polygon) at that level. The lowest-level file is the authoritative source for names and p-codes; all higher-level files MUST be derived from it by selecting the distinct combinations of name and p-code columns for the relevant level. Higher-level geometries MUST be derived by dissolving the lowest-level polygons on the parent p-code — this is the only method that guarantees coincident boundaries across layers.
Column Order
Columns SHOULD appear in the following order within each file:
- Current-level columns:
adm{N}_name,adm{N}_name1,adm{N}_name2,adm{N}_name3,adm{N}_pcode - Ancestor columns: name and p-code columns for each level from N−1 down to 0
valid_on,valid_toarea_sqkm,version(orcod_version)lang,lang1,lang2,lang3adm{N}_ref_name(if present)iso2,iso3(admin 0 only)center_lat,center_lon
Example Column Set
For an admin level 2 file with English primary, Sinhala secondary, and Tamil tertiary language (Sri Lanka, lka_admin2):
adm2_name (string, primary name in English)
adm2_name1 (string, name in Sinhala, nullable)
adm2_name2 (string, name in Tamil, nullable)
adm2_name3 (string, nullable)
adm2_pcode (string, e.g. "LK11")
adm1_name (string, parent admin 1 name)
adm1_name1 (string, nullable)
adm1_name2 (string, nullable)
adm1_name3 (string, nullable)
adm1_pcode (string, e.g. "LK1")
adm0_name (string, country name)
adm0_name1 (string, nullable)
adm0_name2 (string, nullable)
adm0_name3 (string, nullable)
adm0_pcode (string, e.g. "LK")
valid_on (date)
valid_to (date, nullable)
area_sqkm (double)
version (string, e.g. "v03")
lang (string, e.g. "en")
lang1 (string, e.g. "si", nullable)
lang2 (string, e.g. "ta", nullable)
lang3 (string, nullable)
center_lat (double)
center_lon (double)
Names
Name Columns
Each admin level N file contains name columns for all ancestor levels 0 through N. For each level L (0 ≤ L ≤ N):
| Column | Type | Max length | Notes |
|---|---|---|---|
adm{L}_name |
string | 100 | Name in the primary language (lang) |
adm{L}_name1 |
string | 100 | Name in the first alternate language (lang1), nullable |
adm{L}_name2 |
string | 100 | Name in the second alternate language (lang2), nullable |
adm{L}_name3 |
string | 100 | Name in the third alternate language (lang3), nullable |
adm{L}_name MUST be non-null and non-empty for all rows. The alternate language name columns are REQUIRED but MAY contain null values. A name column MUST be null when its corresponding language column (lang1, lang2, lang3) is null. Empty strings ("") MUST be treated as equivalent to null for all name columns.
Name Value Consistency
Name values within a dataset MUST be internally consistent in style. The following requirements apply to all name columns:
- No extraneous whitespace. Names MUST NOT contain leading or trailing whitespace, consecutive spaces, or any non-space whitespace characters (e.g. tabs
\t, newlines\n). - No ALL CAPS names. Names MUST NOT be fully uppercased (e.g.,
KANDAHARis not acceptable; useKandahar). Individual words in acronyms or established abbreviations that are conventionally uppercase are permitted (e.g.,DRC). - No all-lowercase names. Names MUST NOT be fully lowercased (e.g.,
kandaharis not acceptable; useKandahar). This does not apply to function words or particles that are intentionally lowercased within a name. - No indiscriminate auto-capitalization. Names MUST NOT apply title-case capitalization mechanically to every word. Language-specific capitalization rules MUST be respected. In particular, function words and particles such as prepositions and articles (e.g.,
de,do,da,di,du,van,von,of,al-) MUST be lowercased when they appear in the interior of a name, following the conventions of the relevant language (e.g.,Río de la Plata, notRío De La Plata). - Names must contain alphabetic characters. Every name value MUST contain at least one alphabetic character (Unicode letter). Values consisting entirely of digits, punctuation, or other non-letter characters are not valid names.
- Consistent use of abbreviated vs. full forms. Within a single name column, all names MUST use either the abbreviated form or the full form of a descriptor — not a mixture. For example, if
Specialis used in one name,SPMUST NOT appear in another (e.g., all rows should useSpecial Administrative Regionor all should useSAR, not a mix). Abbreviations that are part of the official name of a unit (i.e., the full official name contains the abbreviation) are permitted. - Consistent script and encoding. All values within a single name column MUST be in the script and encoding appropriate for the declared language (
lang,lang1, etc.) and MUST be consistently encoded throughout the file (e.g., no mixing of Latin and non-Latin scripts within the same column). Each individual name value MUST contain only characters from the Unicode block(s) appropriate for the declared language. Validators SHOULD flag values containing characters outside the expected block(s) as violations.
Name Verification Against Official Sources
When validating a dataset, the validator SHOULD use web search to verify that name values match official sources for the country in question. This check applies to all admin levels present in the file.
For admin 1 (province/state) names, the primary reference is the ISO 3166-2 subdivision list for the country (e.g. https://en.wikipedia.org/wiki/ISO_3166-2:CD). The validator MUST check:
- Accents and diacritics — missing or spurious diacritical marks are violations (e.g.
Equateurinstead ofÉquateur,Bas-Ueleinstead ofBas-Uélé,Maï-Ndombeinstead ofMai-Ndombe). - Word separators — hyphens vs. spaces must match the official form (e.g.
Kongo-Centralinstead ofKongo Central). - Spelling — names must match the official ISO 3166-2 spelling exactly for the declared language.
For admin 2 and below, the validator SHOULD cross-reference the Wikipedia page for the country's administrative subdivisions (e.g. the "Territories of the Democratic Republic of the Congo") or equivalent official government source, and flag any name that differs in spelling, accents, or punctuation from the reference list.
Where an official name is ambiguous (e.g. a territory known by both its administrative center name and its official territory name), the validator SHOULD flag this as a warning and ask the user to confirm against the source data.
Name Uniqueness and Consistency
The name columns collectively form a unique identifier for each row: the concatenation of names across all levels (e.g. adm0_name + adm1_name + adm2_name) must uniquely identify each unit. This is enforced at each level independently via the two rules below, which together guarantee uniqueness of the full name path by transitivity. Each unit at level L is identified by its adm{L}_pcode. These checks apply to every level L present in the file. Null name values are excluded from both checks.
Two requirements apply to every level L present in the file:
- No duplicate names within a parent: No two child units with the same parent (
adm{L-1}_pcode) may have the same value in any name column (adm{L}_name,adm{L}_name1,adm{L}_name2,adm{L}_name3). This catches two different areas that have been given the same name. - No name variation for the same P-code: Every row carrying the same
adm{L}_pcodeMUST use the same value in each name column. This catches the same area named inconsistently across rows (e.g. a typo or variant spelling). - Cross-layer consistency: Name values for a given unit MUST be identical across all layers in which that unit appears. For example, the
adm2_namevalues in the Admin 3 layer MUST exactly match those in the Admin 2 layer. Divergence indicates that layers were not derived from a common source.
Admin 0 Country Name
For any name column whose declared language (lang, lang1, lang2, lang3) is one of the six official UN languages (Arabic, Chinese, English, French, Russian, Spanish), adm0_name (or the corresponding alternate name column) MUST use the short name for that language from the UN M49 standard (e.g., Afghanistan in English, République démocratique du Congo in French).
Language Columns
Language codes identify which language each name column is written in:
| Column | Type | Max length | Notes |
|---|---|---|---|
lang |
string | 3 | BCP 47 language tag for adm{L}_name columns |
lang1 |
string | 3 | BCP 47 language tag for adm{L}_name1 columns, nullable |
lang2 |
string | 3 | BCP 47 language tag for adm{L}_name2 columns, nullable |
lang3 |
string | 3 | BCP 47 language tag for adm{L}_name3 columns, nullable |
Language tags MUST be valid BCP 47 language tags. All rows in a file MUST share the same values for lang, lang1, lang2, and lang3 (language codes are constant per layer). lang MUST be non-null and MUST be a romanized language (e.g. English, French, Spanish, Portuguese). lang1, lang2, and lang3 are nullable; a language column being null means that alternate language is absent from the dataset.
Codes
P-Code Columns
For each level L (0 ≤ L ≤ N):
| Column | Type | Max length | Notes |
|---|---|---|---|
adm{L}_pcode |
string | 20 | Place code for the administrative unit at level L |
P-codes (place codes) are semantic identifiers optimised for human readability: the country code and hierarchical structure are visible in the code itself.
Format
P-codes MUST start with the ISO 3166-1 country code (adm0_pcode); the sub-national portion MUST consist of numeric digits only. The country prefix SHOULD be the ISO 3166-1 alpha-2 form (two uppercase letters), as a shorter prefix keeps the full hierarchy compact (e.g., AF for Afghanistan). Some older datasets use ISO 3166-1 alpha-3 instead (e.g., BDI for Burundi); validators MUST accept this but SHOULD warn about it.
P-codes MUST be stored and exchanged without delimiters. Delimited forms such as SS.01.01 or SS-01-01 MUST NOT appear in data columns. Delimiters may be introduced by a presentation layer for readability but cannot be reliably reversed without out-of-band knowledge of each country's digit-width conventions, which vary by country and level.
Uniqueness and Hierarchy
Each administrative unit MUST have exactly one P-code. P-codes MUST be unique within their administrative level. P-codes MUST be hierarchically nested: adm{L}_pcode MUST start with adm{L-1}_pcode for all L > 0.
Global identifiers
P-codes are only guaranteed unique within a given dataset version; they are not globally stable machine identifiers. Admin boundaries change frequently due to elections, conflicts, and administrative reform, so cross-version stability requires an opaque identifier outside the scope of this specification.
Until global identifiers are available, a composite key MAY be formed by appending version to the P-code (e.g. CO05001v02). The trailing digit and leading v make the boundary unambiguous. It will not collide across versions but cannot serve as a persistent reference, since the same unit gets a different composite in each version.
Relationship to Government Codes
P-codes SHOULD be derived from official government codes where they exist. Government codes are typically zero-padded numeric strings; these are appended to the ISO alpha-2 country code to form the P-code. P-codes MUST be typed as strings to preserve leading zeros that would be lost if treated as integers. The government's existing digit width and padding MUST be reproduced exactly.
Examples from well-documented systems:
- US FIPS (United States Census Bureau): States have 2-digit FIPS codes and counties have 3-digit codes within each state. California is
06; Los Angeles County is037within California. The resulting P-codes areUS06(admin 1) andUS06037(admin 2). - DIVIPOLA (Colombia, DANE): Departments have 2-digit codes and municipalities have 3-digit codes within each department. Antioquia is
05; Medellín is001within Antioquia. The resulting P-codes areCO05(admin 1) andCO05001(admin 2). - IEBC county codes (Kenya): Counties are numbered 001–047. The resulting P-codes are
KE001(Mombasa County, admin 1) andKE047(Nairobi City County, admin 1). - INEC LGA codes (Nigeria): States are numbered 01–37; LGAs within each state are numbered 01–NN (2 digits, as no state has more than 44 LGAs). Borno State is
08; its 27 LGAs run from01(Abadam) to27(Shani), with Maiduguri at21. The resulting P-codes areNG08(Borno State, admin 1) andNG0821(Maiduguri LGA, admin 2).
Fallback When No Government Codes Exist
When no official government coding system exists, units SHOULD be sorted alphanumerically by name and assigned sequential numbers. The digit width is determined independently for each admin level within a country: the numeric portion MUST use the minimum number of digits required to represent the largest number of units found within any single parent unit at that level, with zero-padding applied consistently across all codes at that level.
For example, Uganda (UG) has 4 administrative regions at admin 1, and the maximum number of child units per parent decreases as units become more granular:
| Admin level | Unit type | Max units per parent | Digit width | Example codes |
|---|---|---|---|---|
| Admin 1 | Regions | 4 | 1 | UG1, UG2, UG3, UG4 |
| Admin 2 | Districts | 40 | 2 | UG101, UG102, … UG440 |
| Admin 3 | Sub-counties | 20 | 2 | UG10101, UG10102, … UG44020 |
| Admin 4 | Parishes | 15 | 2 | UG1010101, UG1010102, … UG4402015 |
Admin 3 and admin 4 both use two digits here because no parent at either level contains more than 99 child units.
Text Representation
When writing p-codes in prose or documentation, the RECOMMENDED convention is to enclose the p-code in square brackets and the place name in quotation marks, e.g. "Medellín" [CO05001]. This makes p-codes visually distinct from surrounding text and unambiguous when names and codes appear together.
Continuity Across Versions
P-codes SHOULD remain stable across dataset versions. When a government introduces a substantially new boundary system, p-codes SHOULD follow the new system even if continuity is broken. A retired P-code SHOULD NOT be reused for a different administrative unit.
Continuity SHOULD also be broken when a restructuring changes the number of units enough that preserving old codes would produce over-engineered results. For example, Uganda previously had 128 administrative level 1 districts with codes like UG101 and UG432; after restructuring to a small number of regions, forcing continuity would produce codes like UG001 or UG100 rather than the clean UG1, UG2 that the digit-width rule would naturally yield. In such cases the p-code system SHOULD be reset.
Versions
Version Column
| Column | Type | Notes |
|---|---|---|
version |
string | Version string, e.g. v01 or v02.01 |
version MUST be present in all datasets. The version string follows one of two formats:
- Major version (
v{NN}): used when the change status is major (e.g.v03). - Minor version (
v{NN}.{NN}): used when the change status is minor (e.g.v02.01).
The major component is zero-padded to two digits and starts at v01. The minor component is also zero-padded to two digits and resets to 01 on each major increment.
Major Version
A major version MUST be assigned when the update introduces changes that may break existing joins, scripts, dashboards, or maps — i.e. downstream consumers cannot simply auto-refresh. The following changes MUST trigger a major version:
- Boundary geometry has been redrawn, merged, or split (new delimitations or realignments)
- A new administrative level has been introduced, or existing levels have been reclassified
- The number of records has changed (administrative units added or removed)
- P-codes have been significantly reassigned or renumbered across most units
- Administrative unit names have been significantly renamed or updated
- Attribute schema has changed (fields added, removed, or renamed)
Minor Version
A minor version MUST be assigned for corrections that do not affect boundary definitions, record counts, or the coding structure, and that allow downstream systems to auto-refresh without disruption. The following changes MUST trigger a minor version:
- Minor topology fixes (e.g. healing overlaps, removing slivers)
- Small-scale coordinate corrections (e.g. coastline adjustments, capital point locations)
- Adding or updating a small number of supplementary features (e.g. admin centroids, capital points, admin lines)
- Populating missing attribute values
- Correcting spelling or formatting typos in attribute values
- Adding or correcting a small number of P-codes that do not affect existing downstream systems
Note: Some older datasets use
cod_version(e.g.V_01) instead ofversion. This is a legacy variant and SHOULD be updated toversionwhen datasets are revised.
Attributes
Date Columns
| Column | Type | Notes |
|---|---|---|
valid_on |
date | When this version of the data was last updated |
valid_to |
date | When this version was superseded; null if this is the current version |
All rows in a file MUST share the same valid_on and valid_to values (dates are constant per layer). valid_on MUST be non-null. valid_to MUST be null for the current (latest) version of a dataset and non-null for retired versions.
Computed Columns
| Column | Type | Notes |
|---|---|---|
area_sqkm |
double | Area of the polygon in square kilometres |
center_lat |
double | Latitude of a representative point guaranteed within polygon |
center_lon |
double | Longitude of a representative point guaranteed within polygon |
These columns are added during the publishing pipeline and are not required in candidate datasets submitted for validation. Validators MUST NOT raise errors or warnings for absent computed columns.
These values are computed from the geometry. area_sqkm is computed in an equal-area projection (EPSG:6933). center_lat and center_lon are geographic coordinates (EPSG:4326) of a point guaranteed to be within the polygon.
center_lat and center_lon SHOULD be generated using a Maximum Inscribed Circle (MIC) algorithm, which finds the largest circle that fits inside the polygon and uses its center as the representative point. Implementations include DuckDB's ST_MaximumInscribedCircle and GeoPandas' GeoSeries.maximum_inscribed_circle. Many existing datasets use a simple centroid instead, which does not guarantee that the point falls within the polygon (e.g. for concave or donut-shaped polygons).
Identifier Columns (Admin 0 only)
These columns are present only on admin level 0 files:
| Column | Type | Max length | Notes |
|---|---|---|---|
iso2 |
string | 2 | ISO 3166-1 alpha-2 country code, e.g. AF |
iso3 |
string | 3 | ISO 3166-1 alpha-3 country code, e.g. AFG |
Note:
iso2andiso3appear only in admin 0 files in current data. They SHOULD be included in higher admin levels to keep schemas consistent.
Reference Name Column (Deprecated)
Deprecated: The
adm{N}_ref_namecolumn (also seen asadm{N}_ref_name1) is deprecated and SHOULD NOT be included in new datasets. Existing data containing this column MUST NOT cause parsers to fail, but it should be omitted going forward.
| Column | Type | Notes |
|---|---|---|
adm{N}_ref_name |
string | Romanized or UN official reference name for the current-level unit |
This column, when present, contains the preferred reference name for the administrative unit at the current level (level N). It is typically the romanized Latin-script form when the primary script is non-Latin. Only the current level's ref name is included, not ancestors.
Geometry
Coordinate Reference System
All layers MUST use the WGS 84 geographic coordinate system (EPSG:4326). Coordinates MUST be two-dimensional; 3D coordinates (with a Z value) MUST NOT be present.
Geometry Type
Every feature MUST have a non-empty geometry. All geometries MUST be of type Polygon or MultiPolygon. Multipart geometries (MultiPolygon) MUST be used for administrative units that consist of multiple distinct polygons (e.g. a mainland area plus islands); such units MUST NOT be split across multiple rows.
Geometry Validity
All geometries MUST be valid according to the OGC Simple Features specification. Invalid geometries (e.g. self-intersections, unclosed rings, duplicate vertices) MUST be corrected before publication.
Topology
Within a single layer:
- Polygons MUST NOT overlap each other.
- There MUST be no gaps (slivers) between adjacent polygons.
Across layers:
- Each polygon at admin level N MUST be fully contained within exactly one polygon at admin level N−1.
- The boundary edges of child polygons MUST be coincident with the boundary of their parent polygon (no gaps or overlaps at layer boundaries).
A polygon's ancestor name and p-code attribute values MUST match the name and p-code of the parent polygon that spatially contains it.
Area
Each feature's area_sqkm value MUST be the area of the feature's geometry measured in square kilometres, calculated using an equal-area projection. All features within a layer MUST share the same bounding box (i.e. the layer covers a consistent national extent). The layer MUST have a valid bounding box.
Legacy
This document describes known deviations from the COD-AB specification present in existing datasets. These should be addressed in future releases.
Non-Standard Columns
Datasets MAY include additional columns not defined in this specification (e.g., regionname_en, regioncode, unittype). Such columns MUST be placed after all standard columns and SHOULD be documented by the data producer. Parsers MUST NOT fail when encountering non-standard columns.
cod_versionvsversion: Some datasets (those withv_in the directory name) usecod_versioninstead ofversion, and the value format differs (V_01vsv01).adm{N}_ref_name1: A few datasets useadm{N}_ref_name1instead ofadm{N}_ref_name. These should be renamed.valid_totimezone: Some files storevalid_towithout a timezone, others with UTC. This should be standardised to always include UTC.center_lat/center_lonmissing: A small number of files are missing these columns (e.g.,cod_ab_dza_v01).- Non-standard columns: Some files contain extra columns outside this spec (e.g.,
regionname_en,regioncode,unittypein Afghanistan admin 2). - Admin lines, points, and capitals: These supplementary layers have inconsistent schemas across countries and are not yet fully standardised.