Integrating Custom WebGL Content into Mapbox GL JS via Custom Layers
Learn how to integrate raw WebGL content into Mapbox GL JS using custom layers. This guide covers the lifecycle methods onAdd, render, and onRemove to create high-performance 3D visualizations.
09 Mar 2026, 01:53 UTC

The Problem: Beyond Standard Style Layers
Standard Mapbox GL JS layers (like fill, line, or circle) are limited to the Mapbox Style Specification. When you need to render complex 3D geometry, custom particle systems, or high-performance data visualizations that require raw GPU access, these built-in layers are insufficient. The solution is the Custom Layer, which allows you to inject a WebGL render loop directly into the map's painting process.
Prerequisites
- Mapbox GL JS v2.x or v3.x installed in your project.
- A valid Mapbox Access Token.
- Basic familiarity with GLSL (OpenGL Shading Language) for writing vertex and fragment shaders.
- A map instance initialized and loaded.
Implementing the Custom Layer Lifecycle
A custom layer is a JavaScript object that must implement specific lifecycle methods. Mapbox calls these methods to synchronize your WebGL content with the map's camera and zoom level.
1. The onAdd Method
The onAdd method runs once when the layer is added to the map. Use this to compile shaders and initialize GPU buffers. This is where you define the visual properties of your object.
onAdd: function (map, gl) {
// Compile shaders and create a program
this.program = createProgram(gl, vertexSource, fragmentSource);
// Initialize buffers for your geometry
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}2. The render Method
The render method is called every frame. It provides a matrix (a 4x4 projection matrix) that converts geographic coordinates into the current WebGL clip space. You must pass this matrix to your vertex shader to ensure your content stays anchored to the map during panning and zooming.
render: function (gl, matrix) {
gl.useProgram(this.program);
// Pass the Mapbox projection matrix to the shader
gl.uniformMatrix4fv(this.matrixLocation, false, matrix);
// Draw the geometry
gl.drawArrays(gl.TRIANGLES, 0, 3);
}3. The onRemove Method
To prevent memory leaks, you must manually delete WebGL resources. If you remove a layer without cleaning up buffers and programs, the GPU memory will not be reclaimed, eventually crashing the browser tab.
onRemove: function (map, gl) {
gl.deleteBuffer(this.buffer);
gl.deleteProgram(this.program);
}Managing Layer Depth and Z-Index
Custom layers do not follow the same z-index rules as standard layers. To control whether your WebGL content appears above or below existing map features (like roads or buildings), use the beforeId parameter in addLayer().
| Placement Goal | Implementation Strategy |
|---|---|
| Below Labels | Set beforeId to the ID of the first label layer in the style. |
| Above Everything | Omit beforeId to place the layer at the top of the stack. |
| Between Landuse and Roads | Set beforeId to the ID of the road layer. |
Practical Example: Anchoring a 3D Object
To place a custom object at a specific longitude and latitude, use map.project() to convert the coordinates to pixel space, or handle the transformation within the vertex shader using the provided matrix. For a static 3D point, the vertex shader typically looks like this:
attribute vec2 a_pos;
uniform mat4 u_matrix;
void main() {
// The u_matrix handles the conversion from map coords to screen space
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
}Verification and Diagnostics
- Coordinate Stability: Zoom in and out rapidly. If the object "drifts" away from its geographic location, verify that the
matrixprovided in therendermethod is being passed correctly to the shader. - Memory Leak Check: Open Chrome DevTools Memory tab. Add and remove the custom layer repeatedly. If the GPU memory usage climbs steadily without dropping, check your
onRemoveimplementation. - Depth Testing: If the custom layer is invisible, check if it is being rendered behind the
backgroundlayer or if thebeforeIdis targeting a layer that doesn't exist.
Limitations
- Studio Compatibility: Custom layers are code-driven; they cannot be edited or previewed within the Mapbox Studio GUI.
- Performance: Complex shaders running in the
renderloop can drop the map's frame rate. Keep fragment shaders lightweight to maintain smooth 60fps panning.
Rollback Procedure
If the custom layer causes rendering artifacts or performance degradation, remove it from the map instance to restore the default WebGL state:
map.removeLayer('my-custom-webgl-layer');0 replies
A thoughtful contribution can make all the difference. Be the first to share one.