This is the documentation of version 0.62.2. Go to the latest version, 1.0.0-rc.5.

Map modules

Map Modules are self-contained extensions that add specialized functionality to a TomTomMap instance.

Each module provides focused capabilities for different mapping needs — from displaying traffic information to handling user-generated content — while following a consistent pattern for initialization, configuration, and lifecycle management that makes them easy to integrate and use together.

Key features

Types of modules

There are two types of modules in the TomTom Maps SDK, depending on who owns the sources and layers the module controls. Ownership decides both the factory you call and how many instances make sense on one map.

Style-owned modules — get(map, config?)

These modules do not display data of their own. They control sources and layers that the map style already provides — the base map, POI symbols, the traffic tile layers, the terrain elevation — under source and layer IDs that are fixed and global to the style.

A style-owned module is therefore a shared controller, not a piece of content. get() is a lookup, and quite literally so: the SDK keeps one instance per map, so calling it twice returns the same module. Two components can each ask for the terrain without coordinating, and neither has to hoist a shared reference.

Calling get(map) with no configuration is a plain accessor — it changes nothing. Calling it with a configuration applies that configuration to the instance that already exists.

To handle events for only part of what a style-owned module covers, scope the events rather than creating a second module — see Scoped events.

Data-owned modules — create(map, config?)

These modules bring their own data. They add the GeoJSON sources, layers and sprite images they need and own them for their whole lifetime, with every identifier suffixed per instance.

Instances are therefore fully independent: create() builds a new module every time, and stacking several of them on one map — one set of places per search, one route per plan — is a supported thing to do. Nothing is shared or reused between them.

Per-module reference

Module Kind Factory Instances per map Why
BaseMapModule Style-owned get() One per map One global vector-tile source. To act on part of the map, scope its events with events.where({ layerGroups }) rather than creating a second module.
POIsModule Style-owned get() One per map Fixed POI source and layer IDs; visibility, category filters and category icons are global to the map.
TrafficFlowModule Style-owned get() One per map Fixed traffic-flow source; visibility and filters are global to the map.
TrafficIncidentsModule Style-owned get() One per map Fixed traffic-incident source; visibility and filters are global to the map.
TerrainModule Style-owned get() One per map Fixed elevation source; hillshade visibility and the 3D surface are global to the map.
PlacesModule Data-owned create() Multiple Owns its own place source, layers and pin images, suffixed per instance.
RoutingModule Data-owned create() Multiple Owns its own route, waypoint and section sources and layers, suffixed per instance.
GeometriesModule Data-owned create() Multiple Owns its own geometry and label sources, suffixed per instance.
ReachableRangesModule Data-owned create() Multiple Owns its own range and label sources, suffixed per instance.
CustomGeoJSONModule Data-owned create() Multiple Owns the sources and layers declared in its own sources config, suffixed per instance.
TrafficIncidentDetailsModule Data-owned create() Multiple Owns the GeoJSON source and layers for the incidents you fetched yourself.
TrafficAreaAnalyticsModule Data-owned create() Multiple Owns the grid/heatmap source and layers it renders your analytics into.

Who owns what

Everything visible on the map has exactly one owner, and the owner follows from who owns the layers. Every module has knobs of its own: the style-owned modules decide whether the loaded style's layers show, the Styling module how the style draws them, and each data-owned instance everything about the layers it adds itself. The dashed lines are the only places a knob reaches past its module: the base-map group toggles write through BaseMapModule, and the data-owned modules read the accent as their default colour.

Style-owned modules: get(), one per mapStylingModule: get(), one per mapData-owned modules: create(), many per mapMapLibre mapBaseMapModule · POIsModuleTrafficFlowModule · TrafficIncidentsModuleTerrainModuleTheir knobs: what is shownvisible · filters · hillshade on/offIts knobs: how the style draws itsizes · widths · colours · projection · skycolors.accentPlacesModule · RoutingModule · GeometriesModuleReachableRangesModule · CustomGeoJSONModuleTrafficIncidentDetailsModule · TrafficAreaAnalyticsModuleEach instance's own knobscolor · label · width · markerType · …The loaded style's layersbase map · POIs · traffic tiles · hillshadeMap viewprojection · skySources and layers each instance adds(ids suffixed per instance) show, hide, filterrestyleview.* knobs basemap.* toggles write through BaseMapModuledefault colour, readdraw and style

In detail:

