Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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

js
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:

js
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:

js
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

ts
addProtocol(
  customProtocol: string,
  loadFn: (
    requestParameters: RequestParameters,
    abortController: AbortController
  ) => Promise<GetResourceResponse<any>>
): void

Receives:

  • requestParameters.url — the full URL from the source (including the custom scheme), plus optional headers, method, body, type ('string' | 'json' | 'arrayBuffer' | 'image'), credentials, and cache.
  • abortController — an AbortController you 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

js
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.