Postal Codes Dataset for Switzerland, CH

259
Updated:
Files:1
Size:12.8 kB
Formats:csv

Postal Codes Dataset for Switzerland, CH including name of the city, town, or place, various administrative divisions and alternative city names.

Part of a Data Solution

  • Postal Codes Solution

    Every worldwide postal-code dataset in one bundle — the ultimate global reference for precise postal codes.

    Explore →

Premium

You're viewing a free sample

Get the complete dataset — all rows and files — delivered instantly after checkout, with lifetime access to the latest version.

  • Secure checkout via Stripe
  • Instant download after payment
  • Lifetime access to the latest version
  • opendata.swiss terms of use, open tier (commercial use permitted, attribution recommended) license
20% off
$99.90$79.92
one-time payment

API Access

Access dataset files directly from scripts, code, or AI agents.

Browse dataset files
Dataset Files

Each file has a stable URL (r-link) that you can use directly in scripts, apps, or AI agents. These URLs are permanent and safe to hardcode.

/logistics/postal-codes-ch/
https://datahub.io/logistics/postal-codes-ch/_r/-/.licensing-notes.md
https://datahub.io/logistics/postal-codes-ch/_r/-/README.md
https://datahub.io/logistics/postal-codes-ch/_r/-/datapackage.yml
Key Files

Start with these files — they give you everything you need to understand and access the dataset.

datapackage.ymlmetadata & schema
https://datahub.io/logistics/postal-codes-ch/_r/-/datapackage.yml
README.mddocumentation
https://datahub.io/logistics/postal-codes-ch/_r/-/README.md
Typical Usage
  1. 1. Fetch datapackage.yml to inspect schema and resources
  2. 2. Download data resources listed in datapackage.yml
  3. 3. Read README.md for full context

Data Files

Postal Codes Data Resource for Switzerland, CH


About this dataset

Switzerland (CH) — internal licensing notes

Source research, 2026-09-16 — first proper audit of this country

