Skip to content
Documentation menu
docs / examples / customer-pmtiles
Example

Vector tiles you host as PMTiles

Put a large dataset on the map as vector tiles from one PMTiles file on your own bucket: no tile server, no upload to us. The browser reads only the byte ranges it needs, so a file of gigabytes draws as fast as a small one.

ts
import { createMap, registerPmtilesProtocol } from "@mapmap/maps";
import * as maplibregl from "maplibre-gl";

// createMap already registers pmtiles:// on the maplibre-gl it imports.
// Call this yourself only for a maplibregl.Map you construct directly, or
// a second copy of maplibre-gl in your bundle. It is safe to repeat.
registerPmtilesProtocol(maplibregl);

const map = createMap({
  container: "map",
  apiKey: "snk_...",
  attributionPosition: "bottom-left",
  center: [-0.12, 51.51],
  zoom: 11,
});
await map.whenReady();

map.map.addSource("assets", {
  type: "vector",
  // pmtiles:// in front of the https URL of the file on your bucket.
  url: "pmtiles://https://your-bucket.example.com/assets.pmtiles",
  attribution: "(c) Example Council 2026",
});

// source-layer is the layer name inside the archive (--layer when you
// built it). Slots arrive in the SDK 0.17: map.slot("middle") replaces
// "road-labels".
map.map.addLayer({
  id: "assets-lines",
  type: "line",
  source: "assets",
  "source-layer": "assets",
  filter: ["==", ["geometry-type"], "LineString"],
  paint: { "line-color": "#e07b39", "line-width": 2.5 },
}, "road-labels");

map.map.addLayer({
  id: "assets-points",
  type: "circle",
  source: "assets",
  "source-layer": "assets",
  filter: ["==", ["geometry-type"], "Point"],
  paint: { "circle-radius": 4, "circle-color": "#e07b39" },
}, "road-labels");

Worth knowing

  • The bucket's CORS rule must allow GET from your site's origin, allow the Range and If-Match request headers, and expose ETag. Without the Range header allowed, every browser read fails its preflight.
  • Replace a published archive under a new file name rather than overwriting it in place. A cached client revalidating ranges against a changed file gets a full 200 body and the read fails.
  • There is no hosted tileset upload on MapMap: the file stays on your bucket and is read straight from there by the browser. We store nothing.