Skip to content

Vector tiles and snapshots

There are two ways to get vector tiles out of Sync Server.

Live tiles Snapshots
Freshness Follows the cache Frozen at build time
Built On demand, per tile Once, ahead of time
Cost per request Tile generation (cached) A read from an .mbtiles file
Cacheable at the edge Partly Fully — immutable, 1-year max-age
Good for Data that changes and must look current Big, stable basemap-ish layers under real traffic
/api/v0/rest/services/{baseId}/{tableName}/VectorTileServer/tiles.json → TileJSON
/api/v0/rest/services/{baseId}/{tableName}/VectorTileServer/{z}/{x}/{y}.pbf

(.../VectorTileServer/{layer}/{z}/{x}/{y}.pbf also works, for Esri clients that expect a layer segment.)

Same authentication as any other read. Two paths run underneath, chosen automatically:

  • PostGIS bases serve tiles natively with ST_AsMVT, per tile, reading only the geometry that intersects the requested tile through the spatial index. No whole-table fetch.
  • Everything else builds tiles from the cached GeoJSON and caches the result. The tile cache is cleared whenever the table re-syncs.

A PostGIS table can be addressed by its bare name when that’s unambiguous across schemas, or as schema.table when it isn’t.

A snapshot is an immutable .mbtiles tileset built from a table and hosted by the server. You create one by asking for the mbtiles export format and giving it a slug.

Terminal window
curl -X POST '…/rest/services/{baseId}/FeatureServer/export' \
-H 'Content-Type: application/json' -H 'Authorization: Bearer …' \
-d '{
"formats": "mbtiles",
"mbtiles": {
"name": "Country boundaries",
"attribution": "© International Mapping",
"minzoom": 0, "maxzoom": 10,
"layers": [
{ "tableName": "Countries", "layer": "countries", "fields": ["UID", "NAME"] }
]
},
"snapshot": { "slug": "boundaries-2026q3", "public": false }
}'

Once the job succeeds the tileset is served at:

GET /api/v0/tiles/{baseId}/{slug}/tiles.json → TileJSON 3.0.0
GET /api/v0/tiles/{baseId}/{slug}/{z}/{x}/{y}.pbf → a gzipped MVT tile

A public: true snapshot needs no credentials — point MapLibre straight at the tiles.json. Otherwise the caller authenticates for the owning base as usual.

Key Meaning
name, description, attribution Tileset metadata
minzoom / maxzoom Defaults 0 / 12; maxzoom is overridable per layer
layers[].tableName or layers[].layerId Which table becomes this MVT layer
layers[].layer The MVT layer id — [A-Za-z0-9_-], so it’s style-safe
layers[].fields Keep-list of properties. Omit to keep everything
layers[].geometry Derive geometry from named fields for a table whose config has none
projection Omit for Web Mercator; see below

mbtiles does not accept the where / bbox / outSR / outFields export filters — it has its own per-layer fields and projection instead.

Web Mercator (EPSG:3857) is the default and is built with tippecanoe, which drops features by density as you zoom out. That’s what MapLibre and Mapbox GL can read.

Any other EPSG is built with GDAL’s MVT writer against a custom tiling scheme, which needs an explicit tileOrigin (upper-left, in CRS units) and tileDim0 (the size of the single zoom-0 tile). Those tilesets are for OpenLayers/ArcGIS-style clients with custom tile grids — MapLibre cannot read them.

A cached table may have no geometry because its config never named coordinate fields. Rather than editing the table and forcing a re-sync, give the snapshot its own mapping:

{ "geometry": { "latitudeField": "Lat", "longitudeField": "Lon", "geometryType": "Point" } }

Either a geometryField or both latitudeField and longitudeField. To see what a base has to work with:

GET /api/v0/bases/{baseId}/geometry-sources
GET /api/v0/bases/{baseId}/snapshots → list
PATCH /api/v0/bases/{baseId}/snapshots/{slug} → toggle public, edit name/attribution
DELETE /api/v0/bases/{baseId}/snapshots/{slug} → remove the row and the file
GET /api/v0/bases/{baseId}/snapshots/{slug}/download → stream the .mbtiles

Reads need access to the base; writes need write access.