Converting MBTiles to PMTiles with pmtiles convert
pmtiles convert basemap.mbtiles basemap.pmtiles reorders every tile onto a Hilbert curve, deduplicates identical tiles by content hash, copies the metadata, and writes a single archive that object storage can serve with range requests. It is lossless with respect to tile bytes and it is not lossless with respect to everything else — the details below are the ones worth knowing before publishing the output.
When to Use This
Convert when a tileset that already builds as MBTiles needs to be served without a tile server. The common cases are moving a working pipeline onto object storage, publishing a snapshot of a database-backed tileset, or distributing a tileset to consumers who should not need infrastructure to read it.
Do not convert as a routine build step if Tippecanoe can write PMTiles directly — it can, since 2.17, with --output basemap.pmtiles — because a direct write skips an entire read-and-rewrite pass over the whole archive.
Specification Detail
| Aspect | MBTiles input | PMTiles output |
|---|---|---|
| Tile order on disk | Insertion order | Hilbert curve order |
| Duplicate tiles | Stored per row unless the images/map schema is used | Deduplicated by content hash, always |
| Metadata | metadata table, json row |
Metadata section, offset in the header |
| Tile compression | Whatever the rows hold | Preserved, recorded in the header |
| Row addressing | TMS (tile_row counts north) |
Normalised to XYZ internally |
Two of these deserve attention. Deduplication is automatic and often dramatic: a tileset with large uniform areas — ocean, empty land, a single-colour landcover fill — can shrink by half, because every identical tile becomes one byte range referenced many times. And the TMS-to-XYZ normalisation means the conversion silently fixes an axis convention that MBTiles readers had to handle themselves.
Production Command
# Install the single Go binary
curl -sL https://github.com/protomaps/go-pmtiles/releases/latest/download/go-pmtiles_Linux_x86_64.tar.gz \
| tar xz -C /usr/local/bin pmtiles
# Convert, then verify before anything is uploaded
pmtiles convert basemap.mbtiles basemap.pmtiles
pmtiles verify basemap.pmtiles
# Compare what the two archives claim about themselves
sqlite3 basemap.mbtiles \
"SELECT name, value FROM metadata WHERE name IN ('minzoom','maxzoom','format');"
pmtiles show basemap.pmtiles | jq '{min_zoom, max_zoom, tile_type, tile_compression, addressed_tiles_count, tile_contents_count}'
addressed_tiles_count against tile_contents_count is the deduplication ratio, and it is the single most informative number the conversion produces. A basemap covering ocean might report 4.2 million addressed tiles and 1.6 million distinct contents — a 62% reduction that costs nothing.
Then upload with the headers range delivery needs:
aws s3 cp basemap.pmtiles s3://tiles-bucket/v43/basemap.pmtiles \
--content-type "application/octet-stream" \
--cache-control "public, max-age=31536000, immutable"
# Confirm the object answers ranges before pointing a style at it
curl -sI -r 0-99 "https://tiles.example.com/v43/basemap.pmtiles" | head -1
# HTTP/2 206
Interaction Effects
With metadata. The converter copies what it finds. An MBTiles archive with a missing vector_layers produces a PMTiles archive with the same gap, and patching it after conversion is more awkward than fixing the source. Check before converting.
With versioned publishing. The output is one object, so it slots directly into a versioned prefix scheme. It also means a single-tile fix is impossible — the archive is rewritten and republished as a whole.
With tile-join. Merge first, convert last. Converting each input and merging afterwards is not possible; tile-join reads MBTiles, so the conversion belongs at the end of the layer composition chain.
Performance Impact
The conversion is a full read and a full write, so it is bounded by disk throughput and the sort. Measured on an NVMe workstation:
| Tileset | Tiles | Convert time | Peak RAM |
|---|---|---|---|
| City extract | 0.4 M | 24 s | 380 MB |
| Country basemap | 4 M | 4 m 10 s | 1.1 GB |
| Global basemap | 40 M | 46 m | 3.4 GB |
Memory is dominated by the tile-id-to-offset table, which is roughly proportional to the deduplicated tile count. Disk headroom matters more than RAM: the conversion needs room for both archives at once.
Common Mistakes
Converting on every build. If Tippecanoe can write PMTiles directly, the conversion pass is pure overhead — a full read and write of the whole tileset for no change in content.
Uploading with the wrong content type. application/octet-stream is correct. A type the CDN considers compressible invites a body transform that invalidates every offset in the directory.
Skipping pmtiles verify. It reads the directory and confirms every entry resolves, which catches a truncated write before it becomes a 416 in production.
Deleting the MBTiles immediately. It is the only artefact that can be edited tile by tile. Keep it until the converted archive has been serving successfully.
FAQ
Is the conversion lossless?
For tile bytes, yes — each tile’s blob is copied unchanged, and deduplication only removes exact duplicates. Tile ordering and the internal addressing change completely, and anything that depended on MBTiles’ SQLite structure no longer applies.
Can I convert back?
Yes, pmtiles convert accepts a .pmtiles input and writes .mbtiles. The round trip preserves tiles and metadata, but the deduplication is undone — every addressed tile becomes its own row again, so the output is the original size.
Does converting change tile content?
No. It does not re-encode, re-compress or re-simplify anything. If a tile is over budget in the MBTiles it is over budget in the PMTiles, and the fix belongs in the tile build.
What happens to a tileset with mixed compression?
The header records one compression for the whole archive, so mixed inputs are a problem. Normalise before converting — an archive with some gzipped and some raw tiles produces an output that no client can read consistently.
Related
- PMTiles Specification Deep Dive — the parent topic: the archive layout this conversion produces.
- PMTiles vs MBTiles for CDN Delivery — whether the conversion is worth making at all.
- Configuring R2 and S3 for PMTiles Range Requests — where the output goes next.
- How to Inspect PMTiles Metadata with CLI Tools — verifying the result.