You want to change… Owner How
Whether something the loaded style draws is shown — a base-map layer group, the traffic tiles, a POI category, the hillshade, the 3D surface The style-owned module for that part setVisible, updateConfig({ filters }), setHillshadeVisible, setElevationEnabled
How the style draws it — label and icon sizes, road widths, the ten map colours, traffic colours, hillshade lighting, projection and sky The Styling module set(knob, value), applyPreset, setMapColors
Anything a data-owned module draws itself — routes, places, geometries, your GeoJSON, fetched traffic incidents, area analytics That module, through its own configuration create(map, config), applyConfig / updateConfig, setKnob and setVisible
The map's colour identity — the accent routes, places and geometries default to The Styling module publishes it; each data-owned module reads it setMapColors, set('colors.accent', …); a colour set on the module always wins
A look a data-owned module borrows from the style, such as POI-looking places or the colours of fetched traffic incidents The Styling module sets it on the style; the module reads it and redraws its own layers The knobs that restyle what it borrows; a setting given to the module always wins

Two consequences:

Each module guide opens with a What this module owns line that applies this rule to that module.

Changing a setting

Every setting changes through three calls each module has: applyConfig(config) replaces the whole configuration, updateConfig(partial) changes the top-level properties it is given, and setKnob sets one setting by its catalogue id.

Saving and replaying a configuration

getConfig() is the export and applyConfig() the import: a module's configuration that went through JSON, given back to applyConfig, holds the same configuration and draws the same layers. Keep one to restore a user's map, attach it to a support report, or replay it in a test.

localStorage.setItem('places', JSON.stringify(placesModule.getConfig()));

// Later, on the same module or a new one
placesModule.applyConfig(JSON.parse(localStorage.getItem('places')));

Most modules' configurations are JSON whole, styling's knobs included. Five modules also take functions or image elements, which JSON cannot carry. Each has a type for the part that survives, which its reference page details; a replay adds the rest back in code:

Module Serializable part Left out
PlacesModule PlacesModuleSerializableConfig label.title as a function, connections.label, evAvailability.formatText, icon.mapping, extraFeatureProps functions, image elements in icon
RoutingModule RoutingModuleSerializableConfig a custom chargingStops.icon.mapping, waypoints.icon.mapping, waypoints.label.title as a function, image elements in chargingStops.icon and waypoints.icon
GeometriesModule GeometriesModuleSerializableConfig transformFeaturesForDisplay
ReachableRangesModule ReachableRangesModuleSerializableConfig title
CustomGeoJSONModule CustomGeoJSONModuleSerializableConfig images

JSON.stringify drops a function without a trace, but turns an image element into {}, which does not replay. Give icon images as URLs, data URIs or SVG text, and a place or waypoint title as a MapLibre expression, to keep the whole configuration serializable. POIsModule's icon.categoryIcons takes image elements too, with no type to leave them out.

const state = JSON.parse(saved); // PlacesModuleSerializableConfig
placesModule.applyConfig({ ...state, connections: { ...state.connections, label: (connection) => connection.name } });

The configuration is what a module draws with, not the data it shows: replay show() with the data too.

Appearance vocabulary

A concept has one name on every module that has it. The table lists the shared appearance terms, the rule each follows, and the exact properties that carry it; a module missing from a row does not have that concept.

