Using tippecanoe-decode to Inspect Build Output
tippecanoe-decode archive.mbtiles 14 8188 5449 prints one tile as GeoJSON with real coordinates restored. It is the shortest path from “the build succeeded” to “the build produced what I asked for”, and it answers four questions no build log can: which layers exist, which attributes survived, how many features are in a tile, and whether a zoom level contains anything at all.
When to Use This
Immediately after any change to a build’s flags, and whenever a map renders wrong and you need to know whether the fault is in the tiles or the style. It is also the check a CI gate should run on a sampled tile, because a structurally valid archive full of empty tiles passes every other test.
Specification Detail
| Invocation | What it prints |
|---|---|
tippecanoe-decode file.mbtiles |
Every tile in the archive as one GeoJSON stream |
tippecanoe-decode file.mbtiles Z X Y |
One tile, coordinates restored to EPSG:4326 |
tippecanoe-decode -c file.mbtiles |
Coordinates in tile-local units instead of degrees |
tippecanoe-decode -s EPSG:3857 … |
Coordinates in the given projection |
tippecanoe-decode -f … |
Continue past a corrupt tile rather than stopping |
It reads .mbtiles and .pmtiles alike. Without a z x y it decodes the whole archive, which for anything but a tiny tileset is not what you want — always pass a tile address.
Production Command
Pick the largest tile at a mid zoom — it exercises the most layers and the widest attribute coverage — then read the four things that matter:
# 1. Find a tile worth looking at
sqlite3 build/basemap.mbtiles \
"SELECT zoom_level, tile_column, tile_row, length(tile_data)
FROM tiles WHERE zoom_level = 12
ORDER BY length(tile_data) DESC LIMIT 1;"
# 12|2048|1362|318114
# MBTiles rows are TMS; tippecanoe-decode wants XYZ y = 2^z - 1 - row
Y=$(( (1 << 12) - 1 - 1362 ))
# 2. Layer names actually published
tippecanoe-decode build/basemap.mbtiles 12 2048 "$Y" \
| jq -r '.features[].properties["tippecanoe:layer"] // empty' | sort -u
# 3. Attributes that survived filtering
tippecanoe-decode build/basemap.mbtiles 12 2048 "$Y" \
| jq -r '[.features[].properties | keys[]] | unique | .[]'
# 4. Feature count per layer
tippecanoe-decode build/basemap.mbtiles 12 2048 "$Y" \
| jq -r '.features[].properties["tippecanoe:layer"]' | sort | uniq -c
The TMS conversion on the second line is the step people skip, and it decodes a tile from the wrong hemisphere without complaining — the same y-axis flip that mirrors maps vertically elsewhere.
Wrapped as a gate that fails a build rather than a reader:
#!/usr/bin/env bash
set -euo pipefail
ARCHIVE=$1; Z=$2; X=$3; Y=$4
EXPECTED_LAYERS=$(sort layers.manifest)
DECODED=$(tippecanoe-decode "$ARCHIVE" "$Z" "$X" "$Y")
ACTUAL=$(jq -r '.features[].properties["tippecanoe:layer"]' <<<"$DECODED" | sort -u)
COUNT=$(jq '.features | length' <<<"$DECODED")
diff <(echo "$EXPECTED_LAYERS") <(echo "$ACTUAL") \
|| { echo "layer set differs from the manifest"; exit 1; }
[ "$COUNT" -gt 0 ] || { echo "sampled tile is empty"; exit 1; }
echo "ok: $COUNT features across $(wc -l <<<"$ACTUAL") layers"
Reading the Output
Two synthetic properties appear in the decoded GeoJSON and are worth knowing about. tippecanoe:layer carries the layer the feature came from, which is how a single stream of features stays attributable. tippecanoe:extent reports the tile’s coordinate grid, which confirms whether --detail did what you expected.
Coordinates are restored to EPSG:4326 by default, which makes them directly comparable with the source data — a quick jq on one feature’s first coordinate against the same feature in the source is the fastest projection check available.
Values slightly outside the tile’s geographic bounds are correct: they are the buffer strip carrying neighbouring geometry, and a decoder that flags them as errors is wrong.
Interaction Effects
With attribute filtering. The decoded property keys are the definitive answer to what --include and -x actually did. Reading the flags is not the same thing, because an -x naming an attribute that does not exist is silently ignored.
With dropping strategies. Comparing feature counts between a tile built with and without --drop-densest-as-needed quantifies what the strategy removed, which the build log reports only as a warning count.
With the style contract. The layer names and attribute keys from a decode are exactly what a style’s source-layer and get expressions must match — the same schema a style validation workflow checks against.
With PMTiles. tippecanoe-decode reads PMTiles directly, so the same command works after conversion. Useful for confirming that a conversion preserved everything.
Performance Impact
Decoding one tile is milliseconds and reads only that tile’s bytes, so it is safe to run against a large archive and cheap enough for every CI run. Decoding a whole archive is a different proposition: it materialises every feature as JSON, which for a 4-million-tile basemap is hundreds of gigabytes of output and hours of work. Always pass a tile address.
For a broader sample without the cost, decode a handful of tiles chosen by size across several zooms — five tiles catch nearly everything a full decode would.
Common Mistakes
Forgetting the TMS conversion. Decodes a tile from the mirrored latitude and produces confusing but internally consistent output.
Decoding a whole archive by accident. Omitting z x y starts a full decode, which on a large tileset fills a disk.
Sampling an empty tile. A tile at the edge of the extent may genuinely hold nothing. Pick the largest tile at the zoom, not an arbitrary one.
Treating buffer coordinates as errors. Values just outside the tile’s bounds are the buffer working correctly.
FAQ
Does it work on PMTiles?
Yes, with the same syntax. The tile addressing is XYZ in both cases, so no TMS conversion is needed when reading a PMTiles archive directly.
Why do some features have no tippecanoe:layer?
Because it is only emitted when decoding an archive containing several layers. A single-layer tileset omits it, and the layer name is in the archive metadata instead.
Can I decode a tile fetched over HTTP?
Not directly, but pmtiles tile will fetch one from a remote archive and pipe it to a decoder. For a z/x/y tile server, curl the tile and decode with a library — see decoding MVT tiles in Python.
Is the output valid GeoJSON?
It is a FeatureCollection per tile, with the synthetic tippecanoe: properties added. Strip those and it feeds any GeoJSON consumer directly.
Related
- Tippecanoe CLI Fundamentals — the parent topic and the flags this verifies.
- Decoding MVT Tiles to GeoJSON in Python — the library route, for tiles fetched over HTTP.
- Attribute Filtering Rules — the flags whose real effect this command reveals.
- Tile Metadata & TileJSON — the archive-level description that should agree with what a decode finds.