No .licensing-notes.md existed for CH before this pass. datapackage.yml carries the old-style placeholder sources: [{title: "Multiple sources", path: https://datahub.io/collections/postal-codes-datasets}] — the same non-informative pattern already flagged as a defect elsewhere in this project (see feedback_datapackage_no_sources) — and no licenses: array at all. Nothing in the repo currently records what ch/ch.csv actually is.

1. What the current R2 file (ch/ch.csv, 436,424 bytes, 2026-07-30) actually is — VERIFIED

Fetched directly from the main postal-codes R2 bucket (read-only, R2_ACCESS_KEY/R2_SECRET_KEY from this repo's own .env). 4,520 data rows + header.

Fetched https://download.geonames.org/export/zip/CH.zip (CC BY 4.0) directly for comparison: CH.txt also has exactly 4,520 rows, and a row-by-row diff of the first N rows shows the R2 file is a straight tab-to-comma conversion of raw GeoNames CH.txt, nothing more:

GeoNames:  CH  5000  Aarau  Kanton Aargau  AG  Bezirk Aarau  1901  Aarau  4001  47.3887  8.0483  4
R2 file:   CH,5000,Aarau,Kanton Aargau,AG,Bezirk Aarau,1901,Aarau,4001,47.3887,8.0483,4.0,Aarau

Confirmed defect introduced in that conversion: accuracy is float-formatted (4.0, 1.0, 3.0, 6.0 instead of GeoNames' own integer 4, 1, 3, 6). This is the exact class of bug datasets/lk/scripts/publish_r2.py now hard-aborts on (if r["accuracy"].endswith(".0")) — CH predates that guard and ships with the defect live. Distribution in the raw GeoNames file: 4,006 rows accuracy=4, 351 blank, 105 accuracy=3, 31 accuracy=1, 27 accuracy=6.

The alternative_city_name column is also visibly GeoNames' raw alternatenames join — it includes transliteration noise typical of an unfiltered GeoNames pull (e.g. CH4005 as an "alternative name" for Erlinsbach, Bibershtajn for Biberstein) rather than a curated list.

The one genuinely good thing about this file — do not lose this when rebuilding

Checked directly against the real ISO 3166-2:CH canton list (26 cantons): GeoNames' admin_code1 for Switzerland is not the usual "false friend" internal number seen in nearly every other country audited in this project. All 26 distinct admin_code1 values in CH.txt are exactly the real canton abbreviations — AG, AI, AR, BE, BL, BS, FR, GE, GL, GR, JU, LU, NE, NW, OW, SG, SH, SO, SZ, TG, TI, UR, VD, VS, ZG, ZH — with zero mismatches. (No CH- prefix is applied, unlike some other built countries in this repo, e.g. LK ships admin_code1: LK-2.)

admin_code2 (e.g. 1901 for "Bezirk Aarau") also turns out to be a real official BFS number, not a GeoNames-internal one — confirmed via the Swiss Federal Statistical Office's own documented scheme: a Bezirksnummer is the official BFS canton number (01–26, Aargau = 19) followed by a two-digit sequence number within the canton (01, 02, …), so 1901 = canton 19 (Aargau) district 01 (Bezirk Aarau). This checks out across the sample. Switzerland appears to be an exception to this project's usual GeoNames admin-code false-friend problem at both admin1 and admin2 — worth confirming exhaustively against BFS's own Bezirksnummer register before relying on it for a rebuild, but nothing found in this pass contradicts it.

Verdict on the current file: stale-by-convention (float accuracy bug, raw unfiltered alt-names, no licence metadata) but not fabricated or wrong at the admin-code level. It is a legitimate GeoNames CC BY 4.0 extract, mis-packaged (missing attribution, missing licence block, one formatting bug) rather than bad data.

2. Official tier-1/2 sources checked FIRST, per the standing GeoNames-is-fallback rule

Swiss Post (post.ch) — checked, not the primary distribution channel for this

Swiss Post publishes a "street directory with sorting data" (free) and an open-data program (post.ch/open-data), oriented at addressing/sorting data (street-level, for mailers) rather than a public locality↔PLZ↔canton reference table. Not fetched directly this pass — the better-fitting dataset (below) is the one BFS/swisstopo are statutorily tasked with publishing.

swisstopo — CONFIRMED, this is the right official source, fetched and verified directly

Amtliches Ortschaftenverzeichnis mit Postleitzahl und Perimeter ("Official index of cities and towns including postal codes and perimeter"), published by the Federal Office of Topography (swisstopo) under a legal mandate (Art. 24 GeoNV — the ordinance on geographical names). Confirmed via the opendata.swiss CKAN API (api3/action/package_show):

  • Publisher: Bundesamt für Landestopografie swisstopo
  • Update frequency: MONTHLY (accrual_periodicity), confirmed live — the STAC item's properties.updated is 2026-09-01, i.e. this month's refresh, not a stale snapshot.
  • Licence: opendata.swiss "terms of use" category terms_open (rights: https://opendata.swiss/terms-of-use#terms_open) — fetched the terms-of-use page directly. This tier is opendata.swiss's most permissive: commercial use permitted, attribution only "recommended," not a hard legal requirement. No SPDX identifier exists for this — should be recorded as custom:opendata-swiss-terms-open in any licence_status block, per this project's provenance-gate convention (never freeform prose).
  • Distribution: found via the underlying STAC API (data.geo.admin.ch/api/stac/v0.9/collections/ch.swisstopo-vd.ortschaftenverzeichnis_plz/items), which is more reliable than scraping the (JS-rendered, bot-blocking — 403'd on a plain WebFetch, required a browser UA via curl) opendata.swiss/swisstopo HTML pages. Direct CSV asset: https://data.geo.admin.ch/ch.swisstopo-vd.ortschaftenverzeichnis_plz/ortschaftenverzeichnis_plz/ortschaftenverzeichnis_plz_4326.csv.zip (WGS84 variant; a _2056 LV95/projected variant, .gdb, .shp and .xtf are also published from the same STAC item).

Fetched and inspected directly (AMTOVZ_CSV_WGS84.csv, ;-delimited, UTF-8 BOM, CRLF): 5,718 data rows, fields:

Ortschaftsname; PLZ4; Zusatzziffer; ZIP_ID; Gemeindename; BFS-Nr;
Kantonskürzel; Adressenanteil; E; N; Sprache; Validity

This is a materially better source than GeoNames on every axis that matters to this product:

  • Kantonskürzel is the real ISO 3166-2:CH canton code, straight from the authority itself — 26 real cantons confirmed present, plus a deliberate blank for the 20 rows that are actually Liechtenstein municipalities sharing the Swiss postal-code range 9485–9499 (Vaduz, Schaan, Triesen, etc. — Liechtenstein uses Swiss Post). These must be filtered out (or handled explicitly) for a country_code: CH-only product — confirmed GeoNames' own CH.txt already excludes these 20 LI localities (they live in GeoNames' LI.txt instead), so the current R2 file does not have this problem; a naive rebuild from the raw swisstopo file would need the same filter or it would introduce it.
  • BFS-Nr is the real official Swiss municipality number (the same numbering BFS's Amtliches Gemeindeverzeichnis uses), not an internal ID — a genuine upgrade in traceability over GeoNames even though (per above) GeoNames' own admin codes for CH turned out to be legitimate too.
  • 3,190 unique 4-digit postal codes, 2,123 municipalities, all 26 cantons represented — broader and finer-grained than GeoNames' 4,520-row / effectively-locality-level file, because Zusatzziffer ("supplementary digit") captures genuine sub-PLZ subdivisions GeoNames collapses (e.g. Lausanne 25/26/27 as distinct large-account PO subdivisions of PLZ 1000, each with its own coordinate and municipality share).
  • No Bezirk (district) field. This file gives locality → PLZ → Gemeinde (BFS-Nr) → Kanton, but nothing at the admin2/district tier — so it cannot alone reproduce the current file's admin_name2/ admin_code2 (Bezirk) columns. BFS's own Amtliches Gemeindeverzeichnis der Schweiz (bfs.admin.ch/.../agvch.html, also distributed via opendata.swiss, "Historisiertes Gemeindeverzeichnis der Schweiz") is the documented source of the Bezirksnummer/Bezirksname crosswalk and would need to be joined in separately — found and its numbering scheme confirmed (canton-number + 2-digit sequence, matching the 1901-style codes already seen), but the dataset itself was not fetched/inspected row-by-row in this pass.

GeoNames — fallback baseline, evaluated per the standing rule (checked LAST, not defaulted to)

download.geonames.org/export/zip/CH.zip → 200, CC BY 4.0, 4,520 rows. Already characterized in full above (§1) since it's also what the current R2 file is built from. Its admin-code fidelity for CH is unusually good for this project, but its granularity (no PO-box-style sub-PLZ subdivisions), unfiltered alternate-names noise, and update cadence (irregular community-driven refresh vs swisstopo's guaranteed monthly cycle) are all inferior to the confirmed official source.

Verdict

A real official upgrade exists and was not previously used — recommend a rebuild from swisstopo's Amtliches Ortschaftenverzeichnis, not a patch of the current GeoNames-derived file.

This is NOT a "GeoNames is good enough" case like some other already-audited countries in this project — Switzerland has a statutorily-mandated, monthly-updated, directly-fetchable, liberally- licensed (terms_open, commercial use permitted, attribution merely recommended) official federal source that is finer-grained than what ships today. The current file's admin-code correctness (both cantons and what turned out to be real BFS district numbers) is a pleasant surprise and should be preserved/cross-checked in any rebuild, but the file itself is stale packaging of a lesser source, not a defensible final answer.

Recommended next step (not executed in this pass — research/audit only):

  1. Rebuild from swisstopo's ortschaftenverzeichnis_plz_4326.csv.zip (WGS84 variant) as the postal_code/place_name/canton/municipality backbone.
  2. Filter out the 20 rows with blank Kantonskürzel (Liechtenstein municipalities under Swiss PLZs 9485–9499) unless a decision is made to ship them under country_code: LI in a separate product instead.
  3. Source admin_name2/admin_code2 (Bezirk) from BFS's Amtliches Gemeindeverzeichnis (same terms_open family, needs its own licence check before use) rather than carrying GeoNames' — even though GeoNames' Bezirk numbers checked out as genuine BFS numbers in the sample inspected, this should be verified against the authoritative table directly rather than assumed to hold everywhere.
  4. Fix the accuracy float-formatting bug regardless of source chosen — it is a pure packaging defect.
  5. Write a real licences:/licence_status block (custom:opendata-swiss-terms-open, decision: "unresolved" until a human signs off, channel: "postal-codes-r2") via the shared datapackage.yml convention — replace the current sources: [{title: "Multiple sources", ...}] placeholder either way.

Not done in this pass, by design: no script written, no datapackage.yml change, no R2 write, no git commit. This file is the audit record only.

Build — EXECUTED 2026-09-16 (LOCAL ONLY, nothing published)

Built per the verdict above: official swisstopo source as the backbone, replacing the raw GeoNames pass-through.

Result: 5,974 rows — 5,698 official locality rows + 276 PO-box / large-account rows. 3,362 distinct PLZ, all 26 cantons, 2,112 municipalities. District tier filled on 5,881/5,974 rows (98.4%). Frictionless: valid: True (run from inside datasets/ch/data/).

A correction to the audit above, found during the build. The audit concluded swisstopo was "broader and finer-grained" than GeoNames. That was half right. Direct comparison of the two:

  • Every one of swisstopo's 3,177 distinct PLZ4 also appears in GeoNames.
  • GeoNames carries 185 PLZ that swisstopo has no row for at all.

Inspected directly, those 185 are PO-box and corporate large-account codes — "Zofingen PF" (Postfach), "Zofingen PostFinance", "Zofingen PF UBS Verarb.", "Villigen PSI", branch codes like "Aarau 1". They are real deliverable postal codes; they are simply not localities, which is exactly why a locality register omits them. A swisstopo-only build would therefore have been a silent 185-code regression against the file already shipping. They are carried through as extra rows tagged code_type = po_box_or_large_account. The union lands at 3,362 distinct PLZ — exactly matching GeoNames' own count, with official provenance added rather than coverage traded away.

Second structural finding: the row grain is not one-row-per-code. swisstopo's file is a locality × municipality table — a locality straddling a municipal boundary gets one row per municipality plus that municipality's Adressenanteil (address share). 1,202 of 4,060 localities are split this way (PLZ 1015 "Lausanne" spans Ecublens 84.1%, Saint-Sulpice 8.3%, Chavannes-près-Renens 7.6%). Those rows are kept, not collapsed — the 13-column schema has one municipality slot and picking a winner would discard real data. The share ships as a disclosed extra column (address_share_pct) so a consumer can reduce to one row per code themselves. Primary key is (postal_code, place_name, admin_code3), verified unique across the union.

BFS deliberately not used. BFS's authoritative municipality→district register was fetched successfully (agvchapp.bfs.admin.ch/api/communes/ levels) but NOT used: its opendata.swiss record declares no licence at all (license_id: None, every resource rights: None) and BFS's own terms-of-use pages returned HTTP 500 from this environment, so no permission could be confirmed. Silence is not permission. The district tier comes from GeoNames (CC BY 4.0, explicit) instead, joined on the official BFS municipality number. If BFS's terms are confirmed later, swapping that join is an isolated change.

Other decisions, all disclosed not guessed: 20 Liechtenstein rows (PLZ 9485–9499) excluded — separate country, and GeoNames excludes them too. 3 municipalities (BFS-Nr 304, 2275, 6458) have contradictory district values in GeoNames after municipality mergers — those ship with an empty district rather than an arbitrary pick. accuracy empty on every row (swisstopo carries no accuracy grade; this also retires the float-4.0 defect that affected 4,169 of the old file's 4,520 rows). alternative_city_name dropped rather than filled from GeoNames' raw alternate-names noise. admin_name1 uses official ISO 3166-2:CH names, not GeoNames' language-inconsistent ones.

licence_status: compatible — swisstopo terms_open (commercial use permitted, attribution recommended) + GeoNames CC BY 4.0 (attribution-only, no share-alike).

NOT done, by instruction: no R2 upload, no git commit, no _meta/catalog.json change, no publish_r2.py, and datasets/ch/ datapackage.yml left untouched — it still carries the "Multiple sources" placeholder, no licenses array, no premium_tier, and stale resource bytes. All of that needs updating before any republish.