Web Graphics and Geospatial
I Turned Manhattan Into a Video Game Cutscene With Mapbox
A hands-on Mapbox GL JS 3.26 review covering 3D cities, Mapbox Standard, lighting, weather, custom layers, performance, pricing, and MapLibre.
Summarize this article
Open a prefilled request in your preferred assistant.

TL;DR
- Mapbox GL JS is absurdly good at the first 90% of an epic 3D city. A Standard map, a steep camera pitch, one lighting preset, and a dramatic
flyTocan turn an empty<div>into discount Gotham before you have finished naming the component. - Mapbox Standard includes 3D buildings, landmark models, trees, facades, shadows, ambient occlusion, four lighting presets, and runtime configuration. I built a five-city review lab that also adds rain, snow, a glowing GeoJSON route, building selection, a camera move, and a browser evidence exporter.
- The main tradeoff is control. Standard is an imported, Mapbox-maintained basemap; you customize exposed configuration properties and place your layers into slots, but you cannot treat every internal basemap layer like your own pile of LEGO.
- The product is usage-priced and account-dependent. Mapbox GL JS map loads currently include 50,000 free monthly loads, then start at $5 per 1,000; a map load can include unlimited interaction for up to 12 hours.
- Mapbox GL JS 2.x and 3.x use Mapbox's commercial Web SDK license, not an open-source license. If self-hosting, an open renderer, or provider portability is the primary requirement, MapLibre GL JS deserves a serious look.
- My verdict: use Mapbox when the map is a product surface and you want an impressive city quickly; think harder when the map is infrastructure you must fully own, deeply restyle, or run independently of one vendor.
Evidence status: I ran the lab on July 22, 2026 with Mapbox GL JS 3.26.0 at 1440 by 900, rendered Lower Manhattan, completed the camera sample, and recorded zero Mapbox, page, or network errors. The automated Chrome session used SwiftShader rather than a hardware GPU, so its timing is published as harness evidence rather than a Mapbox performance score.
The 45-second version
Mapbox GL JS takes vector tiles, a style, camera state, and your data layers, then renders the result through WebGL in the browser. The current release I reviewed is 3.26.0; the official repository describes it as a client-side renderer for Mapbox styles and vector tiles.
The dramatic city recipe is almost offensively short:
const map = new mapboxgl.Map({
accessToken: publicToken,
container: "map",
style: "mapbox://styles/mapbox/standard",
center: [-74.0109, 40.7076],
zoom: 15.4,
pitch: 73,
bearing: 28,
antialias: true,
config: {
basemap: {
theme: "custom",
lightPreset: "night",
show3dObjects: true,
show3dBuildings: true,
show3dLandmarks: true,
show3dTrees: true,
show3dFacades: true,
showRoadLabels: false,
colorBuildings: "#6962d8",
colorMotorways: "#f04fc6"
}
}
});That is the part Mapbox absolutely nails. You do not need to source building footprints, invent extrusion heights, design a road hierarchy, light every mesh, label Manhattan, and then discover at 2 a.m. that your camera lives underneath New Jersey; Mapbox Standard arrives with a coherent basemap and a pile of 3D decisions already made.
My subjective review card looks like this:
| Area | Verdict | The catch |
|---|---|---|
| Initial setup | Excellent | A Mapbox account and public token are mandatory. |
| 3D city quality | Excellent in covered cities | Coverage and detailed landmarks vary by place. |
| Camera and animation | Excellent | It is easy to make users seasick with professional precision. |
| Custom data layers | Excellent | You still need to understand sources, layers, expressions, and slots. |
| Basemap customization | Good | Standard exposes a curated control panel, not every internal layer. |
| Interaction model | Good | Standard featuresets are useful, but they are not unrestricted access to the basemap internals. |
| Performance controls | Good | Scene cost still depends on sources, layers, vertices, effects, viewport, and GPU. |
| Pricing predictability | Reasonable | Map loads are simple to count, but traffic is still a meter. |
| Ownership and portability | Weak | The current SDK license and hosted style tie the best path to Mapbox. |
The short verdict is 8/10 for teams that want a polished map product quickly, and much lower for teams whose first requirement is “we must own and host every piece.” Those are not contradictory scores; they describe two different jobs.
What Mapbox GL JS actually does
A normal website receives HTML and paints boxes. A Mapbox scene is closer to a tiny game renderer that happens to know where Broadway is; it continuously chooses visible tiles, decodes geometry, applies style rules, places labels, constructs 3D objects, and asks the GPU to draw the current camera view.

