Language guide
Learn the language by example. Every snippet below shows the source and the
exact JSON compile() returns for it.
For the full list of operators, see the operator reference. For the formal grammar, see grammar.md. Want to try snippets as you read? Use the playground.
Values
Numbers, strings, and booleans compile straight to their JSON equivalent.
42
42
3.14
3.14
"hello world"
"hello world"
true
true
true/false are real booleans, not 1/0.
Reading feature properties
get(...) reads a property from the current feature.
get("name")
["get", "name"]
has(...) checks
whether a property exists, without reading it – handy before a get that would
otherwise return null:
has("name_en")
["has", "name_en"]
Arithmetic
+ - * / % ^, with the precedence you’d expect, plus unary -.
1 + 2
["+", 1, 2]
2 + 3 * 4
["+", 2, ["*", 3, 4]]
(2 + 3) * 4
["*", ["+", 2, 3], 4]
-5 + 10
["+", ["-", 5], 10]
2 ^ 10
["^", 2, 10]
Comparisons
== != < <= > >=
get("speed") > 80
[">", ["get", "speed"], 80]
get("type") == "highway"
["==", ["get", "type"], "highway"]
Boolean logic
and / or / not.
get("speed") > 50 and get("speed") < 100
["all", [">", ["get", "speed"], 50], ["<", ["get", "speed"], 100]]
(get("speed") > 80 or get("lane_count") > 2) and get("type") == "highway"
[
"all",
["any", [">", ["get", "speed"], 80], [">", ["get", "lane_count"], 2]],
["==", ["get", "type"], "highway"]
]
and/or/not reject a value that’s provably not a boolean, e.g. 1 and true fails to compile with a clear error – see Errors below.
Variables
var name = expr. Every later line can use it.
var x = 10
x * 2
["let", "x", 10, ["*", ["var", "x"], 2]]
var speed = get("speed")
var isFast = speed > 80
isFast
[
"let",
"speed",
["get", "speed"],
["let", "isFast", [">", ["var", "speed"], 80], ["var", "isFast"]]
]
if / elif / else
else is required, same as Mapbox’s own case.
if get("speed") > 80 then "fast" else "slow"
["case", [">", ["get", "speed"], 80], "fast", "slow"]
if get("speed") > 100 then
"very fast"
elif get("speed") > 50 then
"fast"
else
"slow"
["case", [">", ["get", "speed"], 100], "very fast", [">", ["get", "speed"], 50], "fast", "slow"]
match
Compare one value against several possibilities. A comma-separated list of labels shares one output.
match get("type")
"water" then "blue"
else "gray"
["match", ["get", "type"], "water", "blue", "gray"]
match get("type")
"motorway", "trunk", "primary" then "red"
"secondary", "tertiary" then "orange"
else "gray"
[
"match",
["get", "type"],
["motorway", "trunk", "primary"],
"red",
["secondary", "tertiary"],
"orange",
"gray"
]
Labels can be numbers too:
match get("lanes")
1 then "thin"
2, 3 then "medium"
else "thick"
["match", ["get", "lanes"], 1, "thin", [2, 3], "medium", "thick"]
Functions
Called like you’d expect in any language: name(arg1, arg2, ...). See the
operator reference for the full list, grouped by category
(math, string, color, type conversion).
abs(-42)
sqrt(16)
min(10, max(2, get("size")))
["abs", ["-", 42]]
["sqrt", 16]
["min", 10, ["max", 2, ["get", "size"]]]
concat(get("name"), " - ", get("type"))
upcase(get("name"))
["concat", ["get", "name"], " - ", ["get", "type"]]
["upcase", ["get", "name"]]
rgb(255, 0, 0)
rgba(0, 128, 255, 0.5)
["rgb", 255, 0, 0]
["rgba", 0, 128, 255, 0.5]
toNumber(get("population"))
toBoolean(get("visible"))
["to-number", ["get", "population"]]
["to-boolean", ["get", "visible"]]
coalesce returns the first non-null argument – handy for a property that
might be missing from some features:
coalesce(get("name_en"), get("name"), "Unnamed")
["coalesce", ["get", "name_en"], ["get", "name"], "Unnamed"]
Constants
Zero-argument operators, grouped under a short namespace so they’re easy to
find (math.*, camera.*, feature.*, heatmap.*). See the
operator reference for the full list.
math.pi
math.e
camera.zoom
feature.id
["pi"]
["e"]
["zoom"]
["id"]
interpolate / step
Smoothly (interpolate) or discretely (step) map an input to an output
across a series of stops. Stop inputs must be number literals in strictly
ascending order.
interpolate linear camera.zoom
5 then 2
15 then 20
["interpolate", ["linear"], ["zoom"], 5, 2, 15, 20]
The interpolation type can also be exponential(base):
interpolate exponential(1.5) camera.zoom
5 then 2
15 then 20
["interpolate", ["exponential", 1.5], ["zoom"], 5, 2, 15, 20]
A third option is cubicBezier(x1, y1, x2, y2) – the same easing-curve
shape as CSS’s cubic-bezier() – for eases that aren’t a straight line or
a simple exponential curve:
interpolate cubicBezier(0.42, 0, 0.58, 1) camera.zoom
0 then 0
1 then 1
["interpolate", ["cubic-bezier", 0.42, 0, 0.58, 1], ["zoom"], 0, 0, 1, 1]
interpolateHcl/interpolateLab work the same way, for interpolating
colors in a different color space:
interpolateLab linear camera.zoom
0 then "yellow"
10 then "red"
["interpolate-lab", ["linear"], ["zoom"], 0, "yellow", 10, "red"]
step picks the output for the stop just below the input, with a
default for anything below the first one:
step get("magnitude")
default "small"
3 then "medium"
6 then "large"
["step", ["get", "magnitude"], "small", 3, "medium", 6, "large"]
Putting it together
A more realistic example, combining variables, boolean logic, if/elif,
and interpolate:
var speed = get("speed")
var type = get("type")
var isHighway = type == "motorway" or type == "trunk"
if isHighway and speed > 100 then
rgb(255, 0, 0)
elif isHighway then
rgb(255, 165, 0)
else
interpolate linear speed
0 then rgb(200, 200, 200)
120 then rgb(0, 200, 0)
[
"let",
"speed",
["get", "speed"],
"type",
["get", "type"],
[
"let",
"isHighway",
["any", ["==", ["var", "type"], "motorway"], ["==", ["var", "type"], "trunk"]],
[
"case",
["all", ["var", "isHighway"], [">", ["var", "speed"], 100]],
["rgb", 255, 0, 0],
["var", "isHighway"],
["rgb", 255, 165, 0],
[
"interpolate",
["linear"],
["var", "speed"],
0,
["rgb", 200, 200, 200],
120,
["rgb", 0, 200, 0]
]
]
]
]
A second one: deriving a metric that doesn’t exist as its own property – here, population density from two raw fields – before ramping it through a color scale.
var density = get("population") / get("area_km2")
interpolate linear density
0 then rgb(255, 255, 255)
1000 then rgb(200, 0, 0)
[
"let",
"density",
["/", ["get", "population"], ["get", "area_km2"]],
[
"interpolate",
["linear"],
["var", "density"],
0,
["rgb", 255, 255, 255],
1000,
["rgb", 200, 0, 0]
]
]
Using with TypeScript
compile() returns a general JSON-value type, not one tied to the specific
Mapbox/MapLibre paint or layout property you’re assigning it to – there’s
no way to know that ahead of time from a string of source. Libraries like
@types/mapbox-gl type each property narrowly (e.g.
DataDrivenPropertyValueSpecification<number> for circle-radius), so
assigning a compiled expression directly will fail to typecheck. One cast
at the call site is the fix, same as you’d need for a hand-written
expression array:
import { compile } from "mapbox-expr-lang";
import type { DataDrivenPropertyValueSpecification } from "mapbox-gl";
map.setPaintProperty(
"quakes",
"circle-radius",
compile(`
interpolate linear get("mag")
1 then 3
8 then 38
`) as unknown as DataDrivenPropertyValueSpecification<number>,
);
Errors
compile() throws with a message that says exactly what’s wrong:
1 and true
'and' requires a boolean operand, but this is a number
typof(5)
Unknown function "typof"
unknownVar + 1
Variable "unknownVar" is not defined
min()
"min" expects at least 1 argument, but got 0
Interpolate/step stops are checked for ascending order at compile time, the same rule Mapbox enforces at evaluation time – just caught earlier, with a precise position:
interpolate linear camera.zoom
10 then 1
5 then 2
Stop inputs must be in strictly ascending order, but 5 does not come after 10
import { compile } from "mapbox-expr-lang";
try {
compile("1 and true");
} catch (error) {
console.error(error.message);
// 'and' requires a boolean operand, but this is a number
}