TileJSON — JSON tileset metadata: descriptor for modern web cartography

2026-07-049 min read
TileJSONMapboxtilesMVTPMTilesMBTileswebOGC

TileJSON is a compact open JSON format in which a tile server or static host describes its tile set: where to fetch tiles, within what bounds they exist, the minimum and maximum zoom, who authored the data, and what license applies. A single JSON file a few kilobytes in size turns a "folder of tiles" into a self-contained cartographic resource that MapLibre, Mapbox GL JS, OpenLayers, and Leaflet recognize automatically. For an osm2cdr.ru user this means: publishing PMTiles vector tiles or a raster tileset and wiring them into a site can be done with a single url: '/tilejson.json' — no copying URL templates, no manual bounds, no zoom range guesswork.

Map generation of Tokyo city center
Tokyo center, Shibuya district. Any such fragment from osm2cdr.ru can be exported to PMTiles and described via TileJSON — MapLibre GL JS will pick the map up automatically: bounds, zoom, and URL all read from metadata.

History: from Mapbox 2011 to OGC API Tiles

TileJSON was born in 2011 inside Mapbox as an internal spec for the TileStream tile server. The idea was simple: instead of client-side JavaScript knowing where tiles live and what zoom range they cover, let the server hand out a description and the client read and self-configure. By late 2011 Mapbox published TileJSON 1.0 as an open-source spec on GitHub under BSD license.

Version 2.0 (2013) added UTFGrid support (text "data tiles" for tooltips), template for substitutions, and legend. Version 2.1 (2014) clarified bounds handling across the antimeridian. Version 2.2 (2017) introduced vector tile support — format: "pbf" and vector_layers to describe MVT layers. This turned TileJSON from a raster descriptor into a universal one.

The current final version is TileJSON 3.0.0, released in 2021. It formalized vector layers as a required field for vector tilesets, added fillzoom (the last zoom that should be stretched when tiles for higher zooms are missing), refined the XYZ vs TMS coordinate scheme, introduced data (an array of supplementary GeoJSON sources), and made the spec JSON-Schema-compatible.

In 2023 OGC (Open Geospatial Consortium) released OGC API — Tiles, a new REST standard for publishing tilesets. While formally not TileJSON, OGC API Tiles metadata is deliberately compatible field-for-field: bounds, center, minzoom, maxzoom, tiles (URL templates) — these all carry over almost identically. Many implementations (TileServer GL, Martin, pg_tileserv) emit both TileJSON and OGC API Tiles from one metadata source.

TileJSON file structure

A minimal valid TileJSON 3.0 is an object with two required fields: tilejson (spec version) and tiles (an array of URL templates). Example:

{
  "tilejson": "3.0.0",
  "tiles": ["https://tiles.example.com/{z}/{x}/{y}.pbf"]
}

This is enough for a client to start requesting tiles, but without zoom and bounds the client will fetch any tile at any level — wasteful. In practice always add:

  • minzoom / maxzoom — zoom range where tiles exist. Typically 0–14 for vector, 0–18 for raster.
  • bounds[west, south, east, north] in WGS84 degrees. Geographic data extent.
  • center[lon, lat, zoom] — initial camera position on first open.
  • attribution — HTML snippet with attribution. For OSM data the standard is "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap contributors</a>".
  • scheme"xyz" (Slippy Map, Y grows down) or "tms" (TMS, Y grows up). If omitted, "xyz" is implied.
  • format"pbf" for MVT, "png" / "jpg" / "webp" for raster.

For vector tilesets add vector_layers — an array of layers with attributes and geometry:

"vector_layers": [
  {
    "id": "buildings",
    "fields": {
      "name": "String",
      "height": "Number"
    },
    "minzoom": 12,
    "maxzoom": 14
  }
]

This lets style editors (Maputnik) and validation tools verify that a style references layers and attributes that actually exist.

URL templates in tiles can contain variables: {z} — zoom, {x} / {y} — tile coordinates, {quadkey} — Bing-style (optional). Also valid: {ratio} for retina tiles (@2x) and the subdomain pattern {s} (often [a, b, c]).

Who needs TileJSON: five roles

MapLibre GL web-map developer. With one or two tilesets on a site you can hardcode URL templates in style.json. With dozens (different regions, different styles), it turns into easy-to-mistake copypasta with broken bounds. TileJSON solves it: one line "url": "https://tiles.example.com/region-moscow.json", and the client discovers data location itself.

Tile provider. If you host vector tiles (e.g. published PMTiles on Cloudflare R2 for your region), TileJSON is the standard way to give clients a "connect URL". Alternative — describe the URL in README and hope users copy correctly. TileJSON makes it machine-readable.

Open-data publisher. When a municipality or OSM community publishes a data tileset (regional bike paths, cadastre, infrastructure), a TileJSON description becomes "first-class metadata". Geo-catalogs (Spatineo, OGC Registry, Awesome Vector Tiles) index TileJSON.

Cartographic SDK integrator. Mobile SDKs (MapLibre Native for Android/iOS) also understand TileJSON. One JSON file becomes a "universal adapter" — web client, mobile, desktop QGIS all read the same description.

Stylesheet author. In Mapbox Style JSON sources are described either inline (with URL templates) or via reference "url": "mapbox://..." / "url": "https://tilejson.json". The second path makes style.json smaller, more portable, easier to CDN-cache: style and tileset metadata are separated, you can update the zoom range without touching the style.

Software for TileJSON: seven tools

MapLibre GL JS. Free MapLibre fork of Mapbox GL JS, taken before the license closure. Transparently picks up TileJSON via the url field in a source: { type: 'vector', url: 'https://tiles.example.com/area.json' }. maplibre.org

