N-Ways
Developer docsMaps SDK (web)

Maps SDK for the web

A MapLibre GL JS map with N-Ways’ style, tiles and fonts, your key on every request, and helpers for search, routes and static maps.

On this page
Note:

Version 1.0. Load it from https://nways.ng/sdk/v1/, the latest 1.x release, or pin https://nways.ng/sdk/v1.0.1/. It’s on npm: npm install @nways/maps. Within 1.x nothing is removed or changed in a way that breaks working code. The earlier 0.x stays at /sdk/v0/. Changelog

What it is

The N-Ways Maps SDK for the web is a small layer over MapLibre GL JS, the open-source map library. It gives you a MapLibre map with N-Ways’ hosted style, vector tiles and fonts, the OpenStreetMap credit, and your API key on every request, plus:

  • a client for the N-Ways APIs: geocoding, postcodes, autocomplete, places, routes, the route matrix, fares and traffic;
  • a search box that suggests addresses, postcodes and places as people type;
  • static map addresses for pictures in emails and receipts.

Everything MapLibre can do (markers, popups, GeoJSON layers, camera moves, events) works on the map it gives you.

Load it

In a web page, with nothing else

The standalone build includes MapLibre GL JS, so one import is enough:

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="https://nways.ng/sdk/v1/maplibre-gl.css" />
    <style>body { margin: 0 } #map { height: 100vh }</style>
  </head>
  <body>
    <div id="map"></div>
    <script type="module">
      import { createMap } from 'https://nways.ng/sdk/v1/nways-maps.standalone.js'
      const map = createMap({ container: 'map', key: 'YOUR_API_KEY', center: [7.4951, 9.0579], zoom: 12 })
    </script>
  </body>
</html>

With npm and a bundler

With Vite, webpack, Next.js and other bundlers, MapLibre GL JS 6 and pmtiles 4 are peer dependencies, so your app keeps one copy of each:

cURL
npm install @nways/maps maplibre-gl@6 pmtiles@4
JavaScript
import 'maplibre-gl/dist/maplibre-gl.css'
import { createMap } from '@nways/maps'

const map = createMap({ container: 'map', key: import.meta.env.VITE_NWAYS_KEY, center: [3.3792, 6.5244], zoom: 12 })

Without npm, import https://nways.ng/sdk/v1/nways-maps.js (it imports maplibre-gl and pmtiles by name, for an import map or a bundler), or copy its four files (nways-maps.js, style.js, services.js, autocomplete.js) into your project. Types are next to them (nways-maps.d.ts).

Create a map

createMap(options) returns a MapLibre Map. It takes MapLibre’s map options, plus:

OptionDefaultDescription
containerrequiredThe element, or its id, to draw the map in. Give it a height.
keyrequiredYour API key, restricted to your website.
theme'light''light' or 'dark'.
lang'en''fr' shows French names where there are any (Cotonou, Lomé, Porto-Novo), else local names; 'pt' (from 1.1) Portuguese names the same way.
style'hosted''hosted': N-Ways’ hosted style, kept up to date by us. 'bundled': the style built into the SDK, which never changes under you. Or your own style object or URL.
showErrorstrueWhen N-Ways refuses the key, a short note over the map says why (and the console says more). false leaves it to you.
centerLagos[longitude, latitude].
zoom110 (the world) to about 19 (a building). Tiles go to zoom 15 and are drawn larger beyond it.
maxBoundsWest AfricaThe map stays over Nigeria, Ghana, Benin, Togo, Gabon, Côte d’Ivoire, Liberia, Guinea, Sierra Leone, Guinea-Bissau, Senegal, Cameroon, Burkina Faso, Cabo Verde and The Gambia. Pass your own to narrow it.

Each createMap reads the hosted style once: that’s one map load for billing. Panning and zooming are free. If the hosted style can’t be had (a network problem), the map falls back to the built-in style by itself.

Hosted styles

The SDK uses https://nways.ng/maps/v1/styles/streets-light.json and streets-dark.json. You can use them without the SDK, in any MapLibre map: styleUrl() gives the address with your key.

JavaScript
import * as maplibregl from 'maplibre-gl'
import { Protocol } from 'pmtiles'
import { styleUrl, withKey } from '@nways/maps'

maplibregl.addProtocol('pmtiles', new Protocol().tile)
const key = 'YOUR_API_KEY'
new maplibregl.Map({
  container: 'map',
  style: styleUrl({ key, theme: 'dark', lang: 'fr' }),
  // Tiles and fonts need the key too.
  transformRequest: (url) => ({ url: withKey(url, key) }),
  center: [1.2228, 6.1319],
  zoom: 12,
})

Dark maps and French labels

JavaScript
import { createMap, setTheme } from '@nways/maps'

const map = createMap({ container: 'map', key: 'YOUR_API_KEY', lang: 'fr', center: [2.4183, 6.3654], zoom: 12 })

// Follow the phone's light or dark setting.
const dark = matchMedia('(prefers-color-scheme: dark)')
const follow = () => setTheme(map, dark.matches ? 'dark' : 'light')
dark.addEventListener('change', follow)
follow()

