A custom protocol for merging geometry with attributes from multiple tilesets in MapLibre.
The DEMO combines a geometry tileset with two attribute tilesets. One of them contains an administrative code, which is used to control opacity, while the other contains properties for population and area, which are combined to calculate and color the population density.
You may want to open the browser's developer tools to inspect the Network tab to see the individual tiles being loaded, or the Console tab to observe the durations for merging the tilesets.
For best performance, the protocol uses self.addProtocol in the worker thread and runs there.
To use the maplibre-merge-protocol, you need to import the script in the workers and define your vector tile source with the custom merge:// protocol.
maplibregl.importScriptInWorkers(`./addMergeProtocol.js`);Note: importScriptInWorkers is considered experimental and can break at any point.
To merge Vector Tiles, start with the custom protocol merge://, then concatenate
the tile URLs using | as a separator.
"merge://http://geom.pbf|http://attr1.pbf|http://attr2.pbf"The first tileset must contain geometry (and may also include attributes).
All subsequent tilesets should contain attributes only; any geometry is ignored.
Example:
const style = {
sources: {
merged_tiles: {
type: "vector",
tiles:
["merge://" +
[
'https://tiles/geometry/{z}/{x}/{y}.pbf', // geometry tiles
'https://tiles/attributes1/{z}/{x}/{y}.pbf', // attribute tiles
'https://tiles/attributes2/{z}/{x}/{y}.pbf' // additional attribute tiles
].join('|')
]
}
},
layers: [
// use the source in layers like normal
]
};In the example folder of this repository, you’ll find code that prepares the split tilesets for the DEMO.
You can try it with the npm script example that runs this code and starts a server to display the output.
Separating geometry from attributes and merging them on the client is especially useful when you have complex, static geometry alongside a large or dynamic set of attributes. This approach lets you combine a "bare" geometry tileset with one or more "naked" attribute tilesets directly in the client. This can reduce download size, especially when the geometry tiles are cached between reuse.
A typical example is timeseries data on administrative divisions or on discrete global grids like uber/h3. If you only need to display a single date or compare two dates, including all attributes for every available date can make tiles unnecessarily large. Creating a separate tileset for each date duplicates the geometry, forcing the client to download it multiple times when switching between dates. Pre-generating all possible combinations for a comparison of two dates quickly leads to an explosion in the number of tilesets.
In this case, you would typically use something like the Martin and its PostgreSQL Function Sources to generate tiles on the fly based on query parameters. However, this approach would still result in identical geometry being downloaded multiple times, and it requires hosting and maintaining a dedicated server and database.
The maplibre-merge-protocol addresses this issue by simply allowing you to maintain a single geometry tileset and merge it on the client with one or more attribute tilesets. This enables efficient combination of specific attribute subsets with static geometry. These tilesets can be hosted as simple static assets, without any special infrastructure, as demonstrated in the demo.
The example/demo uses the following data sources:
© BKG (2026), dl-de/by-2-0 (Daten verändert), Datenquellen: https://sgx.geodatenzentrum.de/web_public/gdz/datenquellen/datenquellen_vg_nuts.pdf
MIT © 2026 Stefan Keim