Leaflet's built-in L.geoJSON() method makes it straightforward to render geographic features on an interactive web map. A Leaflet GeoJSON layer accepts any valid GeoJSON object — a FeatureCollection, a single Feature, or a bare Geometry — and converts it into map elements you can style, query, and interact with. This guide walks through the complete API, from basic rendering to production-ready techniques like filtering, popups, custom markers, and marker clustering. If you need a quick way to inspect your data before writing code, drag your file into GeoDataTools to preview it on a map instantly.
Creating a Leaflet GeoJSON Layer
The L.geoJSON() factory function is the entry point. Pass a GeoJSON object as the first argument and an optional configuration object as the second. The returned layer can be added to any Leaflet map instance. Leaflet automatically determines the correct layer type for each geometry: polygons become L.Polygon, lines become L.Polyline, and points become L.Marker by default.
const geojsonLayer = L.geoJSON(data, {
style: styleFunction,
onEachFeature: onEachFeature,
pointToLayer: pointToLayer,
filter: filterFunction
}).addTo(map);
If you pass null as the data argument, you create an empty layer that you can populate later with geojsonLayer.addData(newData). This pattern is useful when loading data asynchronously from an API endpoint.
Styling Features with the Style Option
The style option controls the visual appearance of vector layers such as polygons and lines. You can provide a static object or a function that receives each feature and returns a style object. The function approach lets you create data-driven visualizations such as choropleth maps.
function styleFunction(feature) {
return {
fillColor: feature.properties.density > 100 ? '#b30000' : '#fee8c8',
weight: 2,
opacity: 1,
color: '#636363',
fillOpacity: 0.7
};
}
Supported style properties include color, weight, opacity, fillColor, fillOpacity, dashArray, and lineCap. These map directly to SVG path attributes, so any valid SVG value works.
Adding Popups with onEachFeature
The onEachFeature callback runs once for every feature in your data. It receives the GeoJSON feature object and the Leaflet layer that was created for it, making it the ideal place to bind popups, tooltips, or event listeners.
function onEachFeature(feature, layer) {
if (feature.properties && feature.properties.name) {
layer.bindPopup(
'<strong>' + feature.properties.name + '</strong>' +
'<br>Population: ' + feature.properties.population
);
}
}
Popups support full HTML, so you can include images, links, or small tables. Keep popup content concise — a well-structured GeoJSON properties object makes it easy to generate readable summaries without overloading the user.
Filtering Features
The filter option accepts a function that returns true or false for each feature. Only features that return true are added to the layer. This is evaluated at layer creation time, so if your filter criteria change you need to remove and re-add the layer.
const filteredLayer = L.geoJSON(data, {
filter: function (feature) {
return feature.properties.type === 'park';
}
}).addTo(map);
For dynamic filtering on an existing layer, iterate with eachLayer() and toggle visibility using map.removeLayer() and map.addLayer(). This avoids re-parsing the GeoJSON each time, which matters when working with larger files.
Custom Markers with pointToLayer
By default, Leaflet renders Point geometries as standard pin markers. The pointToLayer option lets you replace them with circle markers, custom icons, or any other Leaflet layer. The function receives the feature and a LatLng object and must return a layer instance.
function pointToLayer(feature, latlng) {
return L.circleMarker(latlng, {
radius: 8,
fillColor: '#2196F3',
color: '#0D47A1',
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
Circle markers scale with the map and render efficiently because they use SVG or Canvas rather than DOM image elements. They are the preferred choice when you have hundreds or thousands of points.
Handling Events on a Leaflet GeoJSON Layer
You can attach event listeners to individual features inside onEachFeature using the standard Leaflet event API. Common patterns include highlighting a feature on hover and resetting the style on mouseout.
function onEachFeature(feature, layer) {
layer.on({
mouseover: function (e) {
e.target.setStyle({ weight: 4, fillOpacity: 0.9 });
e.target.bringToFront();
},
mouseout: function (e) {
geojsonLayer.resetStyle(e.target);
},
click: function (e) {
map.fitBounds(e.target.getBounds());
}
});
}
Calling geojsonLayer.resetStyle(layer) restores the original style defined by your style function, which is more maintainable than manually tracking previous styles. Make sure the geojsonLayer variable is accessible in the event handler's scope.
Loading GeoJSON from a URL
In production applications you rarely hard-code GeoJSON objects. Instead, fetch data from an API or a static file and pass it to the layer after the response resolves.
fetch('/assets/data/neighborhoods.geojson')
.then(response => response.json())
.then(data => {
L.geoJSON(data, {
style: styleFunction,
onEachFeature: onEachFeature
}).addTo(map);
})
.catch(error => console.error('Failed to load GeoJSON:', error));
Always validate your GeoJSON before loading it into a map. Malformed coordinates or missing type fields cause silent rendering failures. The GeoJSON validation and debugging guide covers common pitfalls and how to fix them.
Performance with Large Datasets
The default SVG renderer creates a DOM element for every feature, which slows down rendering beyond a few thousand features. Switching to Leaflet's Canvas renderer improves draw performance significantly because it paints everything onto a single HTML5 Canvas element instead of creating individual SVG nodes.
const map = L.map('map', {
renderer: L.canvas()
});
Beyond the renderer, consider simplifying geometries server-side with tools like Mapshaper, loading data on demand with bounding-box queries, or converting your GeoJSON to vector tiles for very large datasets. Reducing coordinate precision to six decimal places — roughly 0.1 meter accuracy — also shrinks file sizes without visible quality loss.
Marker Clustering
When displaying thousands of point features, overlapping markers make the map unreadable. The Leaflet.markercluster plugin groups nearby points into cluster icons that expand as the user zooms in. Integrating it with a GeoJSON layer requires creating a MarkerClusterGroup and adding the GeoJSON layer to it instead of directly to the map.
const markers = L.markerClusterGroup();
const geojsonLayer = L.geoJSON(data, {
pointToLayer: function (feature, latlng) {
return L.marker(latlng);
},
onEachFeature: onEachFeature
});
markers.addLayer(geojsonLayer);
map.addLayer(markers);
Cluster groups support custom icon styles, spider-leg expansion, and chunk-loading animations. For datasets exceeding 50,000 points, pair clustering with the Canvas renderer and lazy loading for the best user experience.
Coordinate Systems and GeoJSON
The GeoJSON specification (RFC 7946) requires coordinates in the WGS 84 datum using longitude-latitude order. Leaflet expects the same coordinate system, so valid GeoJSON files work without any coordinate transformation. If your source data uses a different projection, reproject it to EPSG:4326 before creating the layer. Tools like the KML to GeoJSON and GeoJSON to KML converters handle this automatically.
Putting It All Together
The Leaflet GeoJSON layer API is deliberately composable: each option handles one concern — appearance, interaction, filtering, or marker type — so you can mix and match them freely. Start with a basic L.geoJSON(data).addTo(map) call, then layer on styling, popups, and clustering as your application requirements grow. When debugging, validate your data first, simplify your options object, and add complexity incrementally.
If you want to experiment without setting up a development environment, open GeoDataTools and drop your GeoJSON file on the map. You can inspect feature properties, toggle layers, and export to other formats like KML — all directly in the browser.
FAQ
What GeoJSON types does L.geoJSON() support?
Leaflet supports all seven GeoJSON geometry types defined in RFC 7946: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, and GeometryCollection. It also accepts Feature and FeatureCollection wrapper objects. FeatureCollection is the most common input because it groups multiple geometries with their associated property data into a single parseable structure.
How do I update a Leaflet GeoJSON layer with new data?
Call clearLayers() on the existing layer to remove all current features, then call addData(newGeoJSON) to render the updated dataset. This approach preserves your style, filter, and event handler options without recreating the layer object from scratch. It is efficient for real-time dashboards where data refreshes periodically from a server.
Can I use Leaflet GeoJSON layers with frameworks like Angular or React?
Yes. Leaflet is framework-agnostic and works with Angular, React, Vue, or plain JavaScript. In Angular, initialize the map inside ngAfterViewInit and destroy it in ngOnDestroy to prevent memory leaks. In React, use a useEffect hook with a cleanup return. Wrapper libraries like ngx-leaflet and react-leaflet add declarative component APIs on top of the core library.