Skip to content
Documentation menu
docs / examples / custom-xyz-raster
Example

Your own XYZ raster tiles

Draw raster tiles from your own tile server over the MapMap basemap: aerial imagery, a flood model, a scanned historic map. One raster source with a {z}/{x}/{y} template, your credit on it, and the roads and labels kept on top.

ts
import { createMap } from "@mapmap/maps";

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

map.map.addSource("my-raster", {
  type: "raster",
  // Your tile server. {z}/{x}/{y} is the XYZ (Google) scheme; add
  // scheme: "tms" if your server counts y from the south.
  tiles: ["https://tiles.example.com/survey/{z}/{x}/{y}.png"],
  tileSize: 256,
  minzoom: 8,
  maxzoom: 17,                          // MapLibre overzooms past this
  bounds: [-2.05, 52.38, -1.73, 52.58], // no requests outside your coverage
  attribution: "Survey imagery (c) Example Ltd 2026",
});

// "road-labels" keeps roads and labels readable over the imagery; use
// "building" to put it beneath the roads too.
// Slots arrive in the SDK 0.17: map.slot("middle") or map.slot("bottom").
map.map.addLayer({
  id: "my-raster",
  type: "raster",
  source: "my-raster",
  paint: { "raster-opacity": 0.8 },
}, "road-labels");

Worth knowing

  • The attribution string on the source joins the map credit while the layer is on the map, which is where an imagery licence usually asks for it.
  • Set maxzoom to the deepest zoom your server actually has. Past it MapLibre stretches the last tiles instead of requesting tiles that do not exist.
  • A TileJSON URL works in place of tiles: url: "https://tiles.example.com/survey/tiles.json" reads the template, zoom range, bounds and attribution from the server.