Skip to content

Overview

An Ezu Style document is a JSON object. Two fields are required — nodes and output — plus a name. Everything else is optional.

{
"name": "watercolor",
"version": "1",
"tile-size": 512,
"pad": 24,
"attribution": "© OpenStreetMap contributors",
"params": { },
"functions": { },
"legend": { },
"sources": { },
"nodes": { },
"output": "@out"
}
Field Type Meaning
name string required — identifies the style in logs and saved files
nodes map of id → node required — the graph. Order is irrelevant
output @node required — must resolve to a Raster
version string the style’s own version, yours to use
tile-size integer rendered tile edge in px. Defaults to 512, which matches MapLibre — leave it out unless you want another edge
pad integer margin around the tile, in px. A floor: the renderer uses this or what the graph’s filters need, whichever is larger. Leave it out to let the graph decide — see padding
attribution string document-level attribution, merged with each source’s
params map typed knobs resolved at render time
functions map reusable subgraphs expanded at build time
legend object what the symbols mean, for a host to draw beside the map
sources map every external resource the style pulls in

tile-size is how many pixels a tile’s fixed patch of ground is drawn into, so every *-px field in the document is measured against it. The same width-px: 8 stroke occupies 1.56% of a 512 px tile and 3.12% of a 256 px one — twice the ground width for the same number.

512 is MapLibre’s vector-tile convention, and matching it is what makes a px number mean the same thing here as it does there: a style ported either way keeps its weights. 256 is the older raster-imagery convention; drawing a 512-designed style into it doubles every stroke, glyph and dab relative to the ground, which reads as a crowded map rather than a scaled one.

So it is worth setting deliberately, and worth checking on a style you are matching against a reference: comparing line widths in pixels across two different canvas sizes compares nothing.

There are exactly two, and the prefix tells you when it resolves:

  • @name — a node reference, resolved at build time. On a scalar field it is a Scalar port, so computed values (math, zoom, expr) plug in.
  • $name — a params reference, resolved at render time against caller-supplied values, falling back to the declared default.

A field name ending in -expr takes a raw MapLibre expression instead.

A style may carry // line comments and /* … */ block comments, anywhere JSON allows whitespace — including inside an expression array.

{
// Sea-and-ink, for the coastal atlas.
"name": "atlas",
"nodes": {
/* Roads fade out below z13, where the classes stop being
distinguishable and the ink turns to mud. */
"fade": { "op": "expr", "expr": ["interpolate", ["linear"], ["zoom"],
13, 0, // off in the countryside
15, 1] }
},
"output": "@fade"
}

Comments are blanked out before parsing, so a parse error still reports the line and column of the file as you wrote it. Nothing rewrites your style, so nothing can drop them.

Only comments are added to JSON: trailing commas and unquoted keys stay errors. Editors validating against the JSON Schema need the file treated as JSON with comments — in VS Code, that means associating it with jsonc.

{
"name": "flat",
"sources": {
"basemap": { "type": "mvt", "url": "https://papers.reearth.land/protomaps/tilejson.json" }
},
"nodes": {
"bg": { "op": "solid", "color": "#fbf6e6" },
"water_f": { "op": "features", "layer": "water" },
"water": { "op": "fill-solid", "features": "@water_f", "fill": "#5876a0" },
"out": { "op": "blend", "base": "@bg", "over": "@water" }
},
"output": "@out"
}

No pad: the renderer sizes the canvas from how far the graph’s filters reach. Declare it when an op paints wider than its geometry — see padding and neighbours.

The spec is pre-1.0 and lives in the ezu-style crate, versioned with the workspace (currently 0.6.x). Breaking changes to the document shape are called out in the changelog. The version field inside a document is for your versioning — nothing in ezu interprets it.

The authoritative machine-readable definition is the JSON Schema, assembled from the same op registry the renderer uses, so it cannot drift from the implementation.

Terminal window
ezu check style.json # parse + graph + assets
ezu check style.json --no-fetch # offline: parse + graph only

This runs the same static checks the renderer runs — reference resolution, port types, cycles, padding limits. See validate in CI.

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.