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 |
Why the canvas defaults to 512
Section titled “Why the canvas defaults to 512”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.
The two reference prefixes
Section titled “The two reference prefixes”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 aScalarport, so computed values (math,zoom,expr) plug in.$name— aparamsreference, 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.
Comments
Section titled “Comments”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.
A complete minimal style
Section titled “A complete minimal style”{ "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.
Versioning
Section titled “Versioning”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.
Validation
Section titled “Validation”ezu check style.json # parse + graph + assetsezu check style.json --no-fetch # offline: parse + graph onlyThis 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.