Add a Custom GeoJSON Layer with Data‑Driven Circle Styling in Mapbox GL JS
Step‑by‑step guide to load a GeoJSON point set into Mapbox GL JS and style circle radii dynamically using a data‑driven expression.
10 Jul 2026, 05:34 UTC

Desired outcome
Render a set of point features from a GeoJSON source on a Mapbox GL JS map, where each point’s circle radius is determined dynamically by a numeric property (e.g., population) using a data‑driven style function.
Prerequisites
- A valid Mapbox access token with GL JS enabled (obtain from your Mapbox account).
- A modern browser that supports ES6 (Chrome, Firefox, Safari, Edge).
- A GeoJSON
FeatureCollectioncontaining at least one numeric property, for example\"population\". - Mapbox GL JS version 2.15.0 or later, loaded via a
<script>tag or a bundler such as Webpack or Vite. - Basic familiarity with inserting HTML and JavaScript into a page.
Procedure
-
Create the map container
Add a
<div>element to your HTML where the map will appear, then initialize the map in JavaScript.<div id="map"></div>// Replace MAPBOX_TOKEN with your actual token const map = new mapboxgl.Map({ container: 'map', // matches the div id style: 'mapbox://styles/mapbox/light-v11', center: [-96, 37.8], // longitude, latitude zoom: 3, accessToken: MAPBOX_TOKEN }); -
Add the GeoJSON source
Assume your GeoJSON data is stored in a variable named
yourGeoJSON. Add it as a vector source.map.addSource('points', { type: 'geojson', data: yourGeoJSON // FeatureCollection with a numeric property e.g. "population" }); -
Add a circle layer with data‑driven radius
The
circle-radiuspaint property uses an interpolation expression that maps thepopulationvalue to a pixel radius between 5 and 30.map.addLayer({ id: 'points-layer', type: 'circle', source: 'points', paint: { 'circle-radius': [ 'interpolate', ['linear'], ['get', 'population'], // input property 0, // population value → 5px radius 5, 100000, // population value → 30px radius 30 ], 'circle-color': '#ff6600', 'circle-opacity': 0.8 } });If you need the layer to appear behind labels, insert it before the
poi-labellayer:map.addLayer(layer, 'poi-label'); -
Optional: Adjust layer order
Mapbox GL JS draws layers in the order they are added. To ensure points are visible over polygons but under labels, you can add the layer after the
waterwaylayer and before theroad-labellayer, or usemap.moveLayerafter creation.
Verification checks
- Open the browser console (F12) and confirm there are no JavaScript errors related to Mapbox or the source/layer IDs.
- Inspect the source:
map.getSource('points')should return an object whose_dataproperty matches the GeoJSON you supplied. - Inspect the layer:
map.getLayer('points-layer')should exist and havetype: 'circle'. - Visually verify that circles vary in size. Click a few points, read their
populationvalue from the GeoJSON, and compare the rendered radius to the expected range (5‑30 px). You can measure radius using the browser’s developer tools overlay. - Toggle visibility to confirm the layer is controllable:
// Hide
map.setLayoutProperty('points-layer', 'visibility', 'none');
// Show again
map.setLayoutProperty('points-layer', 'visibility', 'visible');
Recovery / rollback options
Adding a source and layer modifies the map’s state. To revert:
- Remove the layer (if it exists):
if (map.getLayer('points-layer')) {
map.removeLayer('points-layer');
}
- Remove the source:
if (map.getSource('points')) {
map.removeSource('points');
}
Perform these steps in the console or within your application’s cleanup routine.
Limitations and practical considerations
- Missing numeric property: If a feature lacks the
populationproperty, the expression evaluates toundefinedand Mapbox defaults the radius to 0, making the point invisible. Ensure all features contain the property or provide a fallback using["get", "population", 0]. - Performance with large datasets: GeoJSON sources larger than ~50 MB can cause noticeable lag in the browser. For bigger data, consider clustering (
map.addSourcewithcluster: true) or converting to vector tiles. - Unique IDs: Adding a layer or source with an ID that already exists throws an error. Use a distinct ID or call
map.removeLayer/map.removeSourcebefore re‑adding. - Style version compatibility: The interpolation syntax shown works with Mapbox GL JS ≥ 2.0. Older versions may require the older
"stops"format.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.