Skip to content

PMTiles

A .pmtiles file holds a whole tile pyramid, every zoom level of a dataset, in one file that a static host can serve. The browser reads only the tiles in view. Use it for large data that doesn’t change: building footprints, parcels, a road network, imagery or elevation. It is one of the large-data formats.

Point a vector source’s url: at the file, and name the tile layer to draw in each map layer’s source-layer::

type: map
id: districts
config:
center: [10.2, 42.15]
zoom: 10
mapStyle: "https://tiles.openfreemap.org/styles/positron"
sources:
outlines:
type: vector
url: /gallery-assets/cloud-native/districts.pmtiles
layers:
- id: outlines
type: line
source: outlines
source-layer: districts # a layer inside the archive
paint:
line-color: "#1d3557"

The file is on this documentation site; to run the document on yours, download districts.pmtiles into gallery-assets/cloud-native/ and serve the folder with npx serve. You should see district outlines on a coast. The Large data quick start shows it live with a FlatGeobuf layer.

The source type: says what the tiles hold:

sources:
buildings:
type: vector
url: data/buildings.pmtiles # layers say which source-layer to draw
imagery:
type: raster
url: data/imagery.pmtiles
tileSize: 256 # most raster archives use 256-pixel tiles
terrain:
type: raster-dem
url: data/terrain.pmtiles
encoding: terrarium
  • The archive’s own metadata (zoom range, bounds, attribution, layer names) reaches MapLibre, so you rarely set minzoom or maxzoom.
  • Framing: a tile archive has no extent the browser can measure, so fitTo doesn’t apply to it. Set center and zoom (or bounds) for where the data is.
  • pmtiles://data/buildings.pmtiles works too, the form MapLibre’s own examples use.
  • hover.highlight works on vector archives (core 0.9.2 or later) when the tiles carry feature ids; if they don’t, add promoteId: { <layer>: <property> } to the source, naming a property that is different for every feature, and the map tells you so while you develop.
  • MLT tiles (MapLibre Tiles) draw with no encoding: on maplibre-gl 6.0 or later: maplibre-yaml reads the tile type from the archive’s header, and an encoding: you write wins. MapLibre can fail on MLT tiles with 64-bit integer columns; the map’s notice says to write those columns as doubles or strings, or build MVT tiles. A page that registers its own pmtiles protocol writes encoding: mlt itself (MLT tiles).
  • Vector tiles from GeoJSON: tippecanoe, tippecanoe -o buildings.pmtiles buildings.geojson. Its -zg option picks a zoom range for you. For parcels and building footprints, use the settings that keep the fabric whole.
  • From GDAL: ogr2ogr -f PMTiles out.pmtiles in.gpkg (GDAL 3.8 or later).
  • From an existing MBTiles file: pmtiles convert in.mbtiles out.pmtiles with the pmtiles tool. A browser can’t read .mbtiles directly.

Keep a file small by cutting it to the area and zoom levels you show: each extra zoom level makes it about three times bigger.

A GeoParquet or FlatGeobuf file too large to draw at every zoom (a city’s parcels or buildings seen whole) is what PMTiles is for: the map reads only the tiles in view, simplified for the zoom. tylertoo builds the archive from a GeoParquet file in seconds, with nothing to install but uv:

Terminal window
uv run --with tylertoo==0.8.0 python -c "import tylertoo as t; t.overview('lots.parquet', 'lots-ov.parquet', min_zoom=0, max_zoom=14, collapse_square=True); t.export_pmtiles('lots-ov.parquet', 'lots.pmtiles', layer_name='lots', tile_size_limit=None)"
  • This is the parcel recipe measured on NYC’s 856,687 lots (8 columns): 26 s and 2.1 GB to build a 115 MB archive (tippecanoe took 8 min 40 s), and the citywide map draws in 1–3 s at 58–60 fps on a desktop. collapse_square=True keeps lots too small for a zoom visible as small squares; tile_size_limit=None stops tylertoo 0.8.0 dropping features from dense tiles. min_zoom=0 draws the data at every zoom, a region-wide view too: starting at zoom 10 instead saves only 1.3 s and 3.5 MB, and leaves the map blank further out. MapLibre draws zoom 14’s tiles further in; raise max_zoom for finer detail.
  • tylertoo reads GeoParquet. Convert a FlatGeobuf, GeoJSON or GeoPackage file first with geoparquet-io (nothing to install but uv): uvx --from geoparquet-io==1.7.0 gpio convert geoparquet lots.fgb lots.parquet. GDAL’s ogr2ogr -f Parquet works too when your GDAL has its Arrow/Parquet driver (ogrinfo --formats | grep Parquet; conda-forge’s gdal with libgdal-arrow-parquet, or the ghcr.io/osgeo/gdal Docker image).
  • What the coarse zooms show (squares for small lots, every lot from zoom 14), and how tippecanoe and freestiler compare: Parcel fabrics: PMTiles.
  • Every column stays a tile property. To highlight on hover or address features by id, name an id column with promoteId:
sources:
lots:
type: vector
url: data/lots.pmtiles
promoteId: { lots: BBL }

A basemap style loaded from a URL can use PMTiles too, with no page JavaScript: mapStyle: can point at a style whose sources are pmtiles:// URLs. maplibre-yaml registers the pmtiles:// protocol before the map is created, so the style’s first requests find it.

Relative URLs inside the style are relative to the style.json, the way a stylesheet’s url() is relative to the stylesheet. That applies to a source’s url (including the file inside pmtiles://), tiles, sprite and glyphs. base applies to the style URL itself, so a style under base="/my-repo/" finds its own files under /my-repo/ too. If the style names a file that isn’t there, the map’s notice says which one.

To build a basemap from a Protomaps file, see Host your own basemap.

A page that already calls addProtocol("pmtiles", …) through @maplibre-yaml/core/maplibre (for custom request headers or a custom Source) keeps it: maplibre-yaml leaves an existing registration alone.

  • The notice says “…isn’t a PMTiles file”: a wrong path often returns a web page instead. Check the file name and folder.
  • Nothing draws, no notice: the source-layer: name doesn’t match a layer inside the archive. pmtiles show data/buildings.pmtiles lists them.
  • “…was served without range-request support”: see Publishing › Large files.

More: Large data › When something goes wrong. Export to style.json: Large-data files in an exported style. Gallery: PMTiles source and protocol.