How to consume pilgrimage data in your application.
Read the catalog from @main — jsDelivr refreshes a branch
ref on its own cycle — then pin every file you download to the tag that
index’s release field names. The @v1 alias is no
longer maintained: jsDelivr caches tag URLs permanently, so it still serves a
March 2026 build.
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@main/index.json
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/route.geojson
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/stages.json
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/metadata.json
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/waypoints.geojson
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/stats.json
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/ways/route.json
https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@v1.12.0/routes/camino-frances/ways/stage-00.json
A route with no ways entry in index.json has not
cleared the length gate — its stages are not walkable yet, and apps hide
it. A route whose entry says "sparse": true is walkable but thinly
curated: fewer than half its stages carry a place beyond the day’s own
start and end.
This fetches index.json straight from jsDelivr in your browser, right now — proof that the URL above actually resolves.
Static example shown below. Enable JavaScript to fetch the live response from jsDelivr.
{
"schemaVersion": "1.0.0",
"generatedAt": "2026-08-19T15:31:51.448Z",
"routes": [
{
"id": "camino-frances",
"name": { "en": "Camino de Santiago (Frances)" },
"region": "Europe",
"country": "ES",
"distanceKm": 764,
"topology": "linear",
"tradition": "christian",
"path": "routes/camino-frances"
}
// ...6 more routes
]
}
const CATALOG = 'https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@main';
// @main always serves the current catalog; index.release names the tag every
// route file below is pinned to
const index = await fetch(`${CATALOG}/index.json`).then(r => r.json());
const BASE = CATALOG.replace('@main', `@${index.release}`);
// Load route geometry for Mapbox GL JS, Leaflet, etc.
const route = await fetch(`${BASE}/routes/camino-frances/route.geojson`).then(r => r.json());
map.addSource('camino', { type: 'geojson', data: route });
// Load stage data
const stages = await fetch(`${BASE}/routes/camino-frances/stages.json`).then(r => r.json());
for (const stage of stages.stages) {
console.log(`Stage ${stage.index}: ${stage.name.en} — ${stage.distanceKm} km`);
}
// Historical pilgrim counts
const stats = await fetch(`${BASE}/routes/camino-frances/stats.json`).then(r => r.json());
const trend = stats.annualPilgrims.trend; // [{year: 1985, count: 690}, ...]
import MapKit
struct Catalog: Decodable { let release: String }
let catalogURL = "https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@main"
let (catalogData, _) = try await URLSession.shared.data(from: URL(string: "\(catalogURL)/index.json")!)
let release = try JSONDecoder().decode(Catalog.self, from: catalogData).release
let base = catalogURL.replacingOccurrences(of: "@main", with: "@\(release)")
let url = URL(string: "\(base)/routes/camino-frances/route.geojson")!
let (data, _) = try await URLSession.shared.data(from: url)
// Decode as standard GeoJSON
let features = try MKGeoJSONDecoder().decode(data)
// Or decode with custom types
let route = try JSONDecoder().decode(GeoJSONFeatureCollection.self, from: data)
import json
import urllib.request
CATALOG = "https://cdn.jsdelivr.net/gh/walktalkmeditate/open-pilgrimages@main"
index = json.loads(urllib.request.urlopen(f"{CATALOG}/index.json").read())
BASE = CATALOG.replace("@main", f"@{index['release']}")
# Load stages
url = f"{BASE}/routes/camino-frances/stages.json"
stages = json.loads(urllib.request.urlopen(url).read())
for stage in stages["stages"]:
name = stage["name"]["en"]
km = stage["distanceKm"]
interior = stage.get("interior", {}).get("theme", {}).get("en", "—")
print(f" Stage {stage['index']:2d}: {name:45s} {km:5.1f} km [{interior}]")
When using this data, include:
Contains information from Open Pilgrimages
(https://github.com/walktalkmeditate/open-pilgrimages),
made available under the ODbL.
Route data © OpenStreetMap contributors.
https://www.openstreetmap.org/copyright
See the contributing guide for how to add routes, fix data, and contribute interior journey content.