Mapbox GL JS. Mapbox's commercial library. Fully compatible with TileJSON 3.0; the standard was effectively born inside it. Paid plan for commercial use, but the spec is open. docs.mapbox.com/mapbox-gl-js

OpenLayers. The oldest open-source web cartographic library. The ol/source/TileJSON class — native support since version 4.0. Handy if you mix TileJSON with old WMS/WMTS sources. openlayers.org

Leaflet + leaflet-tilejson. Leaflet itself doesn't understand TileJSON (it's raster-oriented and URL-template-based), but the leaflet-tilejson plugin reads JSON and auto-configures L.TileLayer. github.com/mapbox/leaflet-tilejson

TileServer GL. Open-source tile server from MapTiler. Serves PMTiles / MBTiles / PBF folders and auto-generates a TileJSON endpoint for each tileset. Convenient for self-hosted setups. github.com/maptiler/tileserver-gl

Martin. Rust tile server with PostGIS, PMTiles, and MBTiles support. Emits a /tilejson endpoint per source. Very fast, minimal memory footprint. github.com/maplibre/martin

PMTiles tileserver. The pmtiles serve utility from the PMTiles project serves a local .pmtiles file with auto-generated TileJSON. Perfect for prototyping. github.com/protomaps/PMTiles

Map generation of New York city center
Manhattan. One PMTiles file with this fragment + a TileJSON descriptor — that's all you need to self-host an interactive NYC map on your own CDN.

Pitfalls and tips

Don't confuse bounds and center. bounds describes the geographic range where data EXISTS. center is where the camera looks on first open. If the map is clipped by bounds but center sits outside — the user sees an empty screen. Typical mistake after tileset migration.

scheme: "xyz" vs "tms". XYZ — "Slippy Map" convention, Y grows top to bottom. TMS — the older OSGeo scheme, Y grows bottom to top. MapLibre, Mapbox GL, and most modern engines expect XYZ. If your tiles were generated by old tilemill, check: you may need scheme: "tms" or Y-coordinate conversion.

Antimeridian in bounds. If your set covers the Pacific (e.g. Far East + Alaska), bounds can't be expressed as a single WGS84 rectangle. TileJSON 3.0 allows bounds crossing 180° via two rectangles — but not all clients handle this correctly. Pragmatic workaround: give bounds for one side and don't clip the client viewport.

Vector layers REQUIRED for MVT. Without vector_layers in TileJSON, MapLibre refuses to compile the style (warning, then error on render attempt). Minimum — the list of layer IDs and their fields schema. Easy to forget when generating manually, so use tilelive-copy or TileServer GL, which generate correct TileJSON automatically.

CORS on the TileJSON endpoint. If you host TileJSON on one domain and the front-end on another, you need Access-Control-Allow-Origin: * (or an explicit list) on the server response. Without CORS the browser blocks loading — classic "blank map, CORS error in console" picture.

OSM attribution is required. The OSM ODbL license requires visible "© OpenStreetMap contributors" attribution. Don't forget the attribution field with an HTML link. MapLibre will draw it in the map corner automatically if the field is present.

Versioning tilejson matters. The tilejson: "3.0.0" field isn't cosmetic — clients use it to decide how to parse the rest. If you have legacy TileJSON "2.2.0", don't claim "3.0.0": some 3.0 fields (like the now-required vector_layers.fields) will be expected.

How to make TileJSON for osm2cdr.ru

Direct TileJSON export isn't in the standard osm2cdr.ru format list — it's just a metadata wrapper. But creation takes a couple of steps on top of any tile export (PMTiles recommended; MBTiles and a PBF folder also work).

Step 1. On osm2cdr.ru outline the region (district to whole country). Step 2. In the format dropdown choose PMTiles. Step 3. Wait for the export and download the .pmtiles file. Step 4. Upload .pmtiles to a host — Cloudflare R2, AWS S3, or GitHub Pages for small files (up to 100 MB). Enable CORS on the bucket. Step 5. Create tilejson.json alongside .pmtiles:

{
  "tilejson": "3.0.0",
  "name": "Moscow center vector tiles",
  "description": "OSM vector tiles exported from osm2cdr.ru",
  "version": "1.0.0",
  "scheme": "xyz",
  "tiles": ["pmtiles://https://r2.example.com/moscow.pmtiles/{z}/{x}/{y}"],
  "minzoom": 0,
  "maxzoom": 14,
  "bounds": [37.3, 55.5, 37.9, 55.9],
  "center": [37.62, 55.75, 11],
  "attribution": "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap contributors</a>, generated via <a href=\"https://osm2cdr.ru\">osm2cdr.ru</a>",
  "vector_layers": [
    {"id": "buildings", "fields": {"name": "String"}, "minzoom": 12, "maxzoom": 14},
    {"id": "roads", "fields": {"name": "String", "class": "String"}, "minzoom": 0, "maxzoom": 14}
  ]
}

Step 6. On the front-end, wire it up via MapLibre:

const map = new maplibregl.Map({
  container: 'map',
  style: {
    version: 8,
    sources: {
      osm: { type: 'vector', url: 'https://r2.example.com/tilejson.json' }
    },
    layers: [
      { id: 'roads-line', type: 'line', source: 'osm', 'source-layer': 'roads',
        paint: { 'line-color': '#888', 'line-width': 1 } }
    ]
  },
  center: [37.62, 55.75],
  zoom: 11
});

MapLibre reads TileJSON itself, picks up bounds and zoom range, sets attribution. No URL-template copying, no parameter guessing — update tilejson.json, the map updates.

Map generation of Sydney city center
Sydney Opera precinct. A single TileJSON descriptor turns this PMTiles export into a self-describing resource that any GL-based map can consume without configuration.

Sources

← All articles