Concept The rule Modules and properties
The module's colour A top-level color is the colour of what the module draws. Unset, it is the map's accent (colors.accent, published as map.mapColors.accent), then the module's own default. A part-level colour overrides it. RoutingModule: color, under layers.* paint overrides · GeometriesModule: color, under fill.palette, fill.color, line.color and a feature's color property (order) · PlacesModule: color, under icon.default.style.fillColor, label.color and layers.*
Labels One label shape, LabelConfig: size, color, haloColor, haloWidth, opacity, font. Each module adds the label's content and its own offset. PlacesModule: label (PlaceLabelConfig, adds title, and offset as one number of ems away from the icon) · GeometriesModule: label (GeometryLabelConfig, adds text, a string or expression that defaults to the feature's title), and lineLabel (GeometryLineLabelConfig, the same text plus minZoom, symbolSpacing, and offset as [x, y] ems) · RoutingModule: chargingStops.label (ChargingStopLabelConfig: visible and a title expression, without the LabelConfig styling)
Multipliers A *Factor multiplies a default and is clamped to a published range. Where a size also has presets ('s', 'm', 'l'), the preset picks the base and the factor multiplies it. RoutingModule: width and widthFactor (ROUTE_WIDTH_FACTOR_RANGE, 0.5 to 2), waypointSize and waypointSizeFactor (ROUTE_WAYPOINT_SIZE_FACTOR_RANGE, 0.5 to 2), sections.<type>.width and sections.<type>.widthFactor, sections.<type>.icon.sizeFactor (SECTION_ICON_SIZE_FACTOR_RANGE, 0.25 to 2), highlight.outlineWidthFactor (ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE, 1 to 1.25) · GeometriesModule: highlight.fillOpacityFactor (1 to 4) and highlight.lineWidthFactor (1 to 3) · TrafficIncidentDetailsModule: focus.widthFactor (INCIDENT_FOCUS_WIDTH_FACTOR_RANGE, 1 to 1.8) · Styling knobs: labels.sizeFactor, symbols.sizeFactor, pois.sizeFactor, roads.widthFactor, traffic.flow.widthFactor, traffic.incidents.widthFactor
Hovered and clicked look A feature a handler covers, or one putEventState puts a state on, draws one look whether hovered or clicked. highlight holds the factors that look multiplies by; a factor of 1 draws the feature as any other. A MapLibre layer you give in place of the default draws as given. RoutingModule: highlight.outlineWidthFactor · GeometriesModule: highlight.fillOpacityFactor, highlight.lineWidthFactor · TrafficIncidentDetailsModule: its focus config, the treatment setFocus draws, focus: false to turn it off · PlacesModule: layers.selected
Absolute units A value that mirrors a MapLibre property keeps MapLibre's name and is in pixels. Any other unit is in the name. Opacities run from 0 to 1, label offsets are in ems. Pixels: LabelConfig size and haloWidth, GeometriesModule line.width and lineLabel.symbolSpacing, PlacesModule cluster.badge.strokeWidth and cluster.badge.radius · Metres: TrafficAreaAnalyticsModule maxHeightMeters, minHeightMeters and, per unit of the metric, metersPerUnit · Two exaggerations: TerrainModule elevationExaggeration scales the raised surface, the hillshade.exaggeration knob sets the shading strength
Marker type, fill style, map theme Theme means only the map-wide look. How one module draws its own features has a name of its own. PlacesModule: markerType (PlacesMarkerType; placesMarkerTypes lists pin, circle-icon, base-map, pin-clustered) · GeometriesModule: fillStyle (GeometryFillStyle; geometryFillStyles lists filled, outline, inverted), per feature properties.fillStyle · ReachableRangesModule: fillStyle, the same values · Map theme: StylingModule.setMapColors, fed by the Map Theme plugin's deriveMapColors, deriveMapColorsFromCss, deriveMapColorsFromImage and mapColorsLightDark
Fill colour or palette A colour field takes a colour or a MapLibre expression, never a palette name. A palette is its own field: on geometries and reachable ranges a list of your own CSS colours, which the features or bands take one after another; on area analytics a name, ramped over the metric's value. GeometriesModule: fill.color, fill.palette, unset drawing one colour, the accent · ReachableRangesModule: palette, unset ramping from the accent toward the map's land colour · TrafficAreaAnalyticsModule: metricConfig.<metric>.palette (AreaAnalyticsPalette; areaAnalyticsPalettes lists the names), and metricConfig.<metric>.colorStops, which wins over it
Zoom thresholds minZoom and maxZoom, as on the styling knobs. PlacesModule: entryPoints.minZoom · GeometriesModule: lineLabel.minZoom · RoutingModule: sections.<type>.sign.minZoom, countryCrossings.minZoom · Styling knobs: pois.minZoom, hillshade.maxZoom
Borrowed looks A part drawn to look like the style follows the knobs that restyle what it borrows. A look set on the module wins. PlacesModule with markerType base-map, circle-icon or pin-clustered · RoutingModule sections.traffic · TrafficIncidentDetailsModule line colours. The knobs each one follows: Borrowed looks
Visibility visible in the config, setVisible(visible) to change it, isVisible() to read it back. isVisible() returns the setting, the same value as the module's visible knob. On a data-owned module, hiding keeps the data, show() and clear() leave it hidden, and getShown() returns what is shown. On a style-owned module that nothing has set, isVisible() reads what the style draws, and the traffic modules are hidden until shown. A part that hides on its own has its own visible. All three on PlacesModule, RoutingModule, GeometriesModule, CustomGeoJSONModule, TrafficIncidentDetailsModule, TrafficAreaAnalyticsModule, POIsModule, TrafficFlowModule and TrafficIncidentsModule (plus icons.visible with setIconsVisible / isIconsVisible) · BaseMapModule: visible, groups.<group>.visible, setVisible and isVisible with { layerGroups } · RoutingModule per part: chargingStops.visible, chargingStops.label.visible, summaryBubbles.visible, countryCrossings.visible, sections.<type>.visible · TerrainModule: hillshade with setHillshadeVisible / isHillshadeVisible, elevation with setElevationEnabled / isElevationEnabled
Layer position beforeLayerConfig takes 'top' or a named style layer such as 'lowestLabel' (BeforeLayerConfig) for all of a module's layers. A module drawing several parts also takes a record keyed by its part names, all covering the parts it leaves out (PartsBeforeLayerConfig). GeometriesModule: GeometryBeforeLayerConfig, parts fill, line · RoutingModule: RoutingBeforeLayerConfig, parts mainLines, instructionLines, icons · PlacesModule: PlacesBeforeLayerConfig, parts places, connections · TrafficAreaAnalyticsModule: AreaAnalyticsBeforeLayerConfig, one part per display mode · TrafficIncidentDetailsModule: BeforeLayerConfig alone · CustomGeoJSONModule: MapLibre's beforeID on each layer

