Painting
Ops that consume Features and put pixels on a canvas — flat fills, scatter dabs, brush strokes, stamps, text.
13 ops: brush-file, circles, fill-dabs, fill-solid, label-boxes, label-placement, line, line-stamp, stamp, stroke, text, text-draw, text-labels.
Ops with no demo image below produce no visible result on their own — they feed other nodes rather than paint.
brush-file
Section titled “brush-file”Brush source. src is an @asset ref or a literal path/name resolved by the host’s AssetLoader.
| Field | Type | Required | Description |
|---|---|---|---|
src |
string | yes | Asset reference (@name) or literal path. |
circles
Section titled “circles”Filled disks at every feature point (tiny-skia), with per-feature radius, color, opacity, and optional stroke ring. The crisp vector counterpart to MapLibre’s circle layer. Each paint property has an optional *-expr MapLibre-expression sibling evaluated per feature group.

| Field | Type | Required | Description |
|---|---|---|---|
color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
color-expr |
— | no | A MapLibre color expression (JSON array, e.g. [“match”, [“get”, “c”], “a”, “#f00”, “#0f0”]), evaluated per feature group; overrides the constant color. |
features |
@node |
yes | Reference to another node (@name). |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-expr |
— | no | A MapLibre number expression giving opacity, evaluated per feature group; multiplies both fill and stroke alpha. Overrides the constant opacity. |
radius |
number (0…∞) | $param | @node |
no | — |
radius-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides the constant radius. |
stroke-color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
stroke-color-expr |
— | no | A MapLibre color expression for the stroke ring, evaluated per feature group; overrides the constant stroke-color. |
stroke-width |
number (0…∞) | $param | @node |
no | — |
stroke-width-expr |
— | no | A MapLibre number expression (px) for the stroke ring, evaluated per feature group; overrides the constant stroke-width. A ring is drawn only when width > 0. |
fill-dabs
Section titled “fill-dabs”Watercolor scatter-dab fill with world-deterministic jitter (seamless across tiles).

| Field | Type | Required | Description |
|---|---|---|---|
color |
color | $param | @node |
yes | sRGB hex color, $param reference, or @node scalar port. |
features |
@node |
yes | Reference to another node (@name). |
hardness |
number (0…1) | $param | @node |
no | — |
opacity |
number (0…1) | $param | @node |
yes | — |
opacity-jitter |
number (0…1) | $param | @node |
no | — |
paint |
number (0…1) | $param | @node |
no | — |
position-jitter |
number (0…1) | $param | @node |
no | — |
radius-px |
number (0…∞) | $param | @node |
yes | — |
size-jitter |
number (0…1) | $param | @node |
no | — |
spacing-px |
number (0…∞) | $param | @node |
yes | — |
value-jitter |
number (0…1) | $param | @node |
no | — |
fill-solid
Section titled “fill-solid”Solid polygon fill with optional outline and Gaussian blur.

| Field | Type | Required | Description |
|---|---|---|---|
blur-sigma |
number (0…∞) | $param | @node |
no | — |
blur-sigma-max |
number (0…∞) | no | Upper bound on blur-sigma for padding, required when blur-sigma is an @node port. Values above it are clamped. |
edge |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
edge-width |
number (0…∞) | $param | @node |
no | — |
features |
@node |
yes | Reference to another node (@name). |
fill |
color | $param | @node |
yes | sRGB hex color, $param reference, or @node scalar port. |
fill-alpha |
number (0…1) | $param | @node |
no | — |
fill-expr |
— | no | A MapLibre color expression (JSON array, e.g. [“match”, [“get”, “class”], “water”, “#88c”, “#ccc”] or [“interpolate”, [“linear”], [“get”, “area”], 0, “#eef”, 1000, “#049”]), evaluated per feature group at paint time. When present it overrides the constant fill; a group whose expression doesn’t resolve to a color is not painted. Unlocks continuous data-driven fills that constant fill can’t express. |
opacity-expr |
— | no | A MapLibre number expression (JSON array) giving fill opacity, evaluated per feature group at paint time. When present it overrides the constant fill-alpha; a group whose expression doesn’t resolve to a number falls back to fill-alpha. |
label-boxes
Section titled “label-boxes”The boxes a label-placement node measured its labels by, as polygons — one per box — so any geometry and paint op can draw them: a bordered or rounded badge (buffer → stroke), an offset shadow (transform), or a debug overlay of placement. Reads the decisions already made, so nothing is placed twice and the boxes match what text-draw draws; labels placed from a neighbour tile’s candidates are included, so boxes continue across tile edges. Each polygon carries the labelled feature’s properties plus part (text, icon, background, glyph or collision), placed (false for a candidate that did not place, with include: all) and label-id (shared by every box of one label, the same on every tile). A placed label also carries overlapped (true when it overlaps a label placed before it, which only overlap: always or fallback permits) and variant-index: where the position it took sits in the list it was tried from (anchor-offsets, or the anchors of the deprecated anchor-variants), 0 for the first position, for a lone one and for a line label, and absent when the variant shown draws no text. A label that did not place carries drop-reason instead: collision when it lost its collision test, dedupe when its layer’s dedupe-key-expr dropped it because a label with the same key had already placed within dedupe-distance-px. A point label gives its text block (no padding), icon (rotated by icon-rotate), background box (with background-padding or background-color) and the collision box or boxes it was tested with, padding included. A line label gives one glyph quad per glyph, rotated along the path, and the axis-aligned collision box placement tested for each glyph.
| Field | Type | Required | Description |
|---|---|---|---|
extent |
integer (1…∞) | no | Coordinate extent of the emitted geometry, matching the features it is combined with. |
filter-expr |
— | no | A MapLibre filter expression (JSON array, e.g. [“>”, [“coalesce”, [“get”, “variant-index”], 0], 0]), evaluated per box against every property the box carries, part included, with geometry-type Polygon. A box is emitted only when the expression is truthy; one whose expression errors is dropped. Picks out, say, the labels that moved off their first position, to draw a leader line to their feature. |
include |
placed | all |
no | placed emits the labels that placed; all adds the candidates that lost their collision test or were dropped by dedupe-key-expr, with placed: false, at their first anchor. Duplicate copies of one label are never emitted. |
labels |
@node |
no | Only this text-labels layer of the placement (it must be wired into the placement node too). Default: every layer. |
parts |
array of text | icon | background | glyph | collision |
no | Which boxes to emit. Default: every kind a label has. |
placement |
@node |
yes | Reference to another node (@name). |
label-placement
Section titled “label-placement”Place the labels of every text-labels layer against one shared collision index, the way maplibre-gl-js does — so a POI label can knock out an overlapping road name. labels lists the layers bottom-first (paint order, as in stack); priority is top-down, so the last entry places first and wins. Feed the result to each layer’s text-draw node.
| Field | Type | Required | Description |
|---|---|---|---|
labels |
array of @node |
yes | text-labels layers, bottom first. The topmost layer places first. |
Brush stroke along MVT polylines.
| Field | Type | Required | Description |
|---|---|---|---|
brush |
@node |
yes | Reference to another node (@name). |
color |
color | $param | @node |
yes | sRGB hex color, $param reference, or @node scalar port. |
dtime |
number (0…∞) | $param | @node |
no | — |
dtime-stroke-curve |
array of array of number | string | no | Piecewise-linear [[t, y], ...] multiplier on dtime. y scales the per-vertex dtime — y=3 makes the brush linger 3× longer (slower hand), y=0.3 sweeps through 3× faster. Used with dynamics-driven brushes that respond to stroke speed. |
features |
@node |
yes | Reference to another node (@name). |
hardness-stroke-curve |
array of array of number | string | no | Piecewise-linear [[t, y], ...] driving a libmypaint stroke input on the brush. t is normalized stroke progress in [0, 1]; y is an offset added to the setting’s base value. radius is log-space (y=-2.3 ≈ ×0.1, y=+0.69 ≈ ×2); opaque and hardness are linear. |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-stroke-curve |
array of array of number | string | no | Piecewise-linear [[t, y], ...] driving a libmypaint stroke input on the brush. t is normalized stroke progress in [0, 1]; y is an offset added to the setting’s base value. radius is log-space (y=-2.3 ≈ ×0.1, y=+0.69 ≈ ×2); opaque and hardness are linear. |
pressure-base |
number (0…1) | $param | @node |
no | — |
pressure-jitter |
number (0…1) | $param | @node |
no | — |
radius-px |
number (0…∞) | $param | @node |
no | — |
radius-stroke-curve |
array of array of number | string | no | Piecewise-linear [[t, y], ...] driving a libmypaint stroke input on the brush. t is normalized stroke progress in [0, 1]; y is an offset added to the setting’s base value. radius is log-space (y=-2.3 ≈ ×0.1, y=+0.69 ≈ ×2); opaque and hardness are linear. |
line-stamp
Section titled “line-stamp”Repeat a sprite along each polyline, rotated to the line’s tangent (MapLibre line-pattern). Points/polygons ignored. width-px scales the image height to the stroke width; spacing-px sets the advance (0 = the scaled image width, seamless).
| Field | Type | Required | Description |
|---|---|---|---|
features |
@node |
yes | Reference to another node (@name). |
image |
@node |
yes | Reference to another node (@name). |
opacity |
number (0…1) | $param | @node |
no | — |
scale |
number (0…∞) | $param | @node |
no | Extra uniform scale on top of the width fit. Default 1.0. |
spacing-px |
number (0…∞) | $param | @node |
no | Advance between stamps. 0 = the scaled image width (seamless tiling). |
width-px |
number (0…∞) | $param | @node |
no | Stroke width; the image height is fit to it. 0 = native height. |
Stamp a sprite at every input point. Lines and polygons are ignored. Rotation, scale and position jitter are world-deterministic — a given point gets the same jitter no matter which tile renders it, and the three do not correlate. Provide either an image input (one sprite for every point) or, for a data-driven icon-image, a sprite atlas ref plus a name-expr that names each feature’s icon.
| Field | Type | Required | Description |
|---|---|---|---|
features |
@node |
yes | Reference to another node (@name). |
image |
@node |
no | Reference to another node (@name). |
name-expr |
— | no | A MapLibre expression giving each feature’s icon name (MapLibre data-driven icon-image), cropped per group from the sprite atlas. Used instead of an image input; any icon the bound sheet contains is croppable, so no enumeration is needed. |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-expr |
— | no | A MapLibre number expression giving opacity, evaluated per feature group; overrides the constant opacity. |
position-jitter-px |
number (0…∞) | $param | @node |
no | Per-point random offset, ±value canvas pixels, drawn independently on x and y. Breaks up the regular lattice a point-grid leaves. Default 0 (no offset). |
rotation-deg |
number | $param | @node |
no | Constant rotation around each point, in degrees clockwise. |
rotation-deg-expr |
— | no | A MapLibre number expression (degrees clockwise), evaluated per feature group; overrides the constant rotation-deg. |
rotation-jitter-deg |
number (0…∞) | $param | @node |
no | Per-point random rotation, ±value degrees. |
scale |
number (0…∞) | $param | @node |
no | Uniform scale applied to the sprite. Default 1.0 (native size). |
scale-expr |
— | no | A MapLibre number expression, evaluated per feature group; overrides the constant scale. A group whose expression doesn’t resolve to a number falls back to scale. |
scale-jitter |
number (0…∞) | $param | @node |
no | Per-point random scale, ±value as a fraction of scale (0.2 = ±20%). |
sprite |
string | no | Asset reference (@name) or literal path. |
stroke
Section titled “stroke”Crisp constant-width vector stroke along feature polylines (tiny-skia), with cap/join and optional pixel dasharray. The sharp counterpart to line (painterly brush) — for clean cartographic road/boundary lines.

| Field | Type | Required | Description |
|---|---|---|---|
cap |
butt | round | square |
no | — |
color |
color | $param | @node |
yes | sRGB hex color, $param reference, or @node scalar port. |
color-expr |
— | no | A MapLibre color expression (JSON array, e.g. [“match”, [“get”, “class”], “river”, “#48b”, “#888”]), evaluated per feature group at paint time. When present it overrides the constant color; a group whose expression doesn’t resolve to a color falls back to color. Unlocks data-driven stroke colors. |
dasharray |
array of number (0…∞) | string | no | On/off lengths in pixels; omit for solid. Each length may be a $param. |
features |
@node |
yes | Reference to another node (@name). |
gap-width-expr |
— | no | A MapLibre number expression (JSON array) giving the casing gap in pixels, evaluated per feature group at paint time. When present it overrides the constant gap-width-px; a group whose expression doesn’t resolve to a number falls back to gap-width-px. |
gap-width-px |
number | no | MapLibre line-gap-width: when > 0 the stroke becomes a casing — two parallel bands of width-px each, their inner edges this many pixels apart (outer footprint gap + 2 * width, centred on the line). 0 strokes the centreline. |
join |
miter | round | bevel |
no | — |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-expr |
— | no | A MapLibre number expression (JSON array) giving stroke opacity, evaluated per feature group at paint time. When present it overrides the constant opacity; a group whose expression doesn’t resolve to a number falls back to opacity. |
width-expr |
— | no | A MapLibre number expression (JSON array, e.g. [“interpolate”, [“linear”], [“zoom”], 10, 1, 16, 4]) giving stroke width in pixels, evaluated per feature group at paint time. When present it overrides the constant width-px; a group whose expression doesn’t resolve to a number falls back to width-px. |
width-px |
number (0…∞) | $param | @node |
no | — |
Text labels (MapLibre symbol-placement): placement: point (default) labels each feature point, line / line-center walk each polyline with tangent-rotated glyphs. font is an ordered fallback stack of font and/or glyphs source names; text is a literal string or a MapLibre string expression evaluated per feature group. Paint properties have optional *-expr siblings; layout knobs are build-time constants in em. Collision is on by default and is deterministic across tiles: candidates come from this tile plus the 8 neighbour tiles (host-bound under <source>.<layer>@dx,dy), so borders stay seamless. Set source/layer (the upstream feature source) to enable neighbour gathering; without them collision is centre-tile-only. icon-name / icon-name-expr add MapLibre’s icon-image half: the icon is placed together with the text against the same index, so a symbol shows both or neither unless icon-optional / text-optional say otherwise. font and text are required unless the symbol is icon-only.
| Field | Type | Required | Description |
|---|---|---|---|
allow-overlap |
boolean | no | Deprecated: use overlap (true is "always", false is "never"). MapLibre text-allow-overlap: place the label even if it collides. It still reserves its box (blocking later labels) unless ignore-placement. Ignored when overlap is set. Default false. |
anchor |
center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Which part of the label block sits on the point (point placement). Default center. Ignored when anchor-offsets is set. |
anchor-expr |
— | no | A MapLibre string expression (data-driven MapLibre text-anchor), evaluated per feature group with the tile’s zoom, naming which part of the label block sits on the point: one of center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right. It takes the place of anchor for that group, with offset-em (or radial-offset) shifting the block as it would for that constant anchor, and justify: auto following it. A group whose result is not an anchor name, or whose evaluation fails, uses anchor; a result that is not an anchor name is reported once per node. Ignored, with a warning, when anchor-offsets or anchor-variants is set, since those name the anchors themselves. Point placement only. |
anchor-offsets |
array of object | no | The positions a point label tries, in order: on collision it moves to the next, and takes the first whose box is free. Each entry names the anchor that sits on the point and how far the block shifts from there: offset-em [x, y] in em, right and down positive, or radial-em, an em distance pushed away from the point in the anchor’s direction (corners split it across both axes, and top / bottom measure it to the visible text, as MapLibre’s text-radial-offset does). Give one of the two, or neither for no shift. The same anchor may appear more than once, so a label can try a near spot and then a farther one. label-boxes reports the position taken as variant-index. MapLibre’s text-variable-anchor-offset is the nearest counterpart, but it also nudges top and bottom anchors by a baseline gap that offset-em does not; ezu translate folds that gap into the offsets it writes. When set, anchor, anchor-expr, offset-em, anchor-variants and radial-offset are ignored for point placement. Point placement only. |
anchor-variants |
array of center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Deprecated: use anchor-offsets, with { "anchor": …, "radial-em": … } entries for a radial-offset. MapLibre text-variable-anchor: anchors tried in order on collision, the first free one placed (point placement). Overrides anchor when set. |
background-color |
color | $param | @node |
no | Fill a box behind each label’s text: the laid-out block’s bounds, every line included, grown by background-padding, with background-radius-px corners. The box fits the text however long it is. anchor and offset-em position the box rather than the text, so with anchor: top-left the box’s top-left corner sits on the point and the text starts background-padding inside it; changing the padding never moves the anchored corner. The box is the label’s collision box. It draws under the halo and glyphs, and over the layer’s icons; opacity applies to it as to the text. Point placement only: line-placed labels draw without it. Absent (default): no fill; the box still exists, unpainted, when background-padding is set. |
background-padding |
array of number | no | [top, right, bottom, left] px between the label’s text and the edge of its background box, in the order icon-text-fit-padding uses. Setting it gives each point label the box even without background-color: the box then paints nothing, but anchor and offset-em still position it, it is still the label’s collision box, and label-boxes still reports it as background — a way to reserve space around a label or measure a badge drawn by other ops. Default [0, 0, 0, 0]. |
background-radius-px |
number (0…∞) | $param | @node |
no | Corner radius of the background box’s background-color fill in px, clamped to half its shorter side. Default 0 (square corners). |
collide |
boolean | no | Whether to run deterministic label collision. Default true (MapLibre’s default). Set false to draw every label (the pre-collision behaviour). |
color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
color-expr |
— | no | A MapLibre color expression, evaluated per feature group; overrides the constant color. |
dedupe-distance-px |
number | no | The distance in px, anchor to anchor, within which a placed label hides later labels with the same dedupe-key-expr key. Required with dedupe-key-expr; must be greater than 0. |
dedupe-key-expr |
— | no | A MapLibre expression, evaluated per feature group, giving the key that marks labels as duplicates of each other: once a label of this layer has placed, a later label with an equal key whose anchor is nearer than dedupe-distance-px to it is dropped, so a name shared by several neighbouring features is shown once. Labels are visited in placement order (sort-key-expr, then feature order), and only labels that actually placed count, so a label that lost its collision never hides its duplicates. A deduped label takes no space, and label-boxes with include: all reports it with drop-reason: dedupe. A null or empty result never dedupes; other values compare by their text. Applies within this layer only, needs dedupe-distance-px and collision on, and works the same across tiles as long as the distance stays within the 8 neighbour tiles. Point placement only — line labels have spacing-px. |
features |
@node |
yes | Reference to another node (@name). |
filter-expr |
— | no | The upstream features filter, reproduced when gathering neighbour candidates so they are filtered identically to this tile’s own features. Set it to whatever the features node uses. |
font |
array of string | no | Ordered fallback stack of font and/or glyphs source names from the document’s sources. Outline fonts and SDF glyph stacks mix freely; the first entry covering a char shapes it. Also the default/fallback stack for font-expr and unlisted font-stacks keys. |
font-expr |
— | no | A MapLibre expression yielding an array of font names, evaluated per feature group (MapLibre data-driven text-font); overrides the constant font. Each result is canonicalized (names joined with ,) and looked up in font-stacks; an unlisted stack, a non-array result, or an eval error falls back to font. Every stack a host may need must be enumerable in font-stacks (wasm hosts pre-bind glyph assets). |
font-stacks |
object | no | Named dynamic font stacks: canonical stack key (font names joined with ,) → ordered font/glyphs source names. Consulted by font-expr and by format text sections carrying text-font. Declaration order fixes each stack’s stable id (used by the layout cache and cross-tile collision). |
halo-color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
halo-color-expr |
— | no | A MapLibre color expression for the halo, evaluated per feature group; overrides the constant halo-color. |
halo-width |
number (0…∞) | $param | @node |
no | Halo radius in px around each glyph. Default 0 (no halo). |
halo-width-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides the constant halo-width. |
icon-allow-overlap |
boolean | no | MapLibre icon-allow-overlap. A symbol carries one overlap decision, so with text present this and overlap: always must both be set for the symbol to ignore collision. Default false. |
icon-anchor |
center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Which part of the icon sits on the point (MapLibre icon-anchor). Default center. |
icon-ignore-placement |
boolean | no | MapLibre icon-ignore-placement; with text present it pairs with ignore-placement the same way as the overlap flags. Default false. |
icon-name |
string | no | Constant MapLibre icon-image: the sprite to draw at each point, cropped from icon-sprite. Needs icon-sprite. |
icon-name-expr |
— | no | A MapLibre expression naming each feature’s icon (data-driven icon-image), cropped from icon-sprite; overrides icon-name. A group resolving to an empty/unknown name draws no icon and places as a text-only symbol. |
icon-offset |
array of number | no | MapLibre icon-offset: shift [x, y] in px, itself scaled by icon-size. Default [0, 0]. |
icon-opacity |
number (0…1) | $param | @node |
no | — |
icon-opacity-expr |
— | no | A MapLibre number expression giving the icon’s opacity; overrides icon-opacity. |
icon-optional |
boolean | no | MapLibre icon-optional: let the text place when the icon’s box is blocked. Default false — icon and text place as one unit. |
icon-padding-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides icon-padding-px. |
icon-padding-px |
number (0…∞) | no | MapLibre icon-padding: collision-box inflation around the icon, in px. Default 2. |
icon-rotate-deg |
number | no | MapLibre icon-rotate: rotation in degrees clockwise about the icon centre. Default 0. |
icon-rotate-deg-expr |
— | no | A MapLibre number expression (degrees clockwise); overrides icon-rotate-deg. |
icon-size |
number (0…∞) | no | MapLibre icon-size: multiplier on the sprite’s authored display size (atlas pixels over its pixel ratio). Default 1. |
icon-size-expr |
— | no | A MapLibre number expression, evaluated per feature group; overrides icon-size. |
icon-sprite |
string | no | Asset reference (@name) or literal path. |
icon-text-fit |
none | width | height | both |
no | MapLibre icon-text-fit: stretch the icon to the box its label occupies, on the named axes. The sprite’s stretchX/stretchY/content metadata decides which bands absorb the growth, so a shield keeps its corners; a sprite without it scales whole. The fitted box is also the icon’s collision box. Default none. |
icon-text-fit-padding |
array of number | no | MapLibre icon-text-fit-padding: [top, right, bottom, left] px added around the label’s box before the icon is fitted to it. Default [0, 0, 0, 0]. |
ignore-placement |
boolean | no | MapLibre text-ignore-placement: don’t let this label block later ones (skip inserting its collision box). Default false. |
justify |
auto | left | center | right |
no | Line alignment within the wrapped block. Default auto (follows the anchor’s horizontal side). |
keep-upright |
boolean | no | Flip a right-to-left label so it reads upright (MapLibre text-keep-upright). Line placement only. Default true. |
layer |
string | no | Upstream feature layer name (with source). Used only to name neighbour bindings for cross-tile collision. |
letter-spacing-em |
number | no | Extra advance per glyph in em. Default 0. |
line-height |
number | no | Baseline-to-baseline distance in em. Default 1.2. |
max-angle-deg |
number (0…∞) | no | Max cumulative bend in degrees the line may turn over a label-length window before the anchor is rejected (MapLibre text-max-angle). Line placement only. Default 45. |
max-extent-px |
number (0…∞) | no | Canvas pad this node requests; labels whose bbox half-extent exceeds it are culled with a warning. Default 128. |
max-width-em |
number (0…∞) | no | Wrap target width in em. Default 10; 0 disables wrapping. |
max-zoom |
integer (0…24) | no | Style layer zoom gate: draw nothing above this zoom (mirrors the features node). |
min-zoom |
integer (0…24) | no | Style layer zoom gate: draw nothing below this zoom (mirrors the features node). |
min-zoom-field |
string | no | Per-feature min_zoom property name, reproduced for neighbour candidate filtering (mirrors the features node). |
offset-em |
array of number | no | Block shift [x, y] in em, applied after anchoring. Default [0, 0]. For point placement, ignored when anchor-offsets is set. |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-expr |
— | no | A MapLibre number expression giving opacity, evaluated per feature group; multiplies both fill and halo alpha. Overrides the constant opacity. |
outline-sdf |
boolean | no | Render outline-font glyphs through the SDF field-sampling path (maplibre-gl-js style, which draws every glyph from an SDF) instead of a per-glyph vector fill / stroke. The halo then comes from a distance threshold rather than a stroke, matching MapLibre’s halo shape and cost. SDF glyph (glyphs) stacks are unaffected. Default true. |
overlap |
never | always | fallback |
no | What a label does when it collides with labels placed before it. never (default): it tries each of its positions (anchor-offsets) and drops if none is free. always (MapLibre text-allow-overlap): it shows at its first position without trying the others, whatever it overlaps. fallback: it tries every position as never does, and the symbol’s text-only or icon-only fallbacks when icon-optional / text-optional allow them, and only when all are blocked shows at its first position, overlapping; label-boxes marks it overlapped. A shown label still reserves its box (blocking later labels) unless ignore-placement. On a symbol with an icon, always takes effect only with icon-allow-overlap set too, while fallback applies to the whole symbol. |
padding-expr |
— | no | A MapLibre number expression (MapLibre text-padding), evaluated per feature group; overrides the constant padding-px for that group’s collision boxes. |
padding-px |
number (0…∞) | no | Collision-box inflation in px on every side. Default 2. |
placement |
point | line | line-center |
no | MapLibre symbol-placement. point (default) labels each feature point, and each polygon once at its polygon-anchor. line repeats labels along each polyline every spacing-px; line-center places one at each line’s arc-length midpoint. Line placement ignores wrapping (max-width-em) and lays out a single line along the path. |
polygon-anchor |
pole | centroid |
no | Where point placement labels a polygon. pole (default) is its pole of inaccessibility, the interior spot farthest from its outline, where MapLibre places it. centroid is its area-weighted centroid with any holes cut out, the polygon’s balance point, for a label that belongs in the middle of an area such as a block number; where the centroid falls outside the polygon (a C or U shape) the pole is used instead. Past a source’s max-zoom the anchor is found on the polygon as the source tile holds it, so every deeper zoom labels it in the same place. At the zooms the source serves, each tile finds the anchor on the polygon as that tile was cut, so a polygon reaching further past a tile edge than the tile’s buffer gets a separate anchor in each tile it crosses, as in MapLibre; stitching the features node does not change that, and tiles generated with a buffer wider than the polygon do. Point placement only. |
radial-offset |
number | no | Deprecated: use anchor-offsets entries with radial-em. MapLibre text-radial-offset: em distance each anchor-variants entry pushes the block away from the point in its direction. Default 0. |
size |
number (0…∞) | $param | @node |
no | Font size in px. Default 16. |
size-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides the constant size. |
sort-key-expr |
— | no | A MapLibre number expression (MapLibre symbol-sort-key), evaluated per feature group; lower values place first under collision. Absent = 0. |
source |
string | no | Upstream feature source name (matches the features node). Used only to name the neighbour tile bindings for cross-tile collision. Omit to collide within this tile only. |
spacing-px |
number (1…∞) | no | Gap in px between successive line anchors (MapLibre symbol-spacing). Line placement only. Default 250. |
strikethrough |
boolean | no | Strike the label through: one horizontal bar per wrapped line, spanning the line’s advance extent, in the label’s color / color-expr and opacity. It sits at the primary font’s strikeout stroke (else half its x-height, else 0.3 em above the baseline); a glyphs stack carries no metrics and takes the 0.3 em fallback. With halo-width > 0 the bar gets the same halo as the glyphs, drawn underneath. Part of the drawn label, so it moves with the placed anchor and draws identically across tile seams; it does not change the collision box. Point placement only — line-placed labels ignore it. Default false. |
strikethrough-expr |
— | no | A MapLibre boolean expression, evaluated per feature group; overrides the constant strikethrough (a non-boolean result falls back to it). |
strikethrough-width |
number (0…∞) | no | Strikethrough bar thickness in px, rounded to whole pixels. Default 0.07 em of the label’s size, at least 1 px. |
text |
— | no | The label: a literal string, or a MapLibre string expression (evaluated per feature group; empty/failed → the group draws nothing). |
text-optional |
boolean | no | MapLibre text-optional: let the icon place when the text’s box is blocked. Default false. |
transform |
none | uppercase | lowercase |
no | Case transform applied before shaping. Default none. |
text-draw
Section titled “text-draw”Draw one label layer’s placed labels: labels is the layer’s text-labels node, placement the shared label-placement node that decided it (wire the same text-labels node into both). Styling and layout live on text-labels; this node only rasterizes the winners.
| Field | Type | Required | Description |
|---|---|---|---|
labels |
@node |
yes | Reference to another node (@name). |
max-extent-px |
number (0…∞) | no | Canvas pad (px) this layer’s labels need to cross a tile border un-clipped. Mirrors the text-labels field of the same name. |
placement |
@node |
yes | Reference to another node (@name). |
text-labels
Section titled “text-labels”Text labels (MapLibre symbol-placement): placement: point (default) labels each feature point, line / line-center walk each polyline with tangent-rotated glyphs. font is an ordered fallback stack of font and/or glyphs source names; text is a literal string or a MapLibre string expression evaluated per feature group. Paint properties have optional *-expr siblings; layout knobs are build-time constants in em. Collision is on by default and is deterministic across tiles: candidates come from this tile plus the 8 neighbour tiles (host-bound under <source>.<layer>@dx,dy), so borders stay seamless. Set source/layer (the upstream feature source) to enable neighbour gathering; without them collision is centre-tile-only. icon-name / icon-name-expr add MapLibre’s icon-image half: the icon is placed together with the text against the same index, so a symbol shows both or neither unless icon-optional / text-optional say otherwise. font and text are required unless the symbol is icon-only. Emits placement candidates instead of pixels: wire this node into a label-placement node and draw the result with text-draw.
| Field | Type | Required | Description |
|---|---|---|---|
allow-overlap |
boolean | no | Deprecated: use overlap (true is "always", false is "never"). MapLibre text-allow-overlap: place the label even if it collides. It still reserves its box (blocking later labels) unless ignore-placement. Ignored when overlap is set. Default false. |
anchor |
center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Which part of the label block sits on the point (point placement). Default center. Ignored when anchor-offsets is set. |
anchor-expr |
— | no | A MapLibre string expression (data-driven MapLibre text-anchor), evaluated per feature group with the tile’s zoom, naming which part of the label block sits on the point: one of center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right. It takes the place of anchor for that group, with offset-em (or radial-offset) shifting the block as it would for that constant anchor, and justify: auto following it. A group whose result is not an anchor name, or whose evaluation fails, uses anchor; a result that is not an anchor name is reported once per node. Ignored, with a warning, when anchor-offsets or anchor-variants is set, since those name the anchors themselves. Point placement only. |
anchor-offsets |
array of object | no | The positions a point label tries, in order: on collision it moves to the next, and takes the first whose box is free. Each entry names the anchor that sits on the point and how far the block shifts from there: offset-em [x, y] in em, right and down positive, or radial-em, an em distance pushed away from the point in the anchor’s direction (corners split it across both axes, and top / bottom measure it to the visible text, as MapLibre’s text-radial-offset does). Give one of the two, or neither for no shift. The same anchor may appear more than once, so a label can try a near spot and then a farther one. label-boxes reports the position taken as variant-index. MapLibre’s text-variable-anchor-offset is the nearest counterpart, but it also nudges top and bottom anchors by a baseline gap that offset-em does not; ezu translate folds that gap into the offsets it writes. When set, anchor, anchor-expr, offset-em, anchor-variants and radial-offset are ignored for point placement. Point placement only. |
anchor-variants |
array of center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Deprecated: use anchor-offsets, with { "anchor": …, "radial-em": … } entries for a radial-offset. MapLibre text-variable-anchor: anchors tried in order on collision, the first free one placed (point placement). Overrides anchor when set. |
background-color |
color | $param | @node |
no | Fill a box behind each label’s text: the laid-out block’s bounds, every line included, grown by background-padding, with background-radius-px corners. The box fits the text however long it is. anchor and offset-em position the box rather than the text, so with anchor: top-left the box’s top-left corner sits on the point and the text starts background-padding inside it; changing the padding never moves the anchored corner. The box is the label’s collision box. It draws under the halo and glyphs, and over the layer’s icons; opacity applies to it as to the text. Point placement only: line-placed labels draw without it. Absent (default): no fill; the box still exists, unpainted, when background-padding is set. |
background-padding |
array of number | no | [top, right, bottom, left] px between the label’s text and the edge of its background box, in the order icon-text-fit-padding uses. Setting it gives each point label the box even without background-color: the box then paints nothing, but anchor and offset-em still position it, it is still the label’s collision box, and label-boxes still reports it as background — a way to reserve space around a label or measure a badge drawn by other ops. Default [0, 0, 0, 0]. |
background-radius-px |
number (0…∞) | $param | @node |
no | Corner radius of the background box’s background-color fill in px, clamped to half its shorter side. Default 0 (square corners). |
collide |
boolean | no | Whether to run deterministic label collision. Default true (MapLibre’s default). Set false to draw every label (the pre-collision behaviour). |
color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
color-expr |
— | no | A MapLibre color expression, evaluated per feature group; overrides the constant color. |
dedupe-distance-px |
number | no | The distance in px, anchor to anchor, within which a placed label hides later labels with the same dedupe-key-expr key. Required with dedupe-key-expr; must be greater than 0. |
dedupe-key-expr |
— | no | A MapLibre expression, evaluated per feature group, giving the key that marks labels as duplicates of each other: once a label of this layer has placed, a later label with an equal key whose anchor is nearer than dedupe-distance-px to it is dropped, so a name shared by several neighbouring features is shown once. Labels are visited in placement order (sort-key-expr, then feature order), and only labels that actually placed count, so a label that lost its collision never hides its duplicates. A deduped label takes no space, and label-boxes with include: all reports it with drop-reason: dedupe. A null or empty result never dedupes; other values compare by their text. Applies within this layer only, needs dedupe-distance-px and collision on, and works the same across tiles as long as the distance stays within the 8 neighbour tiles. Point placement only — line labels have spacing-px. |
features |
@node |
yes | Reference to another node (@name). |
filter-expr |
— | no | The upstream features filter, reproduced when gathering neighbour candidates so they are filtered identically to this tile’s own features. Set it to whatever the features node uses. |
font |
array of string | no | Ordered fallback stack of font and/or glyphs source names from the document’s sources. Outline fonts and SDF glyph stacks mix freely; the first entry covering a char shapes it. Also the default/fallback stack for font-expr and unlisted font-stacks keys. |
font-expr |
— | no | A MapLibre expression yielding an array of font names, evaluated per feature group (MapLibre data-driven text-font); overrides the constant font. Each result is canonicalized (names joined with ,) and looked up in font-stacks; an unlisted stack, a non-array result, or an eval error falls back to font. Every stack a host may need must be enumerable in font-stacks (wasm hosts pre-bind glyph assets). |
font-stacks |
object | no | Named dynamic font stacks: canonical stack key (font names joined with ,) → ordered font/glyphs source names. Consulted by font-expr and by format text sections carrying text-font. Declaration order fixes each stack’s stable id (used by the layout cache and cross-tile collision). |
halo-color |
color | $param | @node |
no | sRGB hex color, $param reference, or @node scalar port. |
halo-color-expr |
— | no | A MapLibre color expression for the halo, evaluated per feature group; overrides the constant halo-color. |
halo-width |
number (0…∞) | $param | @node |
no | Halo radius in px around each glyph. Default 0 (no halo). |
halo-width-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides the constant halo-width. |
icon-allow-overlap |
boolean | no | MapLibre icon-allow-overlap. A symbol carries one overlap decision, so with text present this and overlap: always must both be set for the symbol to ignore collision. Default false. |
icon-anchor |
center | left | right | top | bottom | top-left | top-right | bottom-left | bottom-right |
no | Which part of the icon sits on the point (MapLibre icon-anchor). Default center. |
icon-ignore-placement |
boolean | no | MapLibre icon-ignore-placement; with text present it pairs with ignore-placement the same way as the overlap flags. Default false. |
icon-name |
string | no | Constant MapLibre icon-image: the sprite to draw at each point, cropped from icon-sprite. Needs icon-sprite. |
icon-name-expr |
— | no | A MapLibre expression naming each feature’s icon (data-driven icon-image), cropped from icon-sprite; overrides icon-name. A group resolving to an empty/unknown name draws no icon and places as a text-only symbol. |
icon-offset |
array of number | no | MapLibre icon-offset: shift [x, y] in px, itself scaled by icon-size. Default [0, 0]. |
icon-opacity |
number (0…1) | $param | @node |
no | — |
icon-opacity-expr |
— | no | A MapLibre number expression giving the icon’s opacity; overrides icon-opacity. |
icon-optional |
boolean | no | MapLibre icon-optional: let the text place when the icon’s box is blocked. Default false — icon and text place as one unit. |
icon-padding-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides icon-padding-px. |
icon-padding-px |
number (0…∞) | no | MapLibre icon-padding: collision-box inflation around the icon, in px. Default 2. |
icon-rotate-deg |
number | no | MapLibre icon-rotate: rotation in degrees clockwise about the icon centre. Default 0. |
icon-rotate-deg-expr |
— | no | A MapLibre number expression (degrees clockwise); overrides icon-rotate-deg. |
icon-size |
number (0…∞) | no | MapLibre icon-size: multiplier on the sprite’s authored display size (atlas pixels over its pixel ratio). Default 1. |
icon-size-expr |
— | no | A MapLibre number expression, evaluated per feature group; overrides icon-size. |
icon-sprite |
string | no | Asset reference (@name) or literal path. |
icon-text-fit |
none | width | height | both |
no | MapLibre icon-text-fit: stretch the icon to the box its label occupies, on the named axes. The sprite’s stretchX/stretchY/content metadata decides which bands absorb the growth, so a shield keeps its corners; a sprite without it scales whole. The fitted box is also the icon’s collision box. Default none. |
icon-text-fit-padding |
array of number | no | MapLibre icon-text-fit-padding: [top, right, bottom, left] px added around the label’s box before the icon is fitted to it. Default [0, 0, 0, 0]. |
ignore-placement |
boolean | no | MapLibre text-ignore-placement: don’t let this label block later ones (skip inserting its collision box). Default false. |
justify |
auto | left | center | right |
no | Line alignment within the wrapped block. Default auto (follows the anchor’s horizontal side). |
keep-upright |
boolean | no | Flip a right-to-left label so it reads upright (MapLibre text-keep-upright). Line placement only. Default true. |
layer |
string | no | Upstream feature layer name (with source). Used only to name neighbour bindings for cross-tile collision. |
letter-spacing-em |
number | no | Extra advance per glyph in em. Default 0. |
line-height |
number | no | Baseline-to-baseline distance in em. Default 1.2. |
max-angle-deg |
number (0…∞) | no | Max cumulative bend in degrees the line may turn over a label-length window before the anchor is rejected (MapLibre text-max-angle). Line placement only. Default 45. |
max-extent-px |
number (0…∞) | no | Canvas pad this node requests; labels whose bbox half-extent exceeds it are culled with a warning. Default 128. |
max-width-em |
number (0…∞) | no | Wrap target width in em. Default 10; 0 disables wrapping. |
max-zoom |
integer (0…24) | no | Style layer zoom gate: draw nothing above this zoom (mirrors the features node). |
min-zoom |
integer (0…24) | no | Style layer zoom gate: draw nothing below this zoom (mirrors the features node). |
min-zoom-field |
string | no | Per-feature min_zoom property name, reproduced for neighbour candidate filtering (mirrors the features node). |
offset-em |
array of number | no | Block shift [x, y] in em, applied after anchoring. Default [0, 0]. For point placement, ignored when anchor-offsets is set. |
opacity |
number (0…1) | $param | @node |
no | — |
opacity-expr |
— | no | A MapLibre number expression giving opacity, evaluated per feature group; multiplies both fill and halo alpha. Overrides the constant opacity. |
outline-sdf |
boolean | no | Render outline-font glyphs through the SDF field-sampling path (maplibre-gl-js style, which draws every glyph from an SDF) instead of a per-glyph vector fill / stroke. The halo then comes from a distance threshold rather than a stroke, matching MapLibre’s halo shape and cost. SDF glyph (glyphs) stacks are unaffected. Default true. |
overlap |
never | always | fallback |
no | What a label does when it collides with labels placed before it. never (default): it tries each of its positions (anchor-offsets) and drops if none is free. always (MapLibre text-allow-overlap): it shows at its first position without trying the others, whatever it overlaps. fallback: it tries every position as never does, and the symbol’s text-only or icon-only fallbacks when icon-optional / text-optional allow them, and only when all are blocked shows at its first position, overlapping; label-boxes marks it overlapped. A shown label still reserves its box (blocking later labels) unless ignore-placement. On a symbol with an icon, always takes effect only with icon-allow-overlap set too, while fallback applies to the whole symbol. |
padding-expr |
— | no | A MapLibre number expression (MapLibre text-padding), evaluated per feature group; overrides the constant padding-px for that group’s collision boxes. |
padding-px |
number (0…∞) | no | Collision-box inflation in px on every side. Default 2. |
placement |
point | line | line-center |
no | MapLibre symbol-placement. point (default) labels each feature point, and each polygon once at its polygon-anchor. line repeats labels along each polyline every spacing-px; line-center places one at each line’s arc-length midpoint. Line placement ignores wrapping (max-width-em) and lays out a single line along the path. |
polygon-anchor |
pole | centroid |
no | Where point placement labels a polygon. pole (default) is its pole of inaccessibility, the interior spot farthest from its outline, where MapLibre places it. centroid is its area-weighted centroid with any holes cut out, the polygon’s balance point, for a label that belongs in the middle of an area such as a block number; where the centroid falls outside the polygon (a C or U shape) the pole is used instead. Past a source’s max-zoom the anchor is found on the polygon as the source tile holds it, so every deeper zoom labels it in the same place. At the zooms the source serves, each tile finds the anchor on the polygon as that tile was cut, so a polygon reaching further past a tile edge than the tile’s buffer gets a separate anchor in each tile it crosses, as in MapLibre; stitching the features node does not change that, and tiles generated with a buffer wider than the polygon do. Point placement only. |
radial-offset |
number | no | Deprecated: use anchor-offsets entries with radial-em. MapLibre text-radial-offset: em distance each anchor-variants entry pushes the block away from the point in its direction. Default 0. |
size |
number (0…∞) | $param | @node |
no | Font size in px. Default 16. |
size-expr |
— | no | A MapLibre number expression (px), evaluated per feature group; overrides the constant size. |
sort-key-expr |
— | no | A MapLibre number expression (MapLibre symbol-sort-key), evaluated per feature group; lower values place first under collision. Absent = 0. |
source |
string | no | Upstream feature source name (matches the features node). Used only to name the neighbour tile bindings for cross-tile collision. Omit to collide within this tile only. |
spacing-px |
number (1…∞) | no | Gap in px between successive line anchors (MapLibre symbol-spacing). Line placement only. Default 250. |
strikethrough |
boolean | no | Strike the label through: one horizontal bar per wrapped line, spanning the line’s advance extent, in the label’s color / color-expr and opacity. It sits at the primary font’s strikeout stroke (else half its x-height, else 0.3 em above the baseline); a glyphs stack carries no metrics and takes the 0.3 em fallback. With halo-width > 0 the bar gets the same halo as the glyphs, drawn underneath. Part of the drawn label, so it moves with the placed anchor and draws identically across tile seams; it does not change the collision box. Point placement only — line-placed labels ignore it. Default false. |
strikethrough-expr |
— | no | A MapLibre boolean expression, evaluated per feature group; overrides the constant strikethrough (a non-boolean result falls back to it). |
strikethrough-width |
number (0…∞) | no | Strikethrough bar thickness in px, rounded to whole pixels. Default 0.07 em of the label’s size, at least 1 px. |
text |
— | no | The label: a literal string, or a MapLibre string expression (evaluated per feature group; empty/failed → the group draws nothing). |
text-optional |
boolean | no | MapLibre text-optional: let the icon place when the text’s box is blocked. Default false. |
transform |
none | uppercase | lowercase |
no | Case transform applied before shaping. Default none. |
Map renders on this site are made fromOpenStreetMap data viaProtomaps (© OpenStreetMap contributors), elevation from Re:Earth Terrain,Mapterhorn andEGM2008 (NGA), and aerial imagery from GSI Japan(© 国土地理院). The painterly styles use CC0 brushes byDavid Revoy.