Custom Protocols
addProtocol lets you hook a custom URL scheme (pmtiles://, custom://, anything before ://) so that any source in your style referencing that scheme is routed through your own loader function instead of a normal HTTP fetch. This is how PMTiles support is wired in: no scheme-specific code lives in the map itself, a protocol handler supplies the bytes.
Registering a protocol
mapmetricsgl.addProtocol('custom', async (params, abortController) => {
const response = await fetch(`https://${params.url.split('://')[1]}`);
if (response.status === 200) {
const buffer = await response.arrayBuffer();
return { data: buffer };
}
throw new Error(`Tile fetch error: ${response.statusText}`);
});Use it in a style source with the matching scheme:
map.addSource('custom-source', {
type: 'vector',
tiles: ['custom://example.com/tiles/{z}/{x}/{y}.pbf'],
});PMTiles example
The typical use case is serving a single .pmtiles archive without a tile server. A PMTiles-aware loader receives the request, resolves it against the archive, and returns the tile bytes:
import { PMTiles, Protocol } from 'pmtiles';
const protocol = new Protocol();
mapmetricsgl.addProtocol('pmtiles', protocol.tile);
const p = new PMTiles('https://example.com/data.pmtiles');
protocol.add(p);
map.addSource('pmtiles-source', {
type: 'vector',
url: 'pmtiles://https://example.com/data.pmtiles',
});The callback contract
addProtocol(
customProtocol: string,
loadFn: (
requestParameters: RequestParameters,
abortController: AbortController
) => Promise<GetResourceResponse<any>>
): voidReceives:
requestParameters.url— the full URL from the source (including the custom scheme), plus optionalheaders,method,body,type('string' | 'json' | 'arrayBuffer' | 'image'),credentials, andcache.abortController— anAbortControlleryou should respect if the request is cancelled (e.g. the tile scrolls out of view before it finishes loading).
Must return a Promise resolving to { data, cacheControl?, expires? } — data is the resource itself (an ArrayBuffer for vector/raster tiles, parsed JSON for a style/TileJSON, etc.), and cacheControl/expires are optional cache metadata.
On failure, reject the promise or throw — the caller (addProtocol's built-in error path) treats a thrown error as a failed resource load, the same as an HTTP error status would be.
Removing a protocol
mapmetricsgl.removeProtocol('custom');Advanced: custom source types
addSourceType(name, SourceClass) goes a step further than addProtocol — instead of hooking a URL scheme, it registers an entirely custom Source implementation under a new source type. It exists in the SDK, but it's a low-level extension point (you implement the full Source interface yourself); for anything that's really "fetch bytes for this URL scheme", addProtocol is what you want.