Dropping Unused Attributes to Reduce Tile Size

Pass -y <name> once per property you want to keep, and Tippecanoe discards everything else — consistently cutting .pbf payload by 15–60% without touching geometry.

When to Use This

Use an explicit attribute allowlist (-y) when any of these conditions apply:

  • Your source dataset carries more than 8–10 properties but your MapLibre GL style expressions reference fewer than half of them.
  • Tiles at any zoom level regularly exceed 200 KB and geometry simplification alone has not brought them under the 500 KB hard limit.
  • Your CDN logs show that tile transfer size is the dominant cost — attribute bloat accounts for more egress bytes than geometry in feature-dense layers like building footprints or POI datasets.
  • You are caching in an MBTiles SQLite container and want to keep the database file small enough for edge-node replication.

Use -x (blocklist) instead when your dataset has 30+ attributes, you want to keep most of them, and only a small set of legacy or internal columns needs removal.

Why MVT Encoding Amplifies Attribute Bloat

MVT dictionary bloat from unused attributes Two side-by-side MVT layer diagrams. Left: 10 keys in the keys array, 10 value buckets in values array, labelled "Unfiltered — 10 source attributes". Right: 4 keys, 4 value buckets, labelled "Filtered — 4 kept attributes". An arrow labelled "-y flag" points from left to right. Unfiltered — 10 source attributes keys[] name highway osm_id created audit_f legacy src_crs tile_id surface maxspeed string_values[] — all 10 buckets populated Solid = used by style · Dashed = unused -y flag Filtered — 4 kept attributes keys[] name highway surface maxspeed string_values[] — 4 buckets only Smaller key table → smaller .pbf payload

Every MVT layer uses two parallel arrays — keys and values — defined by the Mapbox Vector Tile Specification. Each property key is stored once in the key table; features reference it via an integer index. Unused keys still consume key-table entries, and their string values still populate the string_values deduplication pool. In a dataset with 40 source columns but only 6 consumed by the renderer, the remaining 34 bloat both arrays across every tile in the pyramid. Implementing attribute filtering rules at encode time prevents this waste from propagating into the tile cache.

Specification Detail

Flag Form Behaviour Default
-y <name> --include=<name> Retain only the named attribute; all others are dropped. Repeat for each attribute to keep. All attributes kept
-x <name> --exclude=<name> Drop the named attribute; all others are kept. Repeat for each attribute to remove. Nothing dropped
(none) --exclude-all Drop every attribute; geometry only. Useful for heatmap or density layers.

Version requirement: Both flags are stable across all Tippecanoe v1.x and v2.x releases. Use tippecanoe --version to confirm. The --exclude-all shorthand was added in v1.32.

Flag precedence: If you pass both -y and -x in the same command, -y takes precedence — the explicit allowlist wins and -x entries are redundant. Use one strategy per invocation.

Layer scope: Flags apply globally to all input layers unless combined with --layer / --named-layer. To filter attributes per source layer, run separate Tippecanoe invocations and merge with tile-join.

Production Command

This command encodes an OpenStreetMap road extract, retaining only the four attributes consumed by the frontend style and enforcing the 500 KB tile limit via essential production flags:

bash
tippecanoe \
  --output=roads.mbtiles \
  --layer=roads \
  --minimum-zoom=4 \
  --maximum-zoom=14 \
  --drop-densest-as-needed \
  --extend-zooms-if-still-dropping \
  --no-simplification-of-shared-nodes \
  --force \
  -y name \
  -y highway \
  -y surface \
  -y maxspeed \
  osm_roads_4326.geojson

Verify the output before committing it to the tile cache:

bash
# Decode tile z=12 x=2153 y=1410 and list all property keys in the first feature
tippecanoe-decode roads.mbtiles 12 2153 1410 \
  | jq '[.features[0].properties | keys[]]'
# Expected: ["highway","maxspeed","name","surface"]

