How to Create a Custom Map Style with MapLibre GL
Your Map Should Look Like Your Product
Open ten different websites with maps and you will see the same map ten times: the default palette of a big provider, with someone else's brand baked into every tile. A custom map style fixes that. It turns the map from a third-party widget into a natural part of your interface, with your colors, your contrast and your typography choices.
In this guide we build a branded MapLibre GL style from scratch using open tools: the MapLibre style specification, Protomaps basemap layers and a small build script. No API keys, no per-request billing, and the result is a single JSON file you can host anywhere.
30-SECOND SUMMARY
A MapLibre GL map style is a single JSON document (style spec version 8) that lists sources, layers, fonts and sprites. You do not have to write its 70+ layers by hand: the open source @protomaps/basemaps package generates them from a small color object called a flavor. Start from a named flavor like light or dark, spread your brand colors over it, generate the style with one script, and host the JSON as a static file. Any stock MapLibre GL app can consume it with a single URL.
What a Map Style Actually Is
MapLibre GL renders vector tiles in the browser. What the map looks like is decided entirely by the MapLibre style specification, a JSON document with four important parts:
- sources: where the vector tiles come from (a TileJSON URL or a tile template).
- layers: an ordered list of paint rules. Water is a fill layer, roads are line layers, place names are symbol layers. Order matters: later layers draw on top.
- glyphs: where the font PBF ranges live, used by all text labels.
- sprite: one image atlas with all the small icons (optional but needed for POI icons).
A realistic basemap has around 70 layers. Writing them by hand is possible with a visual editor like Maputnik, but maintaining 70 handwritten layers across data updates is painful. There is a better way.
The Fast Path: Protomaps Flavors
The @protomaps/basemaps package generates the whole layer list from a compact color object called a flavor. Five named flavors ship out of the box: light, dark, white, grayscale and black.
npm install @protomaps/basemaps
import { layers, namedFlavor } from "@protomaps/basemaps";
const style = {
version: 8,
sources: {
protomaps: {
type: "vector",
url: "https://tiles.hititmedya.com/planet.json",
attribution: "Protomaps © OpenStreetMap"
}
},
layers: layers("protomaps", namedFlavor("light"), { lang: "en" }),
glyphs: "https://tiles.hititmedya.com/v1/fonts/{fontstack}/{range}.pbf",
sprite: "https://tiles.hititmedya.com/v1/sprites/v4/light"
};
That is a complete, production-ready basemap style. The interesting part is what comes next.
Making It Yours: a Brand Flavor
A flavor is a plain object where every key is a map feature and every value is a color: water, parks, buildings, road casings, label colors and so on. To brand the map, spread a named flavor and override only what you need:
import { layers, namedFlavor } from "@protomaps/basemaps";
const brandFlavor = {
...namedFlavor("light"),
earth: "#eef1ee",
water: "#b7dcd2",
park_b: "#a8dcbd",
buildings: "#d9ded9",
highway_casing_early: "#a7d9c4",
city_label: "#1f2937",
boundaries: "#8aa397"
};
const brandLayers = layers("protomaps", brandFlavor, { lang: "en" });
Three practical rules we follow when we design flavors at Hitit Medya:
Start from a ramp, not from single colors
Pick your neutral ramp first (background, earth, buildings, roads), then add one accent family. If every feature gets its own hue the map turns into noise.
Respect label contrast
City and road labels must stay readable over every surface they cross. Test label colors against parks, water and dense building areas, not just the background.
Check every zoom level
A style that looks great at zoom 12 may fall apart at zoom 5 where landcover dominates, or at zoom 16 where buildings dominate. Sweep the whole range before shipping.
Special Variants Worth Building
Once the build script exists, extra variants are nearly free and very useful:
- No labels: filter out all symbol layers. Perfect when you draw your own annotations on top.
- Labels only: generate with the labelsOnly option and you get a transparent overlay you can stack above custom data layers.
- Localized labels: pass a different lang value (for example tr) to render place names in another language.
// no labels
const noLabels = layers("protomaps", brandFlavor, { lang: "en" })
.filter((layer) => layer.type !== "symbol");
// transparent label overlay
const labelsOnly = layers("protomaps", brandFlavor, {
lang: "en",
labelsOnly: true
});
Hosting and Using the Style
The output is a static JSON file. Put it on any static host or object storage with CORS enabled, and any MapLibre GL app can use it directly:
const map = new maplibregl.Map({
container: "map",
style: "https://tiles.hititmedya.com/v1/styles/hitit-green.json",
center: [32.85, 39.93],
zoom: 6
});
If you do not want to design your own flavor yet, we publish a gallery of ready-made branded styles with live previews, embed codes and copyable style URLs. Every style is free, with no API key and no request limits.
Browse our ready-made map styles
12 branded MapLibre GL styles with live previews. Copy the embed code or the style URL and ship a custom map today.
All styles are free for commercial use.
Frequently Asked Questions
01Do I need Mapbox to use a custom map style?
No. MapLibre GL is a free, open source fork of Mapbox GL JS and uses the same style specification. Everything in this guide runs on MapLibre with open data, no Mapbox account or token required.
02Can I edit a style visually instead of writing code?
Yes. Maputnik is a free visual editor for the MapLibre style spec: load your generated style, tweak colors layer by layer and export the JSON. We still recommend keeping a build script as the source of truth so updates stay reproducible.
03Where do the fonts and icons come from?
Text labels need glyph PBF files and icons need a sprite atlas. The protomaps/basemaps-assets repository provides both (Noto Sans font stacks and per-flavor sprites) and you can self-host them on any static storage next to your style JSON.
04Does a custom style change what data is on the map?
No. The style only controls how the vector tiles are drawn: colors, line widths, fonts and visibility. The data itself comes from the tile source, which in our setup is an OpenStreetMap extract packaged as PMTiles.
Sources and References
The official reference for every property a map style JSON can contain.
Documentation for the flavor color system used to generate basemap layers.
A free, open source visual editor for MapLibre map styles.