The four pieces matter because they fail differently:
- Sources provide data, commonly vector tiles, raster tiles, GeoJSON, images, or video. A large nationwide dataset should usually arrive as tiled vector data; one heroic 80 MB GeoJSON response is a cry for help.
- Layers describe how source data should render. A single source can feed several layers, such as a blurred route glow, a sharp route core, and clickable stops.
- The style combines Mapbox's basemap with those sources, layers, fonts, colors, lighting, terrain, and configuration.
- The camera decides the center, zoom, pitch, and bearing; most of the cinematic effect comes from this rectangle of numbers, which is humbling for the cinema industry.
This division is one reason Mapbox feels powerful instead of merely pretty. Your data remains data, presentation remains a style layer, and the camera can move independently; you can update one route without rebuilding the basemap, or recolor a layer without replacing its source.
Building the tiny expensive city
I made a zero-build 3D City Review Lab rather than reviewing Mapbox from a landing page. It contains five camera presets for Lower Manhattan, Midtown Manhattan, San Francisco, Tokyo, and Singapore; every city gets a neon route, selectable lighting, rain or snow, 3D-object and facade toggles, label controls, a six-second camera move, and JSON evidence export.

mapbox-3d-city-evidence.json and the reproducible capture script.The implementation is plain HTML, CSS, and JavaScript loaded against the exact 3.26.0 CDN build. That is intentional because a renderer review should not require readers to disentangle Mapbox behavior from React state, a bundler, six UI packages, and whatever spiritual event happened inside node_modules.
After style.load, the lab adds its route and registers building interactions; after the map reaches its first idle event, it enables controls and records elapsed time. Those events are different: load means the map has loaded enough to operate, while idle means requested transitions have finished and currently necessary resources have loaded.
The camera presets use pitch values around 72 to 73 degrees and zoom values around 15.4. Pitch creates the skyline; bearing stops the street grid from looking like a tax form, while a slow flyTo adds the exact amount of motion required for someone to call a map “immersive” in a meeting.
map.flyTo({
center: [-74.0109, 40.7076],
zoom: 16.15,
pitch: 79,
bearing: 143,
duration: 6500,
curve: 1.55,
speed: 0.45,
essential: true
});The essential flag tells Mapbox that the transition should run even when the user prefers reduced motion, so you should not copy it thoughtlessly. A cinematic button the user explicitly presses is one thing; an unavoidable six-second aerial attack on page load is another. For a production interface, I would respect reduced motion by default and offer the dramatic move as an opt-in control.
Mapbox Standard is the cheat code
Mapbox Standard is the default style family built for GL JS 3.x, and it is the main reason the result appears so quickly. The style supports 3D buildings, landmark models, trees, terrain, dynamic lighting, shadows, ambient occlusion, configurable labels, and a growing set of facade details; Mapbox maintains and updates the basemap underneath your application.
Runtime configuration is unusually pleasant. Changing night to dawn does not require fetching an entirely different style, because the app can change the imported basemap's configuration:
map.setConfigProperty("basemap", "lightPreset", "dawn");
map.setConfigProperty("basemap", "show3dObjects", true);
map.setConfigProperty("basemap", "show3dFacades", false);
map.setConfigProperty("basemap", "showRoadLabels", false);The official Standard style guide documents the four light presets -- dawn, day, dusk, and night -- plus the basemap configuration model. This is a much better product API than making developers hunt for 19 unnamed building layers and update paint properties one at a time.
The “epic city” part is therefore not really a 3D modeling accomplishment; it is product leverage. Mapbox has already assembled geographic data, styling, landmark assets, shaders, and renderer behavior into one hosted style, while your application selects a mood and gets on with its actual job.
The Standard style also builds a wall around itself
The same abstraction that saves time also limits control. Standard is imported into your style as a basemap, so its exposed configuration is the supported customization surface; you cannot assume that every internal layer ID is stable, queryable, filterable, or yours to rearrange.
This matters when a designer asks for something more specific than “purple buildings.” If they want one class of minor road below a custom polygon, another class above it, a private color formula on every landmark, and a stable query against internal building layers, you may discover that the convenient vending machine has several knobs painted directly onto the metal.