Every setting as data

A module's display settings are also published as a knob catalogue: static data, readable with no map loaded, that a control panel builds its controls from and an agent checks a value against. An entry's id is the setting's path in the module's configuration, where a number indexes a list (filters.any.0), and its kind says which value it takes and which fields come with it. The entry type is KnobEntry, and the kinds are KnobKind:

Kind Value Fields
toggle true or false
factor a multiplier, 1 keeping what the style does range
number a number range
offset an [x, y] displacement, x to the right and y down, in the unit the description names range, for x and y each
color a CSS colour
enum one of the options options
enums a list of the options; the description says when their order matters options
colors a list of CSS colours or of the names in options options, where there are names
image the id of an image in the style's sprite
text any string

Setting a knob by its id

setKnob sets one setting by its catalogue id and keeps every other one; a value its entry refuses changes nothing. validateKnobValue is the check alone, for a value from a form or a language model. Both throw a RangeError naming the knob and what it expects.

The path is the same for every module, the Styling module's and the map effects plugin's own set included, and a knob only ever redraws what its own module draws:

One catalogue per modulestatic data, no map neededA valuefrom a control, saved settings or an agentSet one knob by idvalidateKnobValueagainst the entryRangeErrornothing changesThat module's configurationone setting changed, the rest keptWhat that module draws, and nothing elseevents: 'config-change'placesKnobCatalogueroutingKnobCataloguetrafficFlowKnobCatalogue…stylingKnobCatalogueeffectKnobCatalogue (map effects plugin)Each entry: id · kind · range or options · defaultkinds shared by every cataloguesetKnob(module, catalogue, id, value)styling.set(id, value)effects.set({ id: value }) the entry for the id invalidvalidredraw
import { setKnob, trafficFlowKnobCatalogue, validateKnobValue } from '@tomtom-org/maps-sdk/map';

setKnob(trafficFlow, trafficFlowKnobCatalogue, 'filters.any.0.roadCategories.values', ['motorway', 'trunk']);

const knob = trafficFlowKnobCatalogue.find(({ id }) => id === 'filters.any.0.roadCategories.values');
validateKnobValue(knob, ['motorway', 'cart track']); // RangeError: … 'cart track' is not

The Styling Module's own set runs the same check. The traffic config playground sets every flow and incidents knob this way, from controls built off the two catalogues.

List of modules

Base map module

A BaseMapModule provides visibility and events for layer groups of the base map.

import { BaseMapModule } from '@tomtom-org/maps-sdk/map';

const baseMapModule = await BaseMapModule.get(map);

// Toggle visibility, for the whole base map or for specific layer groups
baseMapModule.setVisible(true);
baseMapModule.setVisible(false, { layerGroups: { mode: 'include', names: ['borders', 'buildings2D'] } });

Learn more about Base Map Module →

Places module

A PlacesModule handles search results visualization and place information display.

import { PlacesModule } from '@tomtom-org/maps-sdk/map';
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';

// Initialize places module
const placesModule = await PlacesModule.create(map);

// Search and display results
const results = await discoverPlaces({ query: 'restaurants near me' });
await placesModule.show(results);

// Handle place interactions — `events.places` is the pins alone, typed as Place
placesModule.events.places.on('click', (place) => {
    console.log('Selected place:', place.properties.title);
});

Learn more about Places Module →

POIs module

A POIsModule controls the display and events for Points of Interest with category-based filtering and styling.

import { POIsModule } from '@tomtom-org/maps-sdk/map';

