Short answer
MBTiles is a specification for storing a pyramid of map tiles inside a single SQLite database file. Instead of a folder holding millions of small {z}/{x}/{y}.png or .pbf files, you ship one file that a renderer reads tile by tile. That single-file portability is exactly what offline and field mapping needs: copy one .mbtiles to a tablet, a rugged laptop, or an embedded device, and the map works with no network. The format holds both raster tiles (imagery, hillshade, scanned maps) and vector tiles (geology, infrastructure), and it is produced by standard tools — tippecanoe for vector, GDAL for raster.
This guide explains the schema, how to build MBTiles for both data types, the row-flip gotcha that mirrors maps, size management, and when PMTiles is the better choice.
The schema: tiles in SQLite
MBTiles is deliberately small. At minimum a valid file has two tables. The tiles table stores the pyramid:
CREATE TABLE tiles (
zoom_level INTEGER,
tile_column INTEGER,
tile_row INTEGER,
tile_data BLOB
);
CREATE UNIQUE INDEX tile_index ON tiles (zoom_level, tile_column, tile_row);
Each row is one tile: its zoom, its column and row at that zoom, and the raw bytes (tile_data) — a PNG/JPEG/WebP for raster, or a gzip-compressed Mapbox Vector Tile (.pbf) for vector. The metadata table is a key/value store describing the set:
CREATE TABLE metadata (name TEXT, value TEXT);
-- typical keys: name, format, bounds, center, minzoom, maxzoom, attribution
-- vector tilesets add: json (the layer/field schema), and format=pbf
format tells a reader whether tiles are png, jpg, webp, or pbf; bounds is west,south,east,north in WGS84; minzoom/maxzoom define the pyramid depth. For vector MBTiles, the json metadata value carries the vector layer names and field types, which is what lets a styler know that a geology layer with a MapUnit field exists. Because the whole thing is SQLite, you can inspect it with any SQLite client: SELECT name, value FROM metadata; is the fastest way to sanity-check an unfamiliar file.
A practical consequence of the SQLite container: deduplication is easy. Many real datasets repeat identical tiles (solid ocean, empty desert), and tools can store one blob referenced many times, which is why a well-built MBTiles can be dramatically smaller than a raw tile directory.
Building vector MBTiles with tippecanoe
For vector geology or infrastructure data, tippecanoe cuts GeoJSON into vector tiles, simplifying per zoom so low zooms stay light and detail appears as you zoom in:
tippecanoe -o geology.mbtiles \
-Z6 -z14 \
--drop-densest-as-needed \
--coalesce-smallest-as-needed \
--simplification=4 \
-l geology \
-n "Regional geology" \
geology.geojson
-Z/-z set the min and max zoom; do not generate beyond the zoom the field team will actually use, because each extra zoom roughly quadruples the tile count. --drop-densest-as-needed keeps tiles under the size limit in dense areas, and -l names the vector layer so your MapLibre style can reference source-layer: 'geology'. The output is ready to drop into an offline app.
Building raster MBTiles with GDAL
For a basemap, satellite scene, scanned topographic sheet, or hillshade, GDAL writes MBTiles directly:
# Reproject to Web Mercator and pack into MBTiles, building overviews
gdalwarp -t_srs EPSG:3857 -r bilinear scan.tif scan_3857.tif
gdal_translate -of MBTILES scan_3857.tif basemap.mbtiles \
-co TILE_FORMAT=JPEG -co QUALITY=80
gdaladdo -r average basemap.mbtiles 2 4 8 16
A critical detail: web tiles assume EPSG:3857 (Web Mercator), so reproject first with gdalwarp -t_srs EPSG:3857. Skipping this is the most common reason a raster MBTiles renders in the wrong place or not at all. gdaladdo builds the lower-zoom overviews so the map is responsive when zoomed out. Use JPEG for photographic imagery and PNG where you need transparency (a hillshade overlay).
The TMS row-flip gotcha
The single most confusing thing about MBTiles is tile addressing. The spec stores tile_row in TMS order, where row 0 is at the bottom (south) of the map. Most web clients — MapLibre, Leaflet, Google's scheme — use XYZ order, where row 0 is at the top (north). A compliant reader converts between them:
tile_row_TMS = (2^zoom_level - 1) - y_XYZ
If you build or read MBTiles by hand and skip this conversion, the map appears vertically mirrored — coastlines look right at a glance but the north/south is flipped. Mature tools (tippecanoe, GDAL, MapLibre's MBTiles loaders) handle the flip for you; the hazard is only in custom code. When debugging an upside-down offline map, suspect the row scheme first.
Serving and consuming offline
On a device, an app reads tiles straight from the SQLite file: given a request for z/x/y in XYZ, it flips the row to TMS and runs SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?. For development you can serve MBTiles over HTTP with a small server (for example tileserver-gl or mbview) and point MapLibre at it. Vector tiles also need a style document and, for offline use, the fonts (glyphs) and any sprite atlas bundled locally — a frequently forgotten dependency that makes labels vanish in the field.
Size management
Offline storage is finite, so manage the pyramid:
- Clip to the area of operations. Generate tiles only for the project bounds (
--clip-bounding-boxin tippecanoe, or a cutline in GDAL) instead of a whole country. - Cap max zoom. Each zoom level multiplies tile count by ~4. If field navigation needs z16 but the geology layer is only meaningful to z13, tile each layer to its own sensible maximum.
- Choose the right tile format. JPEG at quality 75–85 is far smaller than PNG for imagery; use PNG/WebP only where transparency or crisp lines matter.
- Split or merge deliberately. Keep a heavy imagery basemap separate from a light vector overlay so a team can carry only what they need.
Common pitfalls and why they happen
- Wrong CRS for raster tiles. Web tiling assumes EPSG:3857; feeding a raster in EPSG:4326 or a UTM zone without reprojecting puts tiles in the wrong place. Reproject with
gdalwarp -t_srs EPSG:3857first. - Mirrored maps. Hand-rolled readers that ignore the TMS row flip render the map upside down. Apply the
2^z - 1 - yconversion. - Over-deep pyramids. Generating to z18 "to be safe" can multiply file size enormously for no field benefit. Match max zoom to actual use.
- Missing vector dependencies offline. Vector MBTiles render blank labels without bundled glyphs/sprites; package them with the tiles.
- Treating export success as completeness. A file that opens is not proof the bounds, zoom range, and format metadata are correct; check the
metadatatable.
Validation
SELECT name, value FROM metadata;returns correctformat,bounds,minzoom,maxzoom, and (for vector) ajsonlayer description.SELECT count(*), min(zoom_level), max(zoom_level) FROM tiles;matches the intended pyramid.- The map renders in the right location and is not vertically mirrored in your target client.
- File size fits the device budget; imagery uses an appropriate compressed format.
- For vector tiles, glyphs and sprites are available offline so labels and icons appear.
Bathyl perspective
We use MBTiles when a map has to leave the network — field campaigns, remote sites, devices that cannot assume connectivity. The discipline is the same as for any deliverable: tile only the area and zooms that are needed, record the bounds and format in metadata, and verify the file renders correctly before it ships to someone who cannot phone back from the field. When the data will instead live on cloud storage, PMTiles is often the cleaner choice; offline on a device, MBTiles remains the dependable workhorse.
Related reading
- Vector Tiles for Web Maps
- MapLibre for Geological Web Maps
- Why Your GIS Layers Do Not Line Up
- Spatial data products