# How GeoLibre's Custom MapLibre Protocol Handles Local MBTiles Files

> Discover how GeoLibre's custom MapLibre protocol efficiently handles local MBTiles files. Render SQLite databases directly with a Tauri Rust backend, no web server needed.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-05

---

**GeoLibre registers a custom `geolibre-mbtiles` protocol with MapLibre-GL that intercepts tile requests and forwards them to a Tauri Rust backend, enabling direct rendering of local SQLite MBTiles databases without requiring a web server.**

The **GeoLibre** desktop application leverages a **custom MapLibre protocol** to render local MBTiles files as standard map layers. By registering a protocol handler with MapLibre-GL's `addProtocol` API, GeoLibre transforms local SQLite-based MBTiles databases into virtual tile servers that work seamlessly with the library's existing raster and vector source types.

## Understanding the `geolibre-mbtiles` Protocol Scheme

The protocol uses a custom URL scheme `geolibre-mbtiles://` that embeds the absolute filesystem path and tile coordinates. When MapLibre-GL encounters this scheme in a source URL, it triggers the registered handler instead of making an HTTP request.

### Generating Tile URLs

The helper function `mbtilesTileUrl()` in [`apps/geolibre-desktop/src/lib/mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts) constructs these specialized URLs:

```ts
// apps/geolibre-desktop/src/lib/mbtiles.ts
export function mbtilesTileUrl(path: string): string {
  return `${MBTILES_PROTOCOL}://tile/{z}/{x}/{y}?path=${encodeURIComponent(path)}`;
}

```

This generates URLs like `geolibre-mbtiles://tile/{z}/{x}/{y}?path=%2FUsers%2F...%2Fdata.mbtiles`, encoding the filesystem path as a query parameter to support any OS path structure.

## Registering the Protocol with MapLibre-GL

During application startup, `registerMbtilesProtocol()` registers the handler using MapLibre-GL's `addProtocol` method. This is implemented in [`apps/geolibre-desktop/src/lib/mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts):

```ts
// apps/geolibre-desktop/src/lib/mbtiles.ts
export function registerMbtilesProtocol(): void {
  if (protocolRegistered) return;

  addProtocol(MBTILES_PROTOCOL, async (request) => {
    const params = parseMbtilesTileRequest(request);
    const bytes = await invoke<number[] | Uint8Array>("read_mbtiles_tile", params);
    const array = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
    return {
      data: array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength),
    };
  });
  protocolRegistered = true;
}

```

The handler extracts tile coordinates via `parseMbtilesTileRequest()` and invokes the Tauri command `read_mbtiles_tile` to fetch raw tile bytes from the SQLite database.

## Parsing Tile Requests

The `parseMbtilesTileRequest()` function validates incoming request URLs and extracts the **zoom level (z)**, **tile column (x)**, **tile row (y)**, and **file path**:

```ts
// apps/geolibre-desktop/src/lib/mbtiles.ts
function parseMbtilesTileRequest(request: RequestParameters) {
  const url = new URL(request.url);
  const parts = url.pathname.split("/").filter(Boolean);
  if (parts.length !== 3) throw new Error("Invalid MBTiles tile URL.");
  const path = url.searchParams.get("path");
  if (!path) throw new Error("Invalid MBTiles tile path.");
  return {
    path,
    z: parseTileCoordinate(parts[0], "z"),
    x: parseTileCoordinate(parts[1], "x"),
    y: parseTileCoordinate(parts[2], "y"),
  };
}

```

This ensures only well-formed requests reach the Rust backend, throwing descriptive errors for malformed URLs or missing path parameters.

## Backend Tile Retrieval in Rust

The actual tile data retrieval happens in the Tauri backend via the `read_mbtiles_tile` command defined in [`apps/geolibre-desktop/src-tauri/src/lib.rs`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs):

```rust
// apps/geolibre-desktop/src-tauri/src/lib.rs
#[tauri::command]
fn read_mbtiles_tile(path: String, z: u32, x: u32, y: u32) -> Result<Vec<u8>, String> {
    // Open the MBTiles SQLite file at `path`,
    // execute `SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?`,
    // and return the blob.
}

```

This command opens the specified MBTiles SQLite database, queries the `tiles` table using the provided coordinates, and returns the raw tile blob as a `Vec<u8>`. The JavaScript handler then converts these bytes into an `ArrayBuffer` suitable for MapLibre-GL's consumption.

## Accessing MBTiles Metadata

Beyond tiles, GeoLibre can query MBTiles metadata (name, format, bounds, min/max zoom) via the `readMbtilesMetadata` function:

```ts
// apps/geolibre-desktop/src/lib/mbtiles.ts
export async function readMbtilesMetadata(path: string): Promise<MbtilesMetadata> {
  if (!isTauri()) throw new Error("MBTiles files require GeoLibre Desktop.");
  return invoke<MbtilesMetadata>("read_mbtiles_metadata", { path });
}

```

This wrapper ensures the feature is only available in the desktop environment by checking `isTauri()` before invoking the backend command.

## Implementation Example

To use local MBTiles files in your GeoLibre desktop application:

1. **Generate the tile URL** for your local file:

```ts
import { mbtilesTileUrl, registerMbtilesProtocol } from './lib/mbtiles';

const mbtilesPath = "/Users/alice/data/country.mbtiles";
const tileUrl = mbtilesTileUrl(mbtilesPath);
// Result: "geolibre-mbtiles://tile/{z}/{x}/{y}?path=%2FUsers%2Falice%2Fdata%2Fcountry.mbtiles"

```

2. **Register the protocol** during app initialization:

```ts
registerMbtilesProtocol();

```

3. **Add as a MapLibre source**:

```ts
const source = {
  type: "raster",  // or "vector" for vector tiles
  tiles: [tileUrl],
  tileSize: 256,
};
map.addSource("local-mbtiles", source);

```

## Summary

- GeoLibre implements a **custom MapLibre protocol** named `geolibre-mbtiles` to handle local MBTiles files in the desktop environment.
- The protocol handler is registered via MapLibre-GL's `addProtocol()` in [`apps/geolibre-desktop/src/lib/mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts).
- Tile URLs embed the absolute filesystem path and coordinates in the format `geolibre-mbtiles://tile/{z}/{x}/{y}?path=...`.
- The Tauri backend command `read_mbtiles_tile` in [`apps/geolibre-desktop/src-tauri/src/lib.rs`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs) queries the SQLite database and returns raw tile bytes.
- This architecture allows MapLibre-GL to treat local MBTiles databases as native tile sources without requiring a local HTTP server.

## Frequently Asked Questions

### Why does the custom protocol only work in GeoLibre Desktop?

The `geolibre-mbtiles` protocol relies on Tauri's IPC `invoke` mechanism to call Rust commands that read the SQLite database. Since browser-based environments cannot access the local filesystem directly, the protocol checks `isTauri()` and throws an error if accessed outside the desktop application.

### How does the protocol handle different tile formats (raster vs. vector)?

The protocol is format-agnostic. The Rust backend returns raw bytes from the `tile_data` column regardless of content type. MapLibre-GL interprets these bytes based on the source `type` property you specify (`"raster"` or `"vector"`) when adding the source to the map.

### What happens if the MBTiles file path is invalid or the tile doesn't exist?

The `parseMbtilesTileRequest` function validates the URL structure and throws descriptive errors for malformed requests. If the file path is valid but the tile doesn't exist in the database, the Rust command returns an empty result or error, which the protocol handler translates into a failed request that MapLibre-GL handles gracefully by not rendering that specific tile.

### Can I use this protocol with multiple MBTiles files simultaneously?

Yes. You can register multiple sources using different `mbtilesTileUrl()` calls with distinct file paths. Each source ID in MapLibre-GL can point to a different `geolibre-mbtiles://` URL with its own path parameter, allowing you to overlay multiple local tile layers on the same map.