Overview
unmap is a Canadian-first, privacy-first maps API. One key covers three services:
- Maps are the background map itself: map data served from the edge, plus ready-made MapLibre styles that draw it. Nine map styles, each in light and dark, with terrain and contours where the data exists.
- Geocoding is full-text search, type-ahead autocomplete, category search ("the nearest pharmacy"), and reverse lookup over Canadian addresses and places. Bilingual (English and French) by default, with official Indigenous place names where the source data carries them.
- Routing is point-to-point routes and travel-time areas over Canada's road network, for driving, trucking, cycling, and walking.
The design goal is a time to first Hello World under 60 seconds, with two code statements after install.
Map words, in one minute
If you have not wired up a maps API before, these terms cover most of what the rest of these pages assume.
- Tile. The map is cut into square pieces so the browser fetches only the ones on screen.
/tiles/11/375/685names one piece: zoom 11, column 375, row 685. You will rarely ask for one yourself, because MapLibre works out which pieces it needs. - Zoom. A whole number from 0, the whole world in a single tile, to 14, the most detailed level unmap serves. Each step doubles the detail. A city fits at about zoom 11.
- Vector tile. Our tiles carry data (a road, a park, a label) rather than a picture of a road. The browser draws them, which is why the map stays sharp on a high-density screen and why one tile can be drawn in nine different styles.
- Basemap. The map underneath whatever you put on top. Your markers, routes, and data are yours; the basemap is the background they sit on.
- Style. A JSON document that tells MapLibre how to draw the tiles: which colours, which fonts, what to show at which zoom. It is not CSS, and you do not write one. You ask for a style by name.
- Geocoding. Turning text into a coordinate, as in "Calgary Tower" into a longitude and latitude. Reverse geocoding goes the other way, from a coordinate to the nearest address or place.
- Isochrone. The area you can reach from a point within a time budget, returned as a polygon. Everywhere within a 15 minute drive, for example.
One rule catches almost everyone at least once: coordinates are [longitude, latitude], in that order, here and in GeoJSON. Longitude is the east and west number, and in Canada it is negative. Latitude is north and south. If your map lands in the Indian Ocean, the two are the wrong way round.
Set up the SDK
The @unmap/sdk package puts all three services behind one Stripe-style key (um_live_... in production, um_test_... for development). Construct it once:
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({ key: "um_live_..." });
await unmap.geocoder.search("Calgary Tower");
await unmap.router.route([-114.07, 51.05], [-113.99, 51.05]);Pass a container and you also get a rendered map:
const map = new Unmap({
key: "um_live_...",
container: "map",
center: [-114.07, 51.05],
});
map.map; // the underlying MapLibre GL map
map.geocoder; // search / autocomplete / nearby / reverse
map.router; // route / isochroneThe API surface
new Unmap(options) accepts:
| Option | Type | Notes |
|---|---|---|
key | string | Required. Your um_live_ or um_test_ key. |
gateway | string | Optional. Override the API base URL. Defaults to https://api.unmap.dev. |
container | string | HTMLElement | Optional. If set, a map is mounted here. |
style | string | A style (base, muted, outdoor, blueprint, blush, orchid, canopy, lagoon, tropic, sunset, bold, pastel) or a full style URL. Defaults to base. |
flavor | string | Optional. An older spelling of style, used only when style is absent or is not a style name. |
mode | string | Optional. light or dark. Overrides the mode half of a compound style name. |
lang | string | Optional. Label language, e.g. fr. Omit to follow the browser's Accept-Language. |
theme | string | Optional. A ut1. theme code from /create. Wins over style. |
center | [number, number] | Optional. [lng, lat]. |
zoom | number | Optional initial zoom. |
projection | 'globe' | 'mercator' | Optional. 'globe' draws the map as a sphere. Defaults to 'mercator', the flat map. |
controls | boolean | { position } | Optional. Zoom and compass buttons, on by default. false for a bare canvas; { position: 'bottom-left' } to move them. |
style picks how the map is drawn; center and zoom pick where it starts. mode, lang, and theme pick how it is drawn. style: 'base' with mode: 'dark' and center: [-73.5673, 45.5017] is a valid request. The full list of styles, and what mode, lang, and theme do on the gateway, is on Maps API.
The instance exposes:
.mapis the MapLibre GL map (only when acontainerwas given). Mapbox-GL-JS-compatible surface..geocoderissearch(q, opts?),autocomplete(q, opts?),nearby(category, opts),reverse(lng, lat, opts?). See the Geocoding API..routerisroute(from, to, opts?),isochrone(center, { minutes, mode? }). See the Routing API.
Every method returns a promise of the same JSON the gateway returns, so the API reference pages describe the SDK's return types too. A non-2xx response throws a GeocoderError or RouterError carrying the HTTP status.
What unmap documents, and what MapLibre documents
@unmap/maps is a thin wrapper. createMap() builds the style URL, wires your key into it, and hands back a MapLibre GL JS map. It does not wrap the map itself. So these pages document the gateway, the nine styles, and the three things the SDK adds: .map, .geocoder, and .router. Everything you do to a map afterwards is MapLibre's own API, and it is documented upstream at maplibre.org.
The split is worth knowing before you go looking here for a page that does not exist:
- Events:
map.on('click', …),'load','moveend','error'. MapLibre. - Sources and layers:
addSource(),addLayer(),setPaintProperty(). MapLibre, plus the Style Specification for what a layer may contain. - Camera:
flyTo(),fitBounds(),easeTo(). MapLibre. - Markers, popups, and controls:
Marker,Popup,ScaleControl. MapLibre. (NavigationControlis already on the map;controls: falsetakes it off.) - Styles, keys, quotas, and the endpoints behind all of it: here.
You do not need a second import to reach any of it. @unmap/maps re-exports the very copy of MapLibre your app installed (it takes maplibre-gl as a peer dependency rather than bundling its own), so import { maplibregl } from "@unmap/maps" gives you the same Marker and Popup constructors without a duplicate copy of MapLibre in your bundle.
Demo cities
Three launch cities, exported as DEMO_CITIES. They are starting viewports, not different maps, and every style works over every one of them.
calgaryfor Calgary, Albertamontrealfor Montréal, Québeciqaluitfor Iqaluit, Nunavut
Set the viewport with center and zoom, not with style. A city name in style is recognised and discarded (the SDK will not fetch /styles/montreal.json), so style: 'montreal' renders base from the default viewport rather than moving the camera. Tiles cover all of Canada, so once the map is up you can pan anywhere. A city is only where it starts.
Packages
@unmap/sdk is the one-line install. Every piece of it is also published on its own, for when you only need one service or want to keep a bundle small. None of them is required: the gateway is plain HTTPS and JSON, and Calling the API directly shows the same calls from curl, fetch, Python, and a stock MapLibre. If your page has no build step at all, the SDK also loads from a script tag; the Quickstart has it.
| Package | What it is |
|---|---|
@unmap/sdk | Unmap: map + geocoder + router behind one key. Re-exports everything below. |
@unmap/maps | createMap() and unmapStyle(): MapLibre GL wired to the gateway. Takes maplibre-gl as a peer dependency, so install it alongside. |
@unmap/geocoding | Geocoder: a typed client for /geocode/*. No map dependency, works in Node and the browser. |
@unmap/routing | Router: a typed client for /route and /isochrone. No map dependency. |
@unmap/core | styleUrl(), tileUrl(), and tile math. Zero dependencies. |
@unmap/themes | The ThemeDoc model, the ut1. codec, and the validator behind /create. Data only. |
@unmap/react | useUnmap(), useGeocoder(), useDirections() hooks over the SDK, plus UnmapProvider. SSR-safe. |
@unmap/next | A <Map> Client Component a Server Component page can render directly. Re-exports @unmap/react. |
@unmap/vue | useUnmap(), useGeocoder(), useDirections() composables over the SDK. SSR-safe. |
@unmap/nuxt | A Nuxt module that sets unmap: { key } in runtime config and auto-imports the Vue composables. |
@unmap/cli | unmap create scaffolds a project, unmap add copies registry components into one, unmap init verifies a key and writes a starter config. |
React users start with @unmap/react (hooks) or, on the Next.js App Router, @unmap/next (a drop-in <Map>). For richer, editable UI such as a search box, routing panel, and markers, the component registry installs shadcn-style components built on @unmap/maps that you own and can edit.
Why unmap
- Untracked. Keys are metered, people are not.
- Unlocked. Permissive caching, no expiry clock on what you cache.
- Unbundled. One key across maps, geocoding, and routing.
- North American infrastructure. Cloudflare's North American edge (ENAM/WNAM). We can't promise Canada-only residency on self-serve plans. Talk to us if you need a contractual guarantee.
Attribution and data sources
Contains information made available under the Open Government Licence - Canada from Natural Resources Canada (Canadian Geographical Names Database). Map and address data (c) OpenStreetMap contributors. The routing graph is built from the same OpenStreetMap data, under the Open Database License (ODbL). Terrain is derived from NRCan's Canadian Digital Elevation Model.
For account-data and cache disclosures, see the Privacy Policy and the Terms.
A note on Indigenous place names. unmap's geocoding data includes official Indigenous-language place names sourced from the Canadian Geographical Names Database, an open, government-maintained dataset. These are officialized names only. Community-held and traditional place names are a separate, consent-based effort that we have not undertaken: any future work in that direction would follow OCAP (Ownership, Control, Access, Possession) principles, in partnership with the communities concerned, not a data import. Nothing here should be read as community endorsement.
Ready to render something? Head to the Quickstart or the Examples. Before you ship it, Authentication covers key forms, allowed origins, and the browser-versus-server split. Coming from Google Maps, Mapbox, HERE, or Esri? The migration guides translate the calls you have. Wondering why this exists at all? That case is made, with receipts, at Why another maps API?