In the Mapbox Standard Style, the complexity of its layer styling is abstracted away — you can configure it with predefined variables, but you can't directly edit its layers. This is by design, and allows you to benefit from continuous improvements by our map designers. So what do you do when you need a label to say something different, appear at a different zoom level, or simply not appear at all?
Here's a technique for surgically replacing a single label with your own, while keeping everything else in the Standard style untouched and precisely matching the label style. In the embedded example below, we've replaced the label for "New York" with "New Amsterdam". Why they changed it, I can't say. 😉
Using a classic style? If you're on a classic Mapbox style where you have direct access to edit symbol layers, there's a simpler approach: override the label text using an expression directly on the layer. See Customize label text in the Mapbox docs.
This post is specifically about the Standard style, where internal layers are not editable — which requires a different technique.
Step 1: Get the exact coordinates and properties of the label feature
Most map labels come from the place_label source layer in the Mapbox Streets tileset. Here's a simple map that shows only the place_label source layer's points (and water features for reference). Click a label and you will see its full data, including the coordinates and properties:
Copy the full GeoJSON Feature. Fields like symbolrank, filterrank, class, worldview, text_anchor, and capital all control how the label is styled and at which zoom levels it appears. You'll need to match these when building your replacement feature.
Step 2: Build a custom style that imports Standard
Instead of loading the Standard style directly, create a top-level style JSON that imports Standard and adds your own source alongside it:
const map = new mapboxgl.Map({
style: {
version: 8,
sprite: 'mapbox://sprites/mapbox/standard/...',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
sources: {
'my-label': {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [-74.006, 40.713] },
properties: {
name: 'My Custom Label',
name_en: 'My Custom Label',
filterrank: 1,
class: 'settlement',
worldview: 'all',
symbolrank: 6,
text_anchor: 'left',
capital: 0
}
}]
}
}
},
layers: [ /* your custom layer — see step 3 */ ],
imports: [{ id: 'basemap', url: 'mapbox://styles/mapbox/standard' }]
}
});
The sprite and glyphs URLs must point to the Standard style's assets. Without them, the dot icons and fonts used by settlement labels won't resolve.
Step 3: Add a layer that mirrors the Standard style's styling
The Standard style renders city labels using layers like settlement-major-label and settlement-minor-label. You can inspect these by calling map.getStyle() on a Standard style map. They contain complex Mapbox Style expressions to support config properties like theme, font, and lightPreset.
For a simple replacement, you can strip out that complexity and hardcode the resolved values for your target preset. Here's what settlement-major-label looks like for a symbolrank=6, non-capital feature in the default day preset:
{
id: 'settlement-major-label',
type: 'symbol',
source: 'my-label',
minzoom: 2,
maxzoom: 15,
filter: ['step', ['zoom'], false, 2, true, 13, false],
layout: {
'text-size': ['interpolate', ['cubic-bezier', 0.2, 0, 0.9, 1], ['zoom'],
3, 11, 6, 16, 8, 20, 15, 24],
'text-radial-offset': ['step', ['zoom'], 0.55, 8, 0],
'icon-image': ['step', ['zoom'],
['image', 'dot-11', { params: { 'color-primary': 'hsl(0,0%,0%)', 'color-secondary': 'hsla(0,0%,100%,1)' } }],
8, ''],
'text-font': ['DIN Pro Medium', 'Arial Unicode MS Bold'],
'text-anchor': ['step', ['zoom'], ['get', 'text_anchor'], 8, 'center'],
'text-justify': ['step', ['zoom'],
['match', ['get', 'text_anchor'], ['left', 'bottom-left', 'top-left'], 'left',
['right', 'bottom-right', 'top-right'], 'right', 'center'], 8, 'center'],
'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']],
'text-max-width': 7
},
paint: {
'text-color': 'hsl(0, 0%, 0%)',
'text-halo-color': 'hsla(0, 0%, 100%, 1)',
'text-halo-width': 1,
'text-halo-blur': 1
}
}
The renderer's symbol placement engine will suppress the original label when your replacement occupies the same location — in most cases no extra work is needed. If the original label bleeds through, you can add a small clip layer around the point to suppress it explicitly.
Caveats
This layer is yours to maintain. The Mapbox Standard style is actively maintained and updated. If Mapbox changes how settlement labels are styled in a future release, your custom layer won't automatically follow. You'll need to re-inspect the internal layers and update your config to match.
Hardcoded values won't honor Standard style config. By resolving ["config", "font"], ["config", "lightPreset"], etc. to their defaults, you've opted out of those customization hooks. If you want your label to respond to theme or light preset changes, bring in the full layer config and schema instead of hardcoding values.
Label placement is context-dependent. The text_anchor value in the tileset is computed based on surrounding label density at tile generation time. In isolation, your label will use the correct anchor direction, but the exact placement may look slightly different than it would surrounded by other labels.
Top comments (0)