# How the Custom MapLibre Protocol for Local MBTiles Interacts with Tauri Commands in GeoLibre

> Discover how GeoLibre uses a custom MapLibre protocol to securely access local MBTiles via Tauri commands, enabling sandboxed SQLite database interactions.

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

---

**GeoLibre registers a custom `geolibre-mbtiles://` protocol handler that intercepts MapLibre tile requests and forwards them to Rust-backed Tauri commands, enabling secure, sandboxed access to local SQLite MBTiles databases.**

The [GeoLibre](https://github.com/opengeos/GeoLibre) desktop application solves a critical problem for offline mapping: rendering local MBTiles files in a MapLibre-GL JS frontend without direct filesystem access. By bridging MapLibre's protocol system with Tauri's command interface, the architecture keeps all file I/O in the trusted Rust backend while the web UI remains sandboxed.

## Architecture Overview: Custom Protocol to Tauri Command Flow

The interaction follows a six-step pipeline from user selection to tile rendering:

1. **User selects MBTiles file** → UI triggers file dialog
2. **Metadata extraction** → Tauri command queries SQLite headers
3. **Protocol registration** → Custom handler installed in MapLibre
4. **Tile request interception** → URL parsed, parameters extracted
5. **Backend tile retrieval** → Rust command fetches binary blob
6. **MapLibre rendering** → Raw bytes decoded as raster or vector

This design ensures that browser security restrictions never block local file access—the desktop shell handles all database operations.

## UI Layer: Selecting Files and Initiating the Protocol

The process begins in [[`MbtilesSource.tsx`](https://github.com/opengeos/GeoLibre/blob/main/MbtilesSource.tsx)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/layout/add-data/sources/MbtilesSource.tsx), which renders the **Add Data** interface for local MBTiles layers.

The component orchestrates two critical operations before any tiles load:

```typescript
// Open native file picker with fallback
const selectedPath = await openLocalDataFileWithFallback({ 
  name: 'MBTiles',
  extensions: ['mbtiles'] 
});

// Fetch metadata to determine layer properties
const metadata = await invoke<MbtilesMetadata>('read_mbtiles_metadata', { 
  path: selectedPath 
});

```

The metadata response includes **name**, **format** (png, webp, pbf), **tile type** (raster/vector), **bounds**, **minzoom**, and **maxzoom**—all required to configure the MapLibre source correctly.

## Registering the Custom MapLibre Protocol

Once metadata confirms the file is valid, [[`mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/mbtiles.ts)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts) installs the protocol handler. This module defines three essential exports:

- **`registerMbtilesProtocol()`** — installs the handler via `maplibregl.addProtocol()`
- **`mbtilesTileUrl(path)`** — generates templated URLs for the source
- **`parseMbtilesTileRequest(request)`** — decodes incoming tile requests

The registration happens once per application lifecycle:

```typescript
import { registerMbtilesProtocol } from '@/lib/mbtiles';

// Called when first MBTiles layer is added
registerMbtilesProtocol();

```

The handler itself follows MapLibre's protocol signature, accepting a `Request` and returning a `Promise<Response>` with tile binary data.

## Protocol Handler: Bridging MapLibre Requests to Tauri

When MapLibre needs a tile, it constructs URLs using the template from `mbtilesTileUrl()`:

```typescript
// Generated URL format
const tileUrl = mbtilesTileUrl('/path/to/data.mbtiles');
// → "geolibre-mbtiles://tile/{z}/{x}/{y}?path=%2Fpath%2Fto%2Fdata.mbtiles"

```

The registered handler in [[`mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/mbtiles.ts)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts) (lines 36-43) processes these requests:

```typescript
// Inside registerMbtilesProtocol()
maplibregl.addProtocol('geolibre-mbtiles', async (request) => {
  const { path, z, x, y } = parseMbtilesTileRequest(request);
  
  // Bridge to Tauri backend
  const bytes = await invoke<number[] | Uint8Array>('read_mbtiles_tile', {
    path,
    z,
    x,
    y
  });
  
  return new Response(new Uint8Array(bytes));
});

```

**Key parsing logic** extracts coordinates and the encoded file path from the URL query string, then invokes the Rust command with strongly-typed parameters.

## Tauri Backend: Rust Commands for SQLite Access

The desktop side implements two primary commands in [[`src-tauri/src/lib.rs`](https://github.com/opengeos/GeoLibre/blob/main/src-tauri/src/lib.rs)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs):

### `read_mbtiles_metadata`

Queries the `metadata` table for layer configuration:

```rust
#[tauri::command]
fn read_mbtiles_metadata(path: String) -> Result<MbtilesMetadata, String> {
    let conn = open_mbtiles(&path)?;
    // Extract name, format, bounds, zoom levels...
}

```

### `read_mbtiles_tile`

The core tile retrieval command (lines 20-51) handles:

- **Database connection** — read-only SQLite access via `open_mbtiles()`
- **Coordinate scheme conversion** — `xyz` vs `tms` row calculation: `row = (2^z - 1) - y` for TMS
- **SQL query execution** — `SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?`
- **Optional decompression** — `decompress_tile_data()` for gzip-compressed tiles
- **Empty tile handling** — returns `Vec::new()` for missing tiles to prevent 404 errors

```rust
#[tauri::command]
fn read_mbtiles_tile(path: String, z: u8, x: u32, y: u32) -> Result<Vec<u8>, String> {
    let mut conn = open_mbtiles(&path)?;
    let scheme = detect_scheme(&conn)?; // "xyz" or "tms"
    let row = if scheme == "tms" { flip_y(z, y) } else { y };
    
    let tile_data: Option<Vec<u8>> = conn.prepare(
        "SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?"
    )?.query_row([z, x, row], |row| row.get(0)).ok();
    
    match tile_data {
        Some(data) => decompress_tile_data(&data),
        None => Ok(Vec::new()),
    }
}

```

All database operations use **parameterized queries** to prevent SQL injection, and the connection pool is managed per-request for thread safety.

## Integrating as a MapLibre Source

The UI completes the cycle by creating a MapLibre source using the custom protocol URL:

```typescript
// After metadata retrieval and protocol registration
map.addSource('local-mbtiles', {
  type: 'raster', // or 'vector' based on metadata.tileType
  tiles: [mbtilesTileUrl(selectedPath)],
  tileSize: 256,
  minzoom: metadata.minzoom,
  maxzoom: metadata.maxzoom,
  bounds: metadata.bounds
});

```

MapLibre's tile scheduler automatically requests `geolibre-mbtiles://` URLs as the user pans and zooms, triggering the protocol handler → Tauri command → SQLite query pipeline for each visible tile.

## Security and Performance Considerations

**Sandbox preservation** — The web frontend never receives filesystem paths in a usable form; paths are URL-encoded and processed only in Rust.

**Read-only access** — The `open_mbtiles()` helper explicitly opens connections with `OpenFlags::SQLITE_OPEN_READ_ONLY`.

**Efficient binary transfer** — Tauri's `invoke` serializes `Vec<u8>` directly to JavaScript `Uint8Array` without base64 encoding overhead.

**Missing tile tolerance** — Empty byte arrays prevent MapLibre from retrying failed requests, improving performance over sparse datasets.

## Key Files Reference

| Purpose | File Path | Link |
|:--------|:----------|:-----|
| UI component for MBTiles selection and layer creation | [`apps/geolibre-desktop/src/components/layout/add-data/sources/MbtilesSource.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/layout/add-data/sources/MbtilesSource.tsx) | [Source](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/layout/add-data/sources/MbtilesSource.tsx) |
| Custom protocol registration, URL builder, and request parser | [`apps/geolibre-desktop/src/lib/mbtiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts) | [Source](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/mbtiles.ts) |
| Tauri commands for metadata and tile retrieval | [`apps/geolibre-desktop/src-tauri/src/lib.rs`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs) | [Source](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs) |
| Tauri environment detection helper | [`apps/geolibre-desktop/src/lib/is-tauri.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/is-tauri.ts) | [Source](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/is-tauri.ts) |

## Summary

- **Custom MapLibre protocol** (`geolibre-mbtiles://`) intercepts tile requests in the frontend
- **Protocol handler** parses coordinates and file path, then calls `invoke('read_mbtiles_tile')`
- **Tauri Rust command** executes SQLite queries against the MBTiles database with proper coordinate scheme handling
- **Binary response** flows back through the protocol handler as a `Response` object for MapLibre to decode
- All filesystem access remains confined to the trusted Tauri backend, satisfying browser security models while enabling rich offline mapping

## Frequently Asked Questions

### How does GeoLibre handle TMS vs XYZ tile schemes?

The [`read_mbtiles_tile`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs) command detects the scheme from the MBTiles `metadata` table. For TMS, it flips the Y coordinate using `row = (2^z - 1) - y` before querying. XYZ coordinates pass through unchanged.

### Can multiple MBTiles files be open simultaneously?

Yes. Each tile request includes the full file path in the URL query string, and the Rust backend opens independent SQLite connections per request. No global connection state is maintained between tiles.

### What happens when a requested tile doesn't exist in the database?

The [`read_mbtiles_tile`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src-tauri/src/lib.rs) command returns an empty `Vec<u8>` instead of an error. This signals MapLibre to render transparent/background pixels rather than displaying broken tile indicators.