Custom layers enter Standard through named slots, including bottom, middle, and top; the slot determines the layer's broad visual position without coupling your app to the basemap's internal layer ordering. This is stable and sensible, but it is coarser than placing a custom layer next to any specific internal layer ID.
Feature inspection follows a similar rule. Standard exposes featuresets for interactions, so the review lab targets the buildings featureset from the basemap import:
map.addInteraction("review-building-click", {
type: "click",
target: { featuresetId: "buildings", importId: "basemap" },
handler: ({ feature }) => {
map.setFeatureState(feature, { select: true });
}
});That works for exposed building features, but detailed 3D landmark models are not ordinary building extrusions inside the same featureset. If your product depends on selecting every visible mesh, inspecting arbitrary basemap internals, or freezing the exact cartographic representation forever, Standard deserves a proof of concept before it deserves your architecture.
Mapbox also updates Standard over time. Most product teams will appreciate getting improvements without rebuilding a style; regulated screenshots, pixel-sensitive tests, or long-lived visual archives may prefer a less automatic relationship with the basemap.
Custom data is where Mapbox becomes a real tool
A city that only looks good is a screensaver. The renderer becomes useful when your own routes, parcels, stores, vehicles, hazards, sensors, or questionable startup thesis can live in the same coordinate system.
The lab adds one GeoJSON line source and renders it twice. The lower layer is wide, blurred, translucent, and emissive; the upper layer is narrow and bright, which creates a cheap neon tube without an image asset:
map.addSource("review-route", {
type: "geojson",
lineMetrics: true,
data: route
});
map.addLayer({
id: "review-route-glow",
type: "line",
source: "review-route",
slot: "middle",
paint: {
"line-width": ["interpolate", ["linear"], ["zoom"], 11, 2, 16, 14],
"line-blur": 9,
"line-opacity": 0.55,
"line-emissive-strength": 1.8,
"line-gradient": [
"interpolate", ["linear"], ["line-progress"],
0, "#40e0ff", 0.5, "#8a5cff", 1, "#ff4fd8"
]
}
});Mapbox expressions are one of the best parts of the library. Instead of recalculating styles in application code whenever zoom or feature data changes, you can encode data-driven and camera-driven behavior in a JSON expression that the renderer evaluates. They look strange for the first hour, then regular CSS begins to feel insufficiently mathematical.
GeoJSON is perfect for this small route, but not every dataset belongs in one browser object. Mapbox's performance guidance recommends reducing sources, layers, and vertices; it also recommends vector tiles for large static data and feature state when you only need to change a feature's appearance. The practical rule is simple: prototype with GeoJSON, then tile data whose size or geographic scope makes the browser sigh audibly.
Cameras, weather, and other responsible engineering decisions
Mapbox's camera API is mature enough that the main risk is taste. jumpTo, easeTo, and flyTo cover immediate changes, ordinary transitions, and zooming flight paths; padding and bounds support product layouts where a sidebar occupies half the viewport and the map still needs to frame an object correctly.
The lab also exercises the current setRain and setSnow APIs. Rain accepts density, intensity, opacity, direction, droplet size, distortion, and vignette controls; snow has similar density, direction, flake size, opacity, and vignette properties.
These effects are extremely effective at making a city feel like a scene instead of a diagram, although they do not turn weather into data. If your map says it is raining because the user selected “rain” from a dropdown, you have built atmosphere; if it says it is raining because a timestamped weather source reports precipitation at that coordinate, you have built a weather layer. The shader cannot tell the difference, but your users probably should.
Likewise, terrain and 3D buildings create depth without creating ground truth. A cinematic map can communicate scale beautifully while also hiding labels, occluding routes, exaggerating visual salience, or implying a level of precision the data does not have. I would use the dramatic view for exploration and storytelling, then provide a calmer camera or 2D mode for exact comparison.
Performance without benchmark cosplay
Mapbox performance is not one number because the renderer's work changes with the viewport, device-pixel ratio, GPU, browser, camera, visible tiles, source count, layer count, vertex count, terrain, 3D objects, weather, and whatever your application does on the main thread. Quoting “60 FPS” without those fields is not a benchmark; it is a horoscope for graphics cards.
The official performance model separates render time, source update time, and layer update time. At a high level, render cost grows with sources, layers, and vertices; updating a source also pays for the layers using it, while updating a layer pays for the vertices in its source. That is why merging compatible layers, combining sources, simplifying geometry, tiling large datasets, and avoiding unnecessary data replacement can matter more than micro-optimizing a button handler.
The review lab records two deliberately narrow measurements:
- Time from map construction to the first
idleevent. requestAnimationFrameintervals during one six-secondeaseTo, reported as average frames per second, median frame time, 95th-percentile frame time, and longest frame time.
The exported JSON also carries the library version, user agent, WebGL renderer string when available, viewport, device-pixel ratio, city, light preset, weather, scene toggles, errors, and final camera. Its schema makes those fields harder to quietly lose when a flattering number wanders into a slide deck.
This is still not a comparative renderer benchmark. It is a smoke test for one scene on one machine, and its main value is catching regressions after changing the scene. A serious evaluation should run the same camera path several times on representative low, middle, and high-end devices; it should separate cold cache from warm cache, log long tasks and memory behavior, and compare a controlled scene against the alternative renderer under consideration.
The live automated run makes the limitation wonderfully obvious. Chrome reported ANGLE with a SwiftShader Vulkan device, reached first idle in 24.87 seconds, and emitted only two sampled frames during a 6.13-second camera interval; the resulting 0.33 FPS is a measurement of software rendering inside this automated session, not a useful estimate for a reader's hardware-accelerated browser. I am publishing the raw JSON because a terrible result with its environment attached is more informative than a beautiful number with its environment removed.
Tokens, pricing, and the tiny meter behind the skyline
Mapbox requires an access token. Client-side web apps use public tokens beginning with pk., while secret tokens begin with sk. and do not belong in browser code; Mapbox's token documentation recommends minimum scopes and URL restrictions for public tokens.
A public token is not treated like a server password, but it is still an account credential tied to usage and permissions. Restrict production tokens to your real origins, separate development and production, rotate a leaked or over-broad token, and never solve a frontend error by pasting an sk. token into JavaScript like a small security piñata.
Mapbox GL JS is priced by map loads. According to the current pricing page, the monthly tiers are:
| Monthly Mapbox GL JS map loads | Current price |
|---|---|
| Up to 50,000 | Free |
| 50,001 to 100,000 | $5.00 per 1,000 |
| 100,001 to 200,000 | $4.00 per 1,000 |
| 200,001 to 1,000,000 | $3.00 per 1,000 |
| 1,000,001 to 5,000,000 | $2.50 per 1,000 |
| More than 5,000,000 | Contact Mapbox |
Mapbox's GL JS pricing guide says a load begins when a Map object is initialized; that map can then receive unlimited interactions, and a session ends after 12 hours. This model is friendlier than charging for every pan, zoom, or tile, although an app that repeatedly destroys and recreates maps can create extra billable loads along with extra work.
For a normal product, I would estimate monthly map initializations rather than page views, add traffic growth and bot behavior, then instrument actual map creation. At 40,000 loads, the line item is zero; at 300,000, the first 50,000 are free and the remaining bands price progressively, so you should use the official calculator instead of multiplying all loads by the highest visible tier.
The more important pricing question is not whether the first 50,000 are free; it is whether the map creates enough product value that paid traffic is acceptable. A logistics dashboard, property product, travel application, or live operations tool may have a very clear answer, while decorative maps on viral landing pages can become a surprisingly photogenic cost center.
The license question and MapLibre
Mapbox GL JS version 1 was open source, but versions 2 and 3 use the Mapbox Web SDK license and Mapbox terms. The current repository states that use requires a Mapbox account and compliance with those terms; this is commercial software backed by hosted Mapbox services, even though the source is visible on GitHub.
MapLibre GL JS is the open-source fork and uses the BSD 3-Clause license. It renders interactive vector-tile maps with WebGL and supports features including globe views, terrain, building extrusions, and custom 3D layers; you can pair it with your own or third-party tile and style infrastructure.
That does not make MapLibre a drop-in copy of the complete Mapbox experience. Mapbox Standard, its hosted tiles, detailed landmarks, global basemap work, token system, and commercial support are a service bundle; moving renderers does not magically recreate those assets. The honest comparison is therefore not “paid library versus free library,” because hosting data, operating tiles, sourcing places, maintaining styles, and supporting browsers all cost something.
Use Mapbox when you want the maintained bundle and accept its commercial boundary. Use MapLibre when an open renderer, self-hosting, provider choice, or architectural independence is central enough that you are willing to assemble more of the stack; evaluate both with one real scene and one real dataset before turning license preference into a performance claim.
Who should actually use Mapbox
Mapbox is a strong fit when the map is a major product surface and your team needs to ship geospatial interaction without first becoming a cartography company. The best cases include live fleet views, travel planning, property search, operations dashboards, store or venue exploration, route visualization, incident maps, and data stories where camera and style genuinely help comprehension.
It is especially compelling when three conditions are true at once:
- You want excellent default global cartography and 3D presentation.
- Your application adds meaningful proprietary data or interaction on top.
- Map-load pricing and a hosted dependency are acceptable business constraints.
I would hesitate when the map is only decorative, because a static image or simpler embed may do the job with less JavaScript and less billing surface. I would also hesitate when every internal style detail must be controlled, when offline or air-gapped use is mandatory, when a procurement rule requires an open-source renderer, or when provider portability is more important than the speed of Mapbox Standard.
For the 3D-city use case specifically, the best product pattern is not “show every effect.” Start with one useful layer, one camera that frames it, and one clear interaction; add lighting or 3D only when depth improves the task, then keep the snow machine somewhere the user can turn it off.
Reproduce the review lab
The complete artifact is in demo/, and it has no package installation step:
cd demo
python -m http.server 4173Open http://127.0.0.1:4173, paste a URL-restricted public pk. token, then try the five city presets. The token stays in page memory; the lab does not save it, place it in the URL, or include it in evidence export.
Run the static checks separately:
node --check demo/app.js
node demo/verify-demo.mjsThe verifier checks the pinned Mapbox version, Standard style, 3D configuration, rain and snow APIs, route slot, emissive line, building featureset interactions, evidence export, five review scenes, and absence of browser-storage token persistence. The review-protocol.json states the review axes and explicitly lists what the six-second frame sample does not prove.
After the map reaches idle, click Measure 6 seconds, then Export evidence. Keep the resulting JSON beside any screenshot or performance claim; the city might be fictional in spirit, but the test conditions do not need to be.
The checked-in capture-evidence.mjs automates the same flow with Playwright when MAPBOX_PUBLIC_TOKEN contains a public token. It writes the screenshot and JSON without writing the token; the application also redacts token-like strings from recorded Mapbox error messages before export.
FAQ
Is Mapbox GL JS free?
Mapbox currently includes up to 50,000 GL JS map loads per month at no charge, then applies progressive usage pricing. You still need an account and token, while other Mapbox APIs or products can have separate meters.
Does Mapbox automatically provide 3D buildings?
Mapbox Standard can display 3D buildings, landmarks, trees, terrain, and related lighting details through configuration, although richness and model coverage vary by location. Your own data layers and application interactions remain your responsibility.
Can I make a cyberpunk city with Mapbox?
Yes. A custom Standard theme, night lighting, steep camera pitch, hidden labels, emissive lines, restrained weather, and a slow camera move produce the look quickly; the review lab includes that exact setup. The harder problem is making the view communicate something beyond “our route is purple now.”
Is a Mapbox public token safe in frontend code?
Public pk. tokens are designed for client use, but they should have minimum scopes and URL restrictions. Never put a secret sk. token in frontend code, source control, screenshots, or this demo.
Is Mapbox GL JS open source?
Current Mapbox GL JS versions use Mapbox's Web SDK license and require a Mapbox account under its terms. MapLibre GL JS is the BSD-licensed open-source fork and is the main alternative when renderer ownership or provider independence matters.
Is Mapbox better than MapLibre?
Mapbox offers a more integrated hosted product, especially with Mapbox Standard and its data assets; MapLibre offers an open renderer and greater infrastructure choice. “Better” depends on whether speed and bundled services or ownership and portability are the harder requirement.
Sources and artifact map
- Mapbox GL JS repository and current license
- Mapbox Standard style reference
- Mapbox Standard style guide and customization limits
- Mapbox GL JS v3 migration guide
- Official 3D building example
- Runtime Standard configuration example
- Standard building featureset interaction example
- Sources, layers, and Standard slots
- Mapbox GL JS performance guidance
- Supported browsers and WebGL requirements
- Mapbox access-token guidance and security guidance
- Mapbox pricing and GL JS map-load rules
- MapLibre GL JS repository and project overview
- Reproducible review lab, application source, capture script, live screenshot, runtime evidence, static verifier, review protocol, and runtime evidence schema
- Immutable public five-city lab mirror, v1.0.0 release, and citation metadata
Conclusion
Mapbox GL JS can turn Manhattan into a video game cutscene with very little code because Mapbox has already done an enormous amount of work; Standard brings the map, building data, landmark models, lighting, labels, shaders, and configuration surface, while you provide a token, a camera, and enough purple to alarm a cartographer.
That leverage is the whole review. It is genuinely excellent when you want to put an impressive, data-rich geographic product in front of users quickly; the same convenience becomes a constraint when you need unrestricted basemap internals, self-hosting, an open license, or freedom from map-load pricing.
I would use it for a product whose map matters, especially when my own data and interactions justify the renderer. I would not import a small commercial geography platform merely to put animated rain behind a contact page; even I have limits, and apparently they begin one snow shader after the neon highway.