Markers and popups

The SDK exports MapLibre as maplibregl, so the standalone build needs no other import:

JavaScript
import { createMap, maplibregl } from 'https://nways.ng/sdk/v1/nways-maps.standalone.js'

const map = createMap({ container: 'map', key: 'YOUR_API_KEY', center: [3.3515, 6.6018], zoom: 15 })
new maplibregl.Marker({ color: '#0b7a3e' })
  .setLngLat([3.3515, 6.6018])
  .setPopup(new maplibregl.Popup().setText('Pickup: Allen Avenue roundabout'))
  .addTo(map)

Calling the APIs

createClient({ key }) returns an object with one method per API. Points may be { lat, lng } or [lng, lat]; answers are the APIs’ JSON.

MethodAPI
geocode(q, { near, country, limit })Geocoding
reverse(point)Reverse geocoding (null when nothing is known there)
postcode(code), ghanapost(code)Postcode lookups
autocomplete(q, { session, near })Autocomplete
validateAddress(address, { country })Address validation
placesNearby(point, { category, q, radius }), placesSearch(q), place(id, { fields })Places
route({ origin, destination, mode })Routes
matrix({ origins, destinations, mode })Route matrix
optimize({ stops, vehicles })Route optimisation
fares(from, to, { mode })Fares
traffic([west, south, east, north])Traffic
staticMapUrl({ center, zoom, size, markers, path })Static maps (an address for an <img>; nothing is fetched)

Errors throw an ApiError with status, reason and retryAfter. Errors

A route on the map

Route lines come as GeoJSON, ready for a map layer:

JavaScript
import { createClient, createMap } from '@nways/maps'

const key = 'YOUR_API_KEY'
const map = createMap({ container: 'map', key, center: [3.37, 6.53], zoom: 11 })
const nways = createClient({ key })

const { routes } = await nways.route({
  origin: { lat: 6.6018, lng: 3.3515 },
  destination: { lat: 6.4541, lng: 3.3947 },
  mode: 'okada',
})

map.on('load', () => {
  map.addSource('route', { type: 'geojson', data: { type: 'Feature', properties: {}, geometry: routes[0].geometry } })
  map.addLayer({ id: 'route', type: 'line', source: 'route', layout: { 'line-cap': 'round', 'line-join': 'round' }, paint: { 'line-color': '#1a73e8', 'line-width': 6 } })
})
Warning:

Calling Routes from a web page shows your key in the page. Restrict it to your website and to the APIs the page needs, or ask your own server for the route instead. Key best practices

attachAutocomplete(input, options) turns any <input> into a search box with N-Ways suggestions, keyboard support and screen-reader labels. One search, from the first letter to the pick, counts as one autocomplete session.

HTML
<label for="address">Delivery address</label>
<input id="address" placeholder="Street, postcode or place" />
<script type="module">
  import { attachAutocomplete, createMap } from 'https://nways.ng/sdk/v1/nways-maps.standalone.js'

  const key = 'YOUR_API_KEY'
  const map = createMap({ container: 'map', key })
  attachAutocomplete(document.getElementById('address'), {
    key,
    near: { lat: 6.5244, lng: 3.3792 },
    onSelect: (place) => map.flyTo({ center: place.location.coordinates, zoom: 17 }),
  })
</script>

Restyle it with CSS variables on the page: --nways-ac-bg, --nways-ac-fg, --nways-ac-border, --nways-ac-active, --nways-ac-muted.

Your own style

buildStyle({ key, theme, lang }) returns the SDK’s built-in style as a plain MapLibre style object. Change colours or add layers before you pass it to MapLibre yourself, or start from scratch with the Tiles API.

JavaScript
import { buildStyle, createMap } from '@nways/maps'

const style = buildStyle({ key: 'YOUR_API_KEY' })
style.layers.find((l) => l.id === 'water').paint['fill-color'] = '#5aa9d6'
createMap({ container: 'map', key: 'YOUR_API_KEY', style, center: [-0.187, 5.6037], zoom: 12 })

Attribution

The map’s corner shows N-Ways · © OpenStreetMap contributors · Protomaps. Keep it visible: OpenStreetMap’s licence requires the credit wherever its data is shown. You may make it compact (MapLibre’s ⓘ button), but don’t hide or cover it. If you pass your own style without the credit, the SDK adds it. Attribution rules

Android and iOS

For native apps, use the Android and iOS Maps SDKs: the same hosted styles on MapLibre Native, with your app’s identity sent for its key restriction.

Licences

@nways/maps is open source under the Apache License 2.0 (Apache-2.0), Copyright 2026 N-WAYS LIMITED; the package includes the LICENSE and NOTICE files. The licence covers the code only: N-Ways maps and data stay © OpenStreetMap contributors under the Open Database Licence (ODbL), with the credit shown wherever they appear.

The standalone build includes MapLibre GL JS (BSD 3-Clause), pmtiles (BSD 3-Clause) and fflate (MIT); their licences are in LICENSES.txt next to it.