// Display specific POI categories
const poisModule = await POIsModule.get(map, {
    filters: {
        categories: { show: 'all-except', values: ['CHARGING_LOCATION'] },
    },
});

Learn more about POIs Module →

Routing module

A RoutingModule controls the display of routes, waypoints, and navigation instructions, and user events on such parts.

import { RoutingModule } from '@tomtom-org/maps-sdk/map';
import { calculateRoute } from '@tomtom-org/maps-sdk/services';

// Initialize routing module
const routingModule = await RoutingModule.create(map);

// Calculate and display route
const route = await calculateRoute({
    locations: [
        [4.9041, 52.3676], // Amsterdam
        [4.4777, 51.9244]  // Rotterdam
    ],
});
await routingModule.showRoutes(route);

Learn more about Routing Module →

Traffic flow module

The TrafficFlowModule displays real-time traffic flow information with color-coded speed indicators.

import { TrafficFlowModule } from '@tomtom-org/maps-sdk/map';

// Add traffic flow visualization
const trafficFlowModule = await TrafficFlowModule.get(map);

// Control traffic display
trafficFlowModule.setVisible(true);

// Show road closures only
trafficFlowModule.updateConfig({
    filters: { any: [{ showRoadClosures: 'only' }] },
});

Learn more about Traffic Flow Module →

Traffic incidents module

The TrafficIncidentsModule shows traffic incidents like accidents, road closures, and construction.

import { TrafficIncidentsModule } from '@tomtom-org/maps-sdk/map';

// Display traffic incidents
const trafficIncidentsModule = await TrafficIncidentsModule.get(map, {
    icons: { visible: false },
    filters: { any: [{ magnitudes: { show: 'all-except', values: ['minor'] } }] },
});

Learn more about Traffic Incidents Module →

Geometries module

The GeometriesModule displays custom geometric data like polygons, lines, and markers.

import { GeometriesModule } from '@tomtom-org/maps-sdk/map';

// Initialize geometries module
const geometryModule = await GeometriesModule.create(map);

const geometryToSearch = await geometryData({ geometries: location });
await geometryModule.show(geometryToSearch);

Learn more about Geometries Module →

Reachable ranges module

The ReachableRangesModule displays what calculateReachableRanges returns: each band titled from its budget, each origin's bands nested, coloured from the map.

import { ReachableRangesModule } from '@tomtom-org/maps-sdk/map';

const rangesModule = await ReachableRangesModule.create(map, { fillStyle: 'outline' });
await rangesModule.show(await calculateReachableRanges([10, 20].map((value) => ({ origin, budget: { type: 'timeMinutes', value } }))));

Learn more about displaying reachable ranges →

Custom GeoJSON module

The CustomGeoJSONModule renders your own GeoJSON data with typed sources, clustering, custom images, and style-change restoration.

import { CustomGeoJSONModule } from '@tomtom-org/maps-sdk/map';

const customModule = await CustomGeoJSONModule.create(map, {
    sources: {
        heatmap: { layers: [{ type: 'heatmap' }] }
    }
});

await customModule.show(heatmapData, 'heatmap');

Learn more about Custom GeoJSON Module →

Traffic incident details module

The TrafficIncidentDetailsModule renders a specific set of incidents you fetched yourself, rather than the live vector-tile layer.

import { TrafficIncidentDetailsModule } from '@tomtom-org/maps-sdk/map';
import { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';

const incidentDetailsModule = await TrafficIncidentDetailsModule.create(map);

const result = await trafficIncidentDetails({ bbox });
await incidentDetailsModule.show(result);

Learn more about Traffic Incident Details Module →

Traffic area analytics module

The TrafficAreaAnalyticsModule visualizes aggregated traffic statistics for an area as a hexgrid, square grid, or heatmap.

import { TrafficAreaAnalyticsModule } from '@tomtom-org/maps-sdk/map';

const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    displayMode: 'hexgrid-3d',
    activeMetric: 'congestionLevel',
});

await analyticsModule.show(analyticsResult);
analyticsModule.updateConfig({ activeMetric: 'speed' });

Learn more about Traffic Area Analytics Module →

Terrain module

The TerrainModule draws the relief of the elevation data: hillshade shading, and the map surface raised in 3D.

import { TerrainModule } from '@tomtom-org/maps-sdk/map';

const terrain = await TerrainModule.get(map, { hillshade: true });

// Raise the surface in 3D, for a pitched camera
terrain.setElevationEnabled(true);

Learn more about Terrain Module →