Build a web map with Leaflet: a practical guide
Leaflet is an open-source JavaScript library for creating interactive, mobile-friendly maps in the browser. A basic Leaflet application combines a map container, Leaflet’s CSS and JavaScript, a tile layer from a separately operated provider, and optional vector layers such as markers, polylines, polygons, and GeoJSON. This guide walks through a small but complete implementation, then explains the decisions that matter when the map becomes part of a real geospatial application.
Building a web map with Leaflet is straightforward when the responsibilities are separated clearly. Leaflet handles browser-side map interaction and rendering primitives; it does not provide a universal basemap, geocoder, routing engine, spatial database, or authoritative location dataset. You choose those services independently, then connect them through tile URLs, GeoJSON, APIs, or application code. The official Leaflet quick start demonstrates the basic pattern with Leaflet 1.9.4, an OpenStreetMap raster tile layer, markers, popups, and map events. (leafletjs.com)
How EDK fits
For: Web developers, GIS practitioners, data journalists, civic-technology teams, and application engineers who need a focused interactive map without adopting a full map-rendering platform.
Leaflet is a browser presentation layer for geographic information. Its value is clearest when the application already has, or can retrieve, meaningful representations of physical-world features: roads, buildings, parks, infrastructure, service locations, environmental observations, or public incidents. Leaflet can display those features as tiles, markers, lines, polygons, and GeoJSON, and it can capture user interactions such as clicks and viewport changes. The deeper geospatial work—finding features, validating coordinates, joining observations to regions, measuring proximity, ranking candidates, detecting change, or aggregating events—usually happens in an upstream data service or spatial computation layer. That separation makes Leaflet a useful interface for a broader world model: the map gives people a way to inspect and interact with geographic results, while the underlying system determines what physical features exist, how they are related, and what can be computed about them.
- •Use Leaflet as the visual inspection layer for a searchable physical-world dataset. For example, an application could return nearby roads, buildings, parks, hydrants, service requests, or construction-related features as GeoJSON, then let a user inspect individual features with popups and map events.
- •Use Leaflet to present the results of geographic computation rather than performing all computation in the browser. A backend can filter features by region, calculate proximity to a selected point, rank locations, or aggregate incidents by area, while Leaflet renders the resulting points, lines, or polygons for review.
- •Use Leaflet to compare multiple geographic layers—such as transportation networks, public assets, environmental features, and operational observations—when the application needs people to understand relationships among real-world objects rather than view a basemap alone.
The EDK outcome
Before you start
Basic familiarity with HTML, CSS, and JavaScript.
A modern web browser with developer tools.
A local web server or development environment for testing the page.
A selected tile provider and permission to use its tiles for the intended traffic and distribution model.
A small set of geographic data, such as known coordinates or a GeoJSON FeatureCollection.
Learning objectives
Initialize a Leaflet map with a responsive container and explicit view.
Add a raster tile layer with appropriate attribution.
Create markers, popups, lines, polygons, and map event handlers.
Load geographic features from GeoJSON or an application API.
Recognize the boundary between Leaflet rendering and backend geospatial computation.
Avoid common tile-provider, coordinate-order, popup-security, and production-scale mistakes.
Verification
The map container has a visible height at every supported responsive breakpoint.
Leaflet CSS and JavaScript load without console errors.
Tile requests use the intended HTTPS provider URL and return successfully.
Attribution is visible and readable on the map.
Known coordinates appear in the expected geographic location.
Markers, lines, polygons, and GeoJSON features use the intended coordinate order.
Popup content cannot inject arbitrary HTML from untrusted data.
Viewport-based API requests do not create duplicate layers or unbounded memory growth.
The application remains usable when the tile service or feature API returns an error.
The selected tile provider’s licensing, attribution, caching, rate, and offline requirements are documented for the deployment.
Troubleshooting
Blank map: inspect the map container in developer tools and verify that it has a non-zero height. Also confirm that Leaflet CSS loaded and that map initialization runs after the container exists.
Tiles do not appear: inspect network requests for incorrect URLs, mixed-content blocking, HTTP errors, rate limits, or provider restrictions. Use the exact URL and requirements documented by your tile provider. For OpenStreetMap standard tiles, the official policy requires the HTTPS tile URL and visible attribution. (operations.osmfoundation.org)
Markers appear in the wrong place: check coordinate order. Leaflet examples use latitude, longitude, while GeoJSON uses longitude, latitude in coordinate arrays. Convert explicitly at the API boundary rather than guessing.
Popup shows unexpected markup: popup strings are rendered as HTML. Treat external and user-provided values as untrusted; sanitize them or bind a DOM element whose textContent is set safely. (leafletjs.com)
GeoJSON loads but features are missing: inspect the response for a valid FeatureCollection, empty geometries, unsupported geometry types, incorrect coordinates, or a server-side bounding-box filter that excludes the current map view.
The map becomes slow: reduce the number of browser features, query by viewport, simplify geometries, cluster points, use server-side aggregation, or move toward tiled delivery. Rendering every record as an individual marker is rarely appropriate for large datasets.
Features duplicate after panning: retain a reference to the active layer, clear or replace it before adding the next response, and use stable feature identifiers if the application merges results.
The map works locally but not in production: check content-security policy, asset paths, HTTPS, CDN integrity failures, CORS headers, proxy behavior, API authentication, and the production tile provider’s allowed origins and usage terms.
Next steps
Move tile configuration into environment-specific settings and document the provider contract.
Add loading, empty, and error states for feature requests.
Introduce viewport-based querying before the dataset becomes too large for the browser.
Add a layer control if users need to toggle thematic layers.
Validate geographic results against authoritative source data and preserve provenance in feature properties.
For advanced vector rendering or very large interactive datasets, evaluate a vector-tile or WebGL-oriented map architecture alongside Leaflet.
What you need before writing code
You need a browser-based HTML page, basic JavaScript, and a source of map imagery or tiles. Leaflet itself is provider-agnostic: it supplies the map object, controls, layers, events, and interaction model, while tile imagery comes from a separate provider. The official quick start uses Leaflet 1.9.4 from a CDN and an OpenStreetMap raster tile URL as an example. (leafletjs.com)
For a prototype, a single HTML file is enough. For a production application, decide early whether you will use a managed tile provider, self-host tiles, or connect to an existing internal map service. Confirm the provider’s attribution, rate limits, caching rules, commercial terms, geographic coverage, zoom limits, and availability guarantees before the map becomes operationally important.
- •A web page with a map container element.
- •Leaflet CSS and JavaScript, pinned to a known version.
- •A tile provider or another base layer.
- •Geographic data to display, such as coordinates or GeoJSON.
- •A plan for attribution, error handling, responsive sizing, and data freshness.
Step 1: create the smallest working Leaflet map
The map container must have a non-zero height. Without explicit height, Leaflet can initialize successfully while remaining invisible. The map is then created with a center and zoom level, and a tile layer is added to provide the visual basemap. Leaflet’s standard coordinate order is latitude followed by longitude. (leafletjs.com)
- •Create index.html.
- •Add the Leaflet stylesheet in the document head.
- •Add a div with an id such as map.
- •Add a height rule for that div.
- •Load Leaflet JavaScript after the container exists.
- •Initialize the map and add a tile layer.
Step 2: add markers, popups, and user interaction
Markers represent point features such as facilities, incidents, sensors, or selected locations. Popups provide feature details, while map events let the application respond to clicks, movement, zoom changes, and other interaction. Leaflet’s API includes markers, popups, polylines, polygons, circles, layer groups, controls, and event handlers. (leafletjs.com)
A popup string is interpreted as HTML. If the content contains user-submitted or externally retrieved values, do not concatenate those values into HTML without sanitization. The Leaflet reference explicitly recommends sanitizing untrusted content or creating an element and assigning safe text with textContent. (leafletjs.com)
- •Use markers for discrete point features.
- •Use polylines for routes, roads, streams, or tracks.
- •Use polygons for boundaries, parcels, parks, or service areas.
- •Use map click events to inspect coordinates or start a query.
- •Use layer groups when several features should be toggled together.
Step 3: display GeoJSON and connect the map to geographic data
For application data, GeoJSON is a practical interchange format because it represents points, lines, polygons, and feature properties in a browser-friendly structure. Leaflet can create a GeoJSON layer from an object or fetched response. The important design decision is not merely how to draw the feature; it is how the feature was found, what its coordinates mean, what time period it represents, and whether its properties are safe and useful to expose.
Keep geographic computation close to the data service when the dataset is large or the operation is complex. A backend can perform bounding-box filtering, point-in-polygon tests, nearest-feature searches, spatial joins, or aggregation, then return only the features needed for the current map view. Leaflet can then act as the interactive inspection surface instead of becoming an improvised spatial database.
- •Return only the features needed for the current viewport where possible.
- •Include stable feature identifiers and provenance fields in properties.
- •Make coordinate reference assumptions explicit; browser GeoJSON is normally longitude, latitude.
- •Use server-side filtering for large datasets, repeated queries, or expensive geographic operations.
- •Use styling functions to distinguish feature classes or statuses.
Step 4: make the map production-aware
A prototype can use a public demonstration tile layer, but production operation requires a deliberate provider decision. OpenStreetMap’s standard raster tile service requires the correct HTTPS tile URL, visible attribution, a valid application identity, normal browser referer behavior, and respect for caching headers. The service prohibits bulk downloading, prefetching, and offline use, and it provides no service-level guarantee. (operations.osmfoundation.org)
Leaflet does not remove these obligations because it does not own the tiles. If your application has substantial traffic, commercial use, offline requirements, or a need for guaranteed availability, use a provider or self-hosted stack whose terms match those requirements. Keep the tile URL configurable so that changing providers does not require rewriting the application.
- •Keep tile URLs and attribution configuration outside feature-rendering code.
- •Monitor tile errors separately from application API errors.
- •Respect provider caching instructions and do not prefetch broad areas without permission.
- •Test mobile layouts, slow networks, keyboard access, and high-density screens.
- •Plan how the map behaves when tiles or feature APIs are unavailable.
Step 5: validate the geographic result, not just the rendered screen
A map can look correct while showing incorrect data. Validate coordinate order, geographic extent, timestamps, feature identifiers, and expected counts. Test known points in multiple regions, including locations near the antimeridian or at high latitude if your application may encounter them. Confirm that a marker’s popup describes the same feature represented by the coordinate.
For analytical applications, visual inspection is only one check. Compare a sample of rendered results with the authoritative source, verify that filters return expected features, and test spatial operations independently from the browser. Leaflet can make a result understandable, but it cannot establish that a road, building, incident, or boundary is current or authoritative.
- •Check latitude and longitude order.
- •Check whether coordinates are in WGS 84 geographic degrees.
- •Check dates, time zones, and update timestamps.
- •Check that feature properties match the displayed geometry.
- •Check behavior at different zoom levels and viewport sizes.
Workflow
Create the HTML shell and install Leaflet
For a first prototype, use the official CDN assets shown in Leaflet’s quick start. Pin the version rather than silently accepting an unbounded upgrade.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Leaflet map</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<style>
#map { height: 480px; }
</style>
</head>
<body>
<div id="map" aria-label="Interactive map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="app.js"></script>
</body>
</html>Initialize the map and add a basemap
Choose a starting center and zoom appropriate to your use case. The tile URL, attribution, and maximum zoom belong to the tile layer configuration, not to Leaflet’s map object.
const map = L.map('map').setView([40.7128, -74.0060], 12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);Add a marker and a safe popup
Use latitude first and longitude second in Leaflet’s LatLng arrays. For trusted, developer-authored text, a short HTML string is convenient. For untrusted values, create an element and assign textContent or sanitize the content before binding it.
const marker = L.marker([40.7128, -74.0060], {
title: 'Example location',
alt: 'Example location marker'
}).addTo(map);
const popup = document.createElement('div');
popup.textContent = 'Example location';
marker.bindPopup(popup);
map.on('click', (event) => {
console.log('Clicked:', event.latlng.lat, event.latlng.lng);
});Add lines, polygons, and GeoJSON
Use vector layers when the application needs to show routes, boundaries, service areas, or other geographic shapes. GeoJSON coordinates follow the GeoJSON convention, so confirm the format at the API boundary before rendering.
L.polyline([
[40.7128, -74.0060],
[40.7306, -73.9866]
], { color: 'blue' }).addTo(map);
const area = L.polygon([
[40.72, -74.02],
[40.75, -74.02],
[40.75, -73.98],
[40.72, -73.98]
], { color: 'green' }).addTo(map);
fetch('/api/features.geojson')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(data => {
L.geoJSON(data, {
onEachFeature: (feature, layer) => {
const name = document.createElement('span');
name.textContent = feature.properties?.name ?? 'Unnamed feature';
layer.bindPopup(name);
}
}).addTo(map);
});Connect geographic queries to the viewport
For a larger dataset, use the current map bounds to request only relevant features. This pattern keeps data retrieval and spatial computation in the application backend while Leaflet handles the interactive view.
async function loadVisibleFeatures() {
const bounds = map.getBounds();
const params = new URLSearchParams({
west: bounds.getWest(),
south: bounds.getSouth(),
east: bounds.getEast(),
north: bounds.getNorth()
});
const response = await fetch(`/api/features?${params}`);
if (!response.ok) throw new Error(`Feature request failed: ${response.status}`);
const geojson = await response.json();
return L.geoJSON(geojson).addTo(map);
}
map.on('moveend', loadVisibleFeatures);Prepare for deployment
Replace demonstration tile infrastructure when the application’s traffic, commercial terms, offline requirements, or availability needs exceed the provider’s policy. Keep provider configuration changeable, preserve visible attribution, monitor tile and API failures independently, and test with slow networks and mobile layouts.
const tileConfig = {
url: window.APP_TILE_URL,
attribution: window.APP_TILE_ATTRIBUTION,
maxZoom: 19
};
L.tileLayer(tileConfig.url, tileConfig).addTo(map);Questions teams ask
Does Leaflet include map tiles?
No. Leaflet provides the browser-side map and layer framework, but tile imagery comes from a separate provider. The official quick start uses OpenStreetMap tiles as an example and explicitly notes that Leaflet is provider-agnostic. (leafletjs.com)
Can I use OpenStreetMap tiles in a production Leaflet map?
You may use the standard OpenStreetMap raster tile service only when your usage follows the OpenStreetMap Foundation’s Tile Usage Policy. That includes visible attribution, the correct HTTPS URL, normal caching behavior, and no bulk downloading or offline prefetching. The service is best effort and has no SLA, so many production applications choose another provider or self-host their tiles. (operations.osmfoundation.org)
Why is my Leaflet map blank?
The most common causes are a missing or incorrectly loaded Leaflet stylesheet, a map container with no height, JavaScript running before the container exists, an invalid tile URL, or a browser request blocked by network policy. Inspect the browser console and network panel, confirm that the container has a measurable height, and verify that tile requests return successfully.
How should I show data from an API in Leaflet?
Fetch or query the data from your application backend, validate the response, and convert it to a Leaflet layer—often a GeoJSON layer, marker collection, or line and polygon layer. For large or frequently changing datasets, filter by viewport or query parameters on the server rather than sending the entire dataset to the browser.
Is Leaflet a replacement for a GIS database?
No. Leaflet is primarily an interactive map interface. It can display geographic results and respond to user interaction, but storage, indexing, geocoding, routing, spatial joins, proximity calculations, temporal analysis, and data lineage normally belong in other services or libraries.
Keep exploring
build a web map with leaflet production guide
Continue from the basic implementation into tile-provider selection, deployment, performance, accessibility, monitoring, and operational safeguards.
build a web map with leaflet troubleshooting guide
Diagnose blank maps, missing tiles, incorrect coordinate order, broken popups, CSS sizing issues, and API-layer failures.
build a web map with maplibre
Compare Leaflet’s DOM and raster-layer-oriented approach with a vector-tile and WebGL-oriented web-map workflow.
Sources
Leaflet Quick Start Guide
Official step-by-step documentation for loading Leaflet, initializing a map, adding OpenStreetMap tiles, creating markers and popups, and handling map events.
Open sourceLeaflet API reference
Official reference for Map, Marker, Popup, TileLayer, vector layers, events, panes, and security guidance for popup content.
Open sourceOpenStreetMap Foundation Tile Usage Policy
Authoritative requirements and restrictions for using the standard tile.openstreetmap.org raster tile service.
Open sourceOpenStreetMap Foundation Vector Tile Usage Policy
Relevant official policy for projects considering OpenStreetMap Foundation vector tiles instead of raster tiles. ([operations.osmfoundation.org](https://operations.osmfoundation.org/policies/vector/?utm_source=openai))
Open sourceLeaflet Tutorials
Official collection of tutorials covering mobile maps, custom icons, accessibility, layers, and other implementation patterns. ([leafletjs.com](https://leafletjs.com/examples.html?utm_source=openai))
Open source