Interactive maps, election results, census data and records for Ireland, north and south.
Live site: civgraph.net Support: ko-fi.com/scottmoore0
Civgraph is a free, open site for exploring the administrative geography and political history of the island of Ireland. You can look up any townland, ward, constituency, or census area; see how boundaries have changed over time; and browse election results and demographic data.
Hundreds of map layers covering every major administrative geography on the island of Ireland, from the 19th century to the present day.
- Maps and Boundaries - Local government districts, wards, DEAs, parliamentary constituencies, Assembly areas, townlands, civil parishes, baronies, counties, and more. Browse by era with the time slider.
- Elections and Results - Assembly, Westminster, local government, European Parliament, and referendum results. Full STV count animations, candidate and party entity pages, and constituency-level visualisations.
- Census and Demographics - Small Areas, Output Areas, Super Output Areas, Data Zones, and settlement boundaries from NISRA and the CSO.
- Physical Geography - Rivers, watersheds, seas, regional divides, and land classifications.
- Built Environment and Communities - Peacelines, railways, travel-to-work areas, settlements, and place names.
- Spatial Search - Find any boundary feature by name across all map layers.
- Time Slider - Explore how boundaries have changed decade by decade.
- Conditional Styling - Dynamic map styling based on data attributes.
| Type | Examples |
|---|---|
| Local government | Districts, wards, DEAs, local authorities |
| Parliamentary | Westminster constituencies, Dail constituencies, European Parliament regions |
| Assembly | Northern Ireland Assembly constituencies and regions |
| Communities | Townlands, settlements, place names |
| Historical | Civil parishes, baronies, counties, historic council boundaries |
| Census | Small Areas, Output Areas, Super Output Areas, Data Zones |
| Physical | Rivers, watersheds, seas, regional divides |
| Built environment | Peacelines, railways, travel-to-work areas |
| Layer | Technology |
|---|---|
| Maps | MapLibre GL rendering PMTiles vector tiles; FlatGeobuf is a download format, not a render path |
| Build | esbuild with code splitting and performance budgets |
| Search | Fuse.js for map search, spatial index for feature search |
| Geospatial | Turf.js for area/length calculations |
| Testing | Playwright |
| Hosting | Cloudflare Pages + R2 |
# Install dependencies
npm install
# Build JS bundle + minified CSS
npm run build
# Start local server
python -m http.server 5050
# Open http://localhost:5050Read this before anything else. Several directory names are historical and will mislead you. This section is the canonical layout; CONTRIBUTING.md covers the workflow that goes with it.
| Path | What it actually is |
|---|---|
app/ |
the live site — MapLibre GL. Built bundle in app/build/, committed. |
src/ |
shared browser modules, served unbundled. Live, not legacy. |
render/src/ |
shared renderer source; app/ builds from it |
render/metadata/ |
the render catalogue — see "three stores" below |
tests/ |
the Playwright suite |
test2/ |
a compatibility redirect. Not a directory of tests. |
apps/ |
standalone apps (PRONI search). Unrelated to app/. |
browse/ |
the Browse page — hand-written, no build step |
pages/ |
standalone pages (About, Census Explorer) |
functions/ |
Cloudflare Pages Functions |
scripts/ |
build, validation and data pipelines — not deployed |
data/database/ |
the tracked catalogue: maps.json and friends |
data/maps/ |
geometry, served from R2, not from this repository |
archive/ |
superseded code kept for reference. Do not build on it. |
Two traps in particular:
src/is not the old Leaflet stack.js/was renamed tosrc/when Leaflet was retired, and only the dead parts moved toarchive/leaflet/. What is left is live and load-bearing —app/src/app.jsimports it on line 3. This README describedjs/as the entry point for months after that rename, which is where the "src/ is dead" belief came from; it cost 36,028 lines of live code its linting. Seedocs/review/TECH-DEBT-AUDIT.mditem 4.app/andapps/are different things, as arerender/,test2/andtests/. Renaming them is planned but invalidates clones, so it is scheduled rather than done:docs/directory-rename-runbook.md.
The single most confusing thing here, and getting it wrong reliably produces
convincing but false "this layer is broken" diagnoses. Joined by a bare string id,
with a -vector-test suffix on the render side.
| Store | Owns |
|---|---|
data/database/maps.json |
provenance — licence, attribution, downloads |
render/metadata/maps-test.json |
rendering — tiles, zoom, styling, labels |
c1Cards in src/ui-controller.js |
navigation — what a user can click |
render/metadata/maps-test-index.json and render/metadata/layer-details-test2/ are
generated from maps-test.json: edit the source, then run
node scripts/build-test2-metadata-shards.mjs. The client fetches the generated
detail shards, so editing only maps.json leaves the site showing the old values.
All three edges are guarded by validators in npm run check.
| URL | Source |
|---|---|
/ |
index.html + app/build/ (from render/src/) + src/ + build/ |
/browse/ |
browse/ |
/apps/, /apps/proni-search/ |
apps/ |
/pages/about, /pages/census-explorer |
pages/ |
/render/ |
render/ — staging shell, not the public site |
npm run buildThat chains seven steps; the two that produce the served bundles are
scripts/build-shared-shell-assets.mjs (CSS, critical-CSS inlining, thumbnail
manifest) and npm run build:test2 (the MapLibre app). Produces:
app/build/app.bundle.js— the app bundle, committed to the repositoryapp/build/app.bundle.cssbuild/main.css— deferred CSS, split fromassets/css/main.cssbuild/main.critical.css— inlined intoindex.htmlby the build
The build enforces performance budgets and fails if CSS exceeds 225 KB.
Each of those is referenced from index.html with a ?v= cache token derived
from the file's content hash. Never hand-edit a token — npm run check fails
if one does not match, because a stale token means returning visitors keep running
the old file. scripts/validate-app-shell-cache-tokens.mjs explains why.
This file told you to run a bundle script at scripts/bundle.mjs until 2026-08-17.
There is no such file — it moved to archive/legacy-scripts/ when the Leaflet
stack was retired. npm run check now verifies that every repository path these
documents cite actually resolves, so that particular lie cannot recur.
The feature search index can be rebuilt from FlatGeobuf sources:
node scripts/build-feature-index.jsThis generates:
data/database/spatial-index.json- Monolithic index (fallback)data/database/spatial-index/*.json- Per-map chunks (loaded on demand)data/database/spatial-index/_names.json- Lightweight search index (~3 MB)data/database/spatial-index/_manifest.json- Chunk manifest
npx playwright testCreated by Scott Moore. The project includes works from various individual contributors and organisations, as attributed on the website.
The code is MIT. See LICENSE. You may use, modify and redistribute it, commercially or otherwise, provided the copyright and permission notice travel with it.
The data is not. This repository tracks a large amount of third-party
material — census tables, scanned books, boundary metadata — from NISRA, the
CSO, OSNI/Land & Property Services, Tailte Éireann, PRONI, the Northern Ireland
Assembly, the Houses of the Oireachtas and individual contributors. It arrives
under its own terms, predominantly the Open Government Licence and Creative
Commons Attribution. Those terms travel with the data and are not superseded by
the MIT grant. Per-dataset licence and attribution are recorded in
data/database/maps.json (the references and sourceDownloads fields) and
data/database/external-sources.json; the entry for a layer is authoritative for
that layer.
If you reuse a map layer, credit the originating body — crediting Civgraph alone does not satisfy a source that requires its own attribution.
NOTICE sets this out in full. Licences for the third-party packages
compiled into the browser bundle are reproduced in
app/build/THIRD-PARTY-NOTICES.txt, generated at build time and verified by
npm run check.