If any key outside the allowlist appears, a later pipeline step (e.g., tile-join merge) re-introduced it. Track the source with tippecanoe-decode --stats.

For blocklist mode when you want to keep most columns but remove a handful of internal tracking fields:

bash
tippecanoe \
  --output=buildings.mbtiles \
  --layer=buildings \
  --maximum-zoom=16 \
  --drop-densest-as-needed \
  --force \
  -x legacy_id \
  -x internal_audit_flag \
  -x created_at \
  -x last_modified_by \
  buildings_4326.geojson

Interaction Effects

With --drop-densest-as-needed: Attribute pruning reduces .pbf size independently of feature dropping. Running both together is additive — smaller payloads per feature, plus fewer features in dense tiles. At z10–z12 on urban datasets, combining them is the most reliable way to stay under 500 KB without touching geometry fidelity.

With geometry simplification algorithms: Geometry simplification cuts vertex count; attribute filtering cuts the value payload. On polyline layers (roads, rivers), simplification typically dominates tile size. On polygon layers with many short edges and rich metadata (parcels, land-use), attribute filtering often has a larger impact than simplification. Profile both before tuning.

With GeoParquet input processing: When streaming .geoparquet to Tippecanoe via stdin (using pyarrow column projection), pre-filtering columns in pyarrow before they reach Tippecanoe is faster and uses less pipe memory than relying solely on -y flags. Use both: pyarrow projection as a first pass, -y as a final safety net.

With tile-join: tile-join does not inherit -y/-x flags from the original encode step. If you merge multiple .mbtiles files, re-apply the allowlist with tile-join’s own --include flags or the merged output will carry all source attributes.

Performance Impact

The table below shows directional benchmarks from a 4.2 GB OSM road extract (11 attributes, ~18 million features):

Scenario Tile count Mean tile size (z12) Total MBTiles size
No filtering (all 11 attrs) 61,204 148 KB 4.1 GB
-y allowlist (4 attrs) 61,204 89 KB 2.5 GB
--exclude-all (geometry only) 61,204 61 KB 1.7 GB

A 4-attribute allowlist produced a 40% reduction in total MBTiles size without any geometry change. Tile fetch latency dropped proportionally: a 148 KB tile over a 10 Mbps connection takes ~120 ms; the 89 KB filtered tile takes ~71 ms — a 50 ms improvement per tile that compounds across a map viewport loading 20–30 tiles simultaneously.

Build time is unaffected; Tippecanoe skips dropped attributes early in the feature-reading pass.

Common Mistakes

1. Relying on -x when the schema is dynamic

If your source schema gains a new column between pipeline runs, a blocklist silently passes the new column through. Use an allowlist (-y) when schema stability is not guaranteed.

Symptom: Tile inspector shows an unexpected key (e.g., etl_batch_id) in production tiles after a source schema update.

Fix:

bash
# Switch from -x blocklist to -y allowlist
tippecanoe \
  --output=output.mbtiles \
  -y name -y highway -y surface \
  input.geojson

2. Forgetting that tile-join resets attribute filtering

After merging layers with tile-join, previously filtered attributes from one source can bleed into shared tiles if the other source carries them.

Symptom: jq '[.features[].properties | keys[]] | unique' on a merged tile shows columns from the wrong layer.

Fix:

bash
tile-join \
  --output=merged.mbtiles \
  --include=name \
  --include=highway \
  roads.mbtiles pois.mbtiles

3. Style has() guards omitted after dropping optional attributes

If a style layer previously referenced a property that is now dropped, MapLibre GL JS renders the expression as undefined, which silently coerces to falsy and suppresses styling rather than throwing an error.

Symptom: Features that should render with a colour or label rule appear unstyled; no console error.

Fix — wrap optional property references in has():

json
{
  "type": "symbol",
  "layout": {
    "text-field": ["case", ["has", "name"], ["get", "name"], ""]
  }
}

Or use coalesce to provide a fallback value in data-driven MapLibre style expressions.


Parent: Attribute Filtering Rules