Learn

GeoJSON with Mapbox GL JS

In Mapbox GL JS, GeoJSON is a source type. You add the source, then add one or more layers that reference it.

Add a GeoJSON source and render it

Call map.addSource() with type: 'geojson' and a data value that is either a URL or a GeoJSON object. Then call map.addLayer() for each visual representation you need — the same source can back multiple layers simultaneously.

map.on('load', () => {
  map.addSource('zones', {
    type: 'geojson',
    data: '/delivery-zones.geojson',
  })

  map.addLayer({
    id: 'zones-fill',
    type: 'fill',
    source: 'zones',
    paint: {
      'fill-color': '#3388ff',
      'fill-opacity': 0.4,
    },
  })

  map.addLayer({
    id: 'zones-outline',
    type: 'line',
    source: 'zones',
    paint: { 'line-color': '#3388ff', 'line-width': 2 },
  })
})

zones-fill renders the polygon interiors; zones-outline renders the borders. Both reference the same 'zones' source — one fetch, two visual layers.

Layer types

Mapbox GL JS has five layer types that work with GeoJSON geometry:

TypeRendersGeometry
fillFilled polygonsPolygon, MultiPolygon
lineStrokes on lines or polygon outlinesLineString, MultiLineString, Polygon
circleCircle markers at each pointPoint, MultiPoint
symbolIcons or text labels at point locationsPoint, MultiPoint
fill-extrusionExtruded 3D polygons (requires a height property)Polygon, MultiPolygon

A single GeoJSON source can have multiple layers of different types — for example, a fill layer for the interior and a line layer for the stroke.

Update a source at runtime

Call setData() on the source object to replace all features. The map re-renders immediately.

// Replace all features in the source
map.getSource('zones').setData(updatedFeatureCollection)

// Works with a plain object or a URL
map.getSource('zones').setData('https://api.example.com/zones.geojson')

When you pass a URL, Mapbox GL JS fetches it and replaces the source data. This is the pattern for polling live data: call setData() with the endpoint on an interval, and the map stays current without a page reload.

Filter features by property

map.setFilter() applies a Mapbox expression to a layer. Features that don't match the expression are hidden — the source data is unchanged.

// Show only active zones
map.setFilter('zones-fill', ['==', ['get', 'status'], 'active'])

// Remove filter
map.setFilter('zones-fill', null)

Expressions use the ['get', 'propertyName'] form to read feature properties. ['==', ...], ['>', ...], ['in', ...], and ['all', ...] are the most common operators for GeoJSON property filtering.

Hover highlight

Feature state lets you store transient state (like hover: true) on individual features without modifying the GeoJSON source. The layer paint expression reads the state and applies a different style.

let hoveredId = null

map.on('mousemove', 'zones-fill', (e) => {
  if (e.features.length > 0) {
    if (hoveredId !== null) {
      map.setFeatureState({ source: 'zones', id: hoveredId }, { hover: false })
    }
    hoveredId = e.features[0].id
    map.setFeatureState({ source: 'zones', id: hoveredId }, { hover: true })
  }
})

map.on('mouseleave', 'zones-fill', () => {
  if (hoveredId !== null) {
    map.setFeatureState({ source: 'zones', id: hoveredId }, { hover: false })
  }
  hoveredId = null
})

Reference the feature state in your layer's paint expression:

// In the layer paint
'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 0.7, 0.4]

The ['boolean', ['feature-state', 'hover'], false] form provides a default of false when the state key hasn't been set yet — avoiding a null opacity on first render.

Note: features need numeric id fields in the GeoJSON for setFeatureState to work. If your GeoJSON features don't have id, set promoteId: 'your_property' on the source to promote a property to the feature ID.

Watch out: map.addSource() and map.addLayer() must be called inside map.on('load', ...). Calling them before the map style loads throws Error: Style is not done loading with no other indication of what went wrong.

Editing GeoJSON on a Mapbox GL JS map

@element-editor/adapter-maplibre works with MapLibre GL JS and Mapbox GL JS (with Mapbox GL JS, TypeScript needs one cast on the map instance). It adds drawing and editing as a layer on top of your existing map setup.

Read the quickstart


For React-specific patterns, see GeoJSON with React. For format reference, see What is GeoJSON?.

Continue reading

Mike Acler

Mike Acler

Design engineer and creator of Element Editor, an open-source map drawing and GeoJSON editing library for React. Writes about web cartography and GIS tooling.