Skip to content

Offline Maps — .mfdmap

Load a downloaded country (or sub-region) into MapLibre without network access.

What is .mfdmap?

An encrypted PMTiles archive. AES-256-GCM, key derived from the license token issued at purchase. PMTiles natively supports byte-range reads, so the file works directly with MapLibre's PMTiles protocol handler.

Buy + download flow

┌────────────────────┐  POST /api/downloads/purchase   ┌──────┐
│ buyer (your app)   │ ──────────────────────────────▶ │ MFD  │
│                    │ ◀── { ok, downloadId,           │      │
│                    │       licenseToken (15-min) }   │      │
│                    │  GET /api/downloads/{token}     │      │
│                    │ ──────────────────────────────▶ │      │
│                    │ ◀── { ok, url, expiresAt }      │      │
│                    │  GET signed-R2-url              │ R2   │
│                    │ ──────────────────────────────▶ │      │
│                    │ ◀── stream of country.mfdmap    │      │
└──────┬─────────────┘                                 └──────┘

       ▼  store license token securely
   decrypt with libmfdmap → MapLibre

The license token is one-shot — once you redeem the URL, the token can't be redeemed again. Store the decrypted PMTiles or the raw .mfdmap + token, not just the token.

Web (MapLibre GL JS)

js
import maplibregl from 'maplibre-gl';
import { Protocol } from 'pmtiles';
import { mfdProtocol } from '@mapsfordevs/offline-web';

// register both protocols (mfdProtocol handles decryption + delegates to pmtiles)
maplibregl.addProtocol('mfdmap', mfdProtocol({ token: licenseToken }));

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.mapsfordevs.com/styles/standard.json',
  center: [28.04, -26.20],
  zoom: 11
});

// override the source URL to point at the local file
map.on('load', () => {
  map.getSource('mfd').setTiles([
    'mfdmap:///path/or/blob-url/za.mfdmap'
  ]);
});

In practice, hand the .mfdmap file to a URL.createObjectURL(blob) if it lives in a Blob, or fetch from local cache (Service Worker, OPFS, IndexedDB).

iOS (Swift)

swift
import MapLibre
import MapLibreMFDOffline   // helper module shipping with @mapsfordevs/offline-ios

let licenseToken = "..."
let mfdmap = URL(fileURLWithPath: "/path/to/za.mfdmap")

let proto = MFDOfflineProtocol(token: licenseToken)
MLNNetworkConfiguration.sharedManager.protocols = [proto]

let url = URL(string: "mfdmap://\(mfdmap.path)")!
mapView.styleURL = URL(string: "https://api.mapsfordevs.com/styles/standard.json")!
mapView.style?.sources["mfd"]?.setTiles(["mfdmap://\(mfdmap.path)"])

Android (Kotlin)

kotlin
import org.maplibre.android.maps.MapView
import com.mapsfordevs.offline.MFDOfflineProtocol

val proto = MFDOfflineProtocol(licenseToken)
MapLibre.registerProtocol("mfdmap", proto)

map.setStyle("https://api.mapsfordevs.com/styles/standard.json") { style ->
    style.getSourceAs<VectorSource>("mfd")
        ?.setTiles(arrayOf("mfdmap:///data/data/com.example.app/files/za.mfdmap"))
}

Storage size

RegionTypical .mfdmap size at z8-17
Small country (Luxembourg, Lesotho)50–150 MB
Mid (South Africa, UK)1.5–3 GB
Large (Brazil, India)4–8 GB
Country aggregate (US, Russia federal districts)8–25 GB

Plan storage accordingly. Most apps sell per-country, not per-continent.

Updates

Each .mfdmap is dated (_20260425). When we publish a refresh, we issue you a new download via the same purchase. Yearly licenses get all updates within the year; perpetual licenses get the version they bought (re-purchase to refresh).

Caching

You may cache the decrypted PMTiles bytes locally to avoid re-decrypting on each launch. Do not redistribute the decrypted file — it leaves the protection of the license.