Guide · JavaScript · beginner

Plot lightning strikes on a Leaflet map.

This walks through adding a live lightning layer to an existing Leaflet map. You fetch recent flashes for whatever the map is currently showing, draw each one as a circle marker, and refresh on a timer. It is roughly thirty lines and needs no plugin.

Before you start

  1. A working Leaflet map. Any version from 1.x is fine.
  2. An API key. Pay As You Go is free to start and includes 5,000 calls a month.
  3. A server-side route to hold the key, covered below.

Do not put your API key in browser code. Anything in a script tag is public. Proxy the request through your own backend and keep the key there.

A one-route proxy

The browser calls your server, your server calls the API with the key, and the response is passed straight back. This example is a Next.js route handler, but the shape is the same anywhere.

app/api/flashes/route.ts

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const qs = new URLSearchParams({
    since_minutes: searchParams.get("since_minutes") ?? "15",
    min_lat: searchParams.get("min_lat") ?? "",
    max_lat: searchParams.get("max_lat") ?? "",
    min_lon: searchParams.get("min_lon") ?? "",
    max_lon: searchParams.get("max_lon") ?? "",
  });

  const res = await fetch(`https://api.lightningapi.dev/v1/flashes?${qs}`, {
    headers: { "X-API-Key": process.env.LIGHTNING_API_KEY! },
    next: { revalidate: 20 },
  });

  return new Response(await res.text(), {
    status: res.status,
    headers: { "content-type": "application/json" },
  });
}

The twenty second revalidate window matches how often the underlying data changes, so several users sharing a map do not each spend a call.

Drawing the layer

Keep the flashes in their own layer group so you can clear and redraw without touching the base map. Query the map's current bounds so you never ask for data outside the viewport.

Lightning layer

const lightning = L.layerGroup().addTo(map);

async function refresh() {
  const b = map.getBounds();
  const qs = new URLSearchParams({
    since_minutes: "15",
    min_lat: b.getSouth().toFixed(3),
    max_lat: b.getNorth().toFixed(3),
    min_lon: b.getWest().toFixed(3),
    max_lon: b.getEast().toFixed(3),
  });

  const res = await fetch("/api/flashes?" + qs);
  if (!res.ok) return;
  const { flashes = [] } = await res.json();

  lightning.clearLayers();
  for (const f of flashes) {
    L.circleMarker([f.lat, f.lon], {
      radius: 4,
      weight: 0,
      fillColor: "#ffd166",
      fillOpacity: 0.85,
    }).addTo(lightning);
  }
}

refresh();
setInterval(refresh, 20000);
map.on("moveend", refresh);

Watch your quota

Refreshing on both a timer and every map move is convenient and expensive. A user panning around burns calls quickly. Debounce the moveend handler, and consider dropping the timer interval when the tab is hidden.

Pay As You Go includes 5,000 calls a month at no cost and bills $0.003 per call after that, with a 100,000 call monthly cap. If you are building something with real users behind it, Pro is where browser access and a larger allowance start.

Frequently asked questions

Why not call the API directly from the browser?

Your key would be visible to anyone who opens developer tools. A one-route proxy keeps it on the server and costs you almost nothing in complexity.

Does this work with Mapbox GL or MapLibre?

The fetch half is identical. Only the drawing changes, since those libraries use GeoJSON sources rather than circle markers. The radar tiles guide shows the MapLibre pattern.

How often should I refresh?

Twenty seconds matches how often the data changes. Faster than that spends quota without showing you anything new.

How many flashes come back?

Up to the per-call row cap for your plan, which is 20,000 on Pro and 50,000 on Ultimate. A single map viewport over fifteen minutes is normally far below that.

Can I show only cloud-to-ground strikes?

Yes. Each flash carries its classification, so you can filter client side, and the stream endpoint accepts a cg_only parameter if you move to push delivery later.

Related

Last checked 2026-09-20

Coverage areas

Lightning data provided as-is; not for safety-critical use. Commercial use is permitted on every current plan. Read the EULA →