CLI commands
Every command and option of the maplibre-yaml CLI (@maplibre-yaml/cli
0.6). For a walkthrough of init, validate, preview and emit, see
The CLI (mlym).
Running the CLI
Section titled “Running the CLI”The CLI needs Node.js 20.9 or later. On an older Node it stops with “maplibre-yaml needs Node.js 20.9 or newer”.
It is one program with several names:
| You type | Works |
|---|---|
npx maplibre-yaml@latest … | Anywhere, with no install. npx downloads maplibre-yaml, a small package that runs @maplibre-yaml/cli |
npx @maplibre-yaml/cli@latest … | Anywhere, with no install: the full name |
npx mlym … | After npm install --save-dev @maplibre-yaml/cli in the project. Without the install it fails with a 404, because no npm package is called mlym |
mlym … or maplibre-yaml … | After npm install -g @maplibre-yaml/cli, in any folder; or in package.json scripts of a project that installed it |
The examples on this page use npx maplibre-yaml@latest.
Global options
Section titled “Global options”| Option | Alias | Description |
|---|---|---|
--help | -h | Show help for a command (npx maplibre-yaml@latest init --help) |
--version | Show the CLI version |
Commands
Section titled “Commands”Create a new project from a template.
npx maplibre-yaml@latest init [folder] [options]| Argument | Required | Default | Description |
|---|---|---|---|
folder | No | ./<name> (./my-map for basic) | Folder to create. It must not exist yet, or be empty |
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--template | -t | string | basic | basic, story or astro |
--name | -n | string | the folder’s name | Project name, used for the page title and the map id |
--list | boolean | List the templates and exit |
An unknown template or option (for example --dir, which init does not
have) prints an error and exits with code 1, and nothing is created.
Templates
Section titled “Templates”| Template | Creates | Run it with |
|---|---|---|
basic | index.html (CDN import map, <ml-map> with a height, viewport meta tag; core loads from the CDN at the release line this CLI was built with) and map.yaml (a street basemap and one clickable point) | npx serve . in the folder, then open the address it prints. Needs no install or build. Another local server works if it supports range requests (which PMTiles, FlatGeobuf, GeoParquet and COG files need); python -m http.server doesn’t |
story | index.html and a story file, story.yaml | A story map with no build step: the page loads <ml-scrollytelling> from the CDN, at the release line this CLI was built with. Serve the folder (npx serve .) and scroll; see Story maps in plain HTML |
astro | An Astro 7 site: a layout (with the viewport meta tag) and a page rendering public/configs/map.yaml with @maplibre-yaml/astro’s <Map>, the optional integration in astro.config.mjs with commented-out site/base lines for GitHub Pages, and a .github/workflows/deploy.yml that publishes it with withastro/action. Its @maplibre-yaml/core and @maplibre-yaml/astro ranges are the versions this CLI release was built with, and its package.json asks for Node.js 22.23.4 or later (Astro 7.4.2’s own requirement) | npm install, then npm run dev. For GitHub Pages, set site and base in astro.config.mjs and push (see Publishing & subpaths) |
Every YAML file the templates write starts with the
schema modeline, so editors with the YAML extension
complete and check it as you type. init prints the next steps for the
template it used.
npx maplibre-yaml@latest init my-map # ./my-map from the basic templatenpx maplibre-yaml@latest init my-site --template astro # an Astro sitenpx maplibre-yaml@latest init --list # list the templatesvalidate
Section titled “validate”Check map documents and story files, written in YAML, JSON or TOML, against the format. It reports problems with their path in the document, a message, and the line and column.
npx maplibre-yaml@latest validate <patterns> [options]| Argument | Required | Description |
|---|---|---|
patterns | Yes | One or more file paths, folders or glob patterns. All of them are validated |
A pattern can be:
- a file:
map.yaml; - a folder:
maps/(every.yamland.ymlfile under it, and the.jsonand.tomlfiles under it that are map or story documents; below); - a glob:
"maps/**/*.yaml". Quote it, so the CLI sees the pattern rather than your shell’s expansion of it. A quoted pattern with a space in a folder name,"My Maps/*.yaml", works; - several of these:
"src/**/*.map.yaml" "maps/*.yaml".
Every argument is checked and reported. A file name that doesn’t exist
prints File not found: …, and a pattern that matches nothing prints
No files match "…". Both make the command exit with code 2 unless a file
also has errors (code 1). Files excluded by validate.ignorePatterns in the
project settings file are skipped, even when named
directly.
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--format | -f | string | human | Output format: human, json, sarif, vscode (below) |
--strict / --no-strict | boolean | on in CI, off otherwise | Treat warnings as errors. Without either flag, strict mode is on when the CI environment variable is set (as CI services do) and off on your machine | |
--strict-deprecations | boolean | false | Also treat deprecation warnings as errors (--strict leaves them as warnings) | |
--watch | -w | boolean | false | Keep running and re-validate each file when it changes. Only changed files are checked again |
--verbose | boolean | false | List the .json and .toml files a folder search skipped because they are not map or story documents |
validate reports three kinds of finding:
- errors: the document is invalid; the map won’t render as written.
- warnings: valid, but probably not what you meant, such as a layer naming a source that isn’t declared, or a map with no basemap. In strict mode they count as errors.
- notes: how a valid value is read, such as a basemap written as just a
.pmtilesfile. A note never makes a file invalid, even in strict mode.
JSON and TOML files in a folder
Section titled “JSON and TOML files in a folder”A folder of maps often holds other JSON too: GeoJSON, a package.json, a
style.json. So under a folder argument, a .json or .toml file is
validated only if its first 64 KB (for a larger JSON file, also its last
64 KB, where sorted keys put the root type) show it is a document: it names a type
of map or scrollytelling, has a top-level pages key, or carries a
$schema (or a TOML #:schema line) under
https://docs.maplibre-yaml.org/schema/. The check reads the text and
doesn’t parse it, so a document with a syntax error, or one that can’t be
read, still joins the run and fails there. The project settings file, maplibre-yaml.config.json, is
always skipped. A file you name, or a glob matches, is validated whatever
it contains.
--verbose lists each file the folder search skipped. A folder with no
document in it is reported, and the command exits with code 2:
✗ No map or story documents found in maps (looked for .yaml and .yml files, and .json and .toml files that declare a map or story; --verbose lists the files skipped)npx maplibre-yaml@latest validate map.yamlnpx maplibre-yaml@latest validate maps/ --verbosenpx maplibre-yaml@latest validate "maps/**/*.yaml" --watchnpx maplibre-yaml@latest validate "**/*.yaml" --format sarif > results.sarifOutput formats
Section titled “Output formats”human (the default) prints each file with a mark, then its findings
and a summary:
⚠ map.yaml warning layers.0.source: Layer "quakes" uses source "quake", which is not declared under sources:. Did you mean "quakes"? at line 13, column 13
⚠ 1 file(s) with warnings: 1 warning(s)A note prints as ℹ note, and a file with only notes is ✓ valid. With
more than 10 files, the human and vscode formats show a progress bar.
json prints one object: valid, a files array (each with its
absolute file path, valid, errors and warnings), and a summary of
counts. Each finding has path, message, line, column and severity;
notes are in warnings with "severity": "note" and are counted in
summary.noteCount, not warningCount. The full shape is in
Using AI agents › The JSON output.
sarif prints SARIF 2.1.0, which
GitHub code scanning reads (recipe below). Notes
have the SARIF level note.
vscode prints one line per finding,
file:line:column: severity: [path] message, where severity is error,
warning, or info for a note. VS Code’s problem matcher reads it:
see Validate on save.
preview
Section titled “preview”Start a local server that shows one document (a map or a story map, in YAML, JSON or TOML) on a page of its own, and reloads it when you save the file.
npx maplibre-yaml@latest preview <file> [options]| Argument | Required | Description |
|---|---|---|
file | Yes | The map document or story file to show (.yaml, .yml, .json or .toml) |
| Option | Type | Default | Description |
|---|---|---|---|
--port | number | 3000 | Port for the server |
--no-open | boolean | Don’t open a browser tab (by default preview opens one) | |
--debug | boolean | false | Show a panel with the parsed document as JSON. Maps only |
A mistake in the file shows as an overlay with the field path and message, in place of the map. A status bar at the bottom shows whether the server is connected and which file it shows. Data paths in the YAML resolve against the YAML file’s folder, which the server serves.
preview shows the document alone, not the HTML page you wrote around it.
To see your own page, serve the folder instead (for example npx serve .).
npx maplibre-yaml@latest preview map.yamlnpx maplibre-yaml@latest preview map.yaml --port 8080 --no-openExport a map document to a standalone MapLibre style.json: a file that
opens in Maputnik and renders in a plain
new maplibregl.Map({ style }), with no maplibre-yaml on the page. Only
type: map documents export, in format v1 or v2.
Export to style.json explains how to use the result, and
Export classes what happens to each part of the
document.
npx maplibre-yaml@latest emit <file> [options]| Argument | Required | Description |
|---|---|---|
file | Yes | Path to a type: map document in YAML, JSON or TOML |
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--out | -o | string | stdout | Write the style to this file. A folder (one that exists, or a path ending in /, like dist/) gets style.json inside it |
--with-fallbacks | boolean | on | Export what has a style.json fallback (markers become a symbol layer with pin images; fitTo becomes a fixed camera) and print a warning for each loss | |
--strict | boolean | off | Refuse the export (exit 1) if anything would change what the map shows. Cannot be combined with --with-fallbacks | |
--target | string | The maplibre-gl version the style is for, e.g. 5.6.0. Features that version lacks (such as state: below its minimum, or color-relief below 5.6) are reported | ||
--trust | string | Deprecated, and has no effect: it is no longer listed in --help, and passing it prints a note. Trust gates raw !html in popups and click.emit host events, and neither is part of a style.json, so it never changed the exported file. A later release will remove it | ||
--sprite-base | string | Absolute URL where the folder holding the style will be served. Required, with --out, when the document generates sprite images (markers, images:), because MapLibre rejects relative sprite URLs |
Warnings go to stderr, so npx maplibre-yaml@latest emit map.yaml > style.json
keeps the JSON clean. A part of the document with no style.json form prints
a contract warning, and a part whose look changes prints a lossy warning,
which --strict refuses (Reading the report).
Some documents need their data at export time. emit reads it the way the
published site would serve it: an https:// URL is fetched, and a local path
resolves against the YAML file’s folder, then the current folder and its
public/ folder.
- A
classify:layer with computed breaks (method:) has its source’s data loaded, and the breaks are written into the style. If the data can’t be loaded, the export fails and says why. - A GeoJSON
url:source is written asdata: <url>, as the style spec expects, so the exported style loads it. - Large-data files: FlatGeobuf and GeoParquet
are converted to inline GeoJSON; PMTiles and COG sources, and a Protomaps
basemap, become
pmtiles://andcog://URLs that the page showing the style must register. See Large-data files in an exported style.
npx maplibre-yaml@latest emit map.yaml # print the stylenpx maplibre-yaml@latest emit map.yaml --out dist/ # write dist/style.jsonnpx maplibre-yaml@latest emit map.yaml --strict --out dist/ # fail on any loss (CI)npx maplibre-yaml@latest emit map.yaml --out dist/ --sprite-base https://maps.example.org/dist/The last line exports a document with markers: it writes style.json and
the sprite files (mlym.json, mlym.png and their @2x versions) into
dist/, and the style points at them under https://maps.example.org/dist/.
convert
Section titled “convert”Rewrite a map or story document in another format: YAML, JSON or TOML. The output is the document as written, not a filled-in copy: no defaults are added, nothing is expanded except what the target can’t hold, and expressions stay inline where they fit.
npx maplibre-yaml@latest convert <file> --to <yaml|json|toml> [options]| Argument | Required | Description |
|---|---|---|
file | Yes | The document to convert (.yaml, .yml, .json or .toml). Its extension names its format; with another extension, the content decides |
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--to | string | Required. The format to write: yaml, json or toml. The input’s own format reprints the file | ||
--out | -o | string | stdout | Write to this file instead. It may not be the input file |
--strict | boolean | off | Exit 1 and write nothing if anything would be lost | |
--format | -f | string | text | json prints one JSON object on stdout instead (below) |
What the output can’t keep is listed on stderr, so
npx maplibre-yaml@latest convert map.yaml --to json > map.json keeps the
file clean. A loss is a comment, an anchor or merge key (written out in
full), or a null left out of TOML where leaving the key out means the same
thing. With losses the command still writes the output and exits 0, unless
--strict is set. The schema line becomes the target’s own form, and !html
becomes {"$html": …} and back, so neither counts as a loss.
The command exits 1 and writes nothing when the input is not a valid
document (warnings don’t stop it), when a value can’t be written in the
target (a null inside an expression going to TOML, inf or nan going to
JSON), or when --out names the input. A file that doesn’t exist exits 2.
npx maplibre-yaml@latest convert map.yaml --to toml # print the TOMLnpx maplibre-yaml@latest convert map.yaml --to json --out map.json # write a filenpx maplibre-yaml@latest convert story.yaml --to toml --strict # fail on any lossJSON & TOML documents › Convert between formats shows a loss report.
JSON output
Section titled “JSON output”With -f json the command prints one JSON object on stdout whatever the
outcome, with the same exit codes and nothing on stderr, so a script or an
agent reads the losses instead of parsing prose:
{ "success": true, "from": "yaml", "to": "toml", "text": "type = \"map\"\n…", "losses": [ { "kind": "comment", "message": "the comment on line 5 is dropped: # whole world", "line": 5, "column": 11 } ], "errors": []}textis the converted document; with--outit is written to the file andout(its path) replacestext.- Each loss has a
kind(comment,anchor,mergeornull), amessage, and apathor alineandcolumn. - On failure
successisfalseand there is notext.errorscarries the validation errors, or the values the target can’t hold, withpath,message,lineandcolumnasvalidate -f jsonreports them, plus acode(convert-null,toml-syntax, …). A--strictrefusal is one error of codestrict, beside thelossesthat caused it.
npx maplibre-yaml@latest convert map.yaml --to toml -f jsonschema
Section titled “schema”Print the JSON Schema for a block, read from the installed
@maplibre-yaml/core package, so it matches the version validate checks
against. The schemas describe format v1; there is no
JSON Schema for format v2.
npx maplibre-yaml@latest schema [block] [options]| Argument | Required | Default | Description |
|---|---|---|---|
block | No | map | One of map, scrollytelling, root, any |
| Option | Alias | Type | Description |
|---|---|---|---|
--out | -o | string | Write the schema to a file instead of stdout |
npx maplibre-yaml@latest schema map # print the map schemanpx maplibre-yaml@latest schema any --out any.schema.json # write a file for offline useA modeline then points at the written file:
# yaml-language-server: $schema=./any.schema.jsonSee Editor setup and Using AI agents for what the schemas are used for.
Regenerate the pattern images a Mapzen-classic
preset references. The classics are pure style spec, so their whole look
rides on *-pattern images; bake derives them reproducibly instead of
leaving them as unexplained binaries.
npx maplibre-yaml@latest bake [preset] --out <dir>| Preset | Writes | From |
|---|---|---|
crosshatch | earth.png, landuse.png, water.png | Tangram’s MIT-licensed crosshatch textures, vendored unmodified in the CLI package (assets/tangram/, with provenance). The hatches are pre-filtered for MapLibre, which samples patterns without mipmaps: twice the period, softened strokes, 32 tones |
crosshatch-walls | building-hatch.png | Tangram’s light pen hatch for a fill-extrusion-pattern, smoothed along the stroke so walls seen edge-on stay steady. The crosshatch document itself draws plain walls |
blueprint | grid.png | A generated drafting grid |
With no preset, bake lists them. The docs gallery’s committed images are
exactly this output — a test pins them pixel for pixel:
npx maplibre-yaml@latest bake crosshatch --out docs/public/classics/crosshatchnpx maplibre-yaml@latest bake blueprint --out docs/public/classics/blueprintbreaks
Section titled “breaks”Compute classify: breaks for a field from a
whole local data file, and print them ready to paste. In the browser,
computed breaks (method:) need the whole dataset: a FlatGeobuf or PMTiles
source read by view never has it, so its layer refuses method: and points
here. breaks reads every feature of the file, computes the breaks with the
same code the browser uses for a GeoJSON source, and prints a breaks: line
to put under classify: in place of method: and classes:.
npx maplibre-yaml@latest breaks <file> --field <name> [options]| Argument | Required | Description |
|---|---|---|
file | Yes | A local GeoParquet (.parquet), FlatGeobuf (.fgb) or GeoJSON (.geojson, .json) file. The extension names its format. A GeoParquet overview file is read at its full-detail level |
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--field | string | Required. The numeric property to classify, as field: | ||
--method | string | quantile | quantile, equal or jenks, as method: | |
--classes | number | 5 | How many classes, 2 to 12, as classes: | |
--normalize | string | Divide the field by this property to break a rate, as normalize: | ||
--multiply | number | Multiply the rate (100000 for “per 100,000”), as multiply:. Needs --normalize | ||
--format | -f | string | text | json prints one JSON object on stdout instead: success, file, format, field, method, classes, breaks, min, max, count and noData, or success: false and errors (as convert’s: one { path, message, code } per problem, code naming the exit code, e.g. file-not-found) |
The output is a comment naming what was computed, the field’s range, and the breaks:
npx maplibre-yaml@latest breaks districts.fgb --field population --method jenks --classes 5# population, jenks, 5 classes, from districts.fgb (48 features)# range: 2063 to 24402breaks: [4548, 9185, 15975, 20657]Paste it under the layer’s classify:, beside field::
- id: districts-fill type: fill source: districts classify: field: population # population, jenks, 5 classes, from districts.fgb (48 features) # range: 2063 to 24402 breaks: [4548, 9185, 15975, 20657] palette: YlGnBuFeatures whose value is missing, null, empty or not a number are no data: the comment counts them, and they don’t move the breaks. When ties leave fewer distinct classes than asked (40% of the features at 0 cannot fill five equal-count classes), a third comment line says how many the breaks make.
The command exits 1 when --field is missing, names a property no feature
has (it lists the ones they have), or names a text field (it says so and
suggests categories:); when a flag is out of
range; and when the file is not one of the three formats, including a
PMTiles archive, whose tiles hold a generalised subset of the data: run
breaks on the file the archive was built from. A file that can’t be read
exits 2.
npx maplibre-yaml@latest breaks tracts.parquet --field median_income # quantile, 5 classesnpx maplibre-yaml@latest breaks tracts.geojson --field cases --normalize population --multiply 100000npx maplibre-yaml@latest breaks points.fgb --field value --method equal --classes 4 -f jsonprepare
Section titled “prepare”Look at a data file and say what to do with it. prepare reports the file’s
facts, prints the one recommended route from the
Large data guide’s size-to-format table with its
commands (file names filled in), and the YAML source block to paste. It only
reads: it writes no data and runs nothing.
npx maplibre-yaml@latest prepare <file> [options]| Argument | Required | Description |
|---|---|---|
file | Yes | A local path or an http(s) URL: GeoParquet (.parquet), FlatGeobuf (.fgb), GeoJSON (.geojson, .json) or PMTiles (.pmtiles). The file’s first bytes decide its format; the extension is the fallback |
| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--format | -f | string | text | json prints one JSON object on stdout instead: success, file, facts and recommendation (verdict, route, summary, steps, yaml, alternative, notes), or success: false and errors (as breaks prints them) |
What it reads:
- GeoParquet and FlatGeobuf: only the footer or header, never the
features: rows or features, size, geometry, CRS, the
bboxcolumn, row groups and rows per group, sort order, and a GeoParquet overview file’s levels. A FlatGeobuf whose header doesn’t record its feature count (some streaming writers leave it out) is routed by its size instead: up to 10 MB it is read whole; above that, theogr2ogrcommand re-creates it with an index and the count, to read by view. - GeoJSON: the whole file up to 200 MB, to count its features. Above that it reads the first 8 MB and estimates the count from them, and the report calls it an estimate.
- PMTiles: the header and metadata: tile type, zooms, bounds and the
tile layers that
source-layer:names. An archive is already tiles, so the answer is “no change needed” and thetype: vector(orraster) source. - A URL is read with range requests, as a map reading the file by view
reads it. A server that ignores them (
python -m http.server) is refused rather than downloaded whole; the message says to host the file where range requests work, or to runprepareon a downloaded copy. Each request waits up to 30 seconds for an answer.
The route, from the number of features at full detail:
| Features | Route | What prepare prints |
|---|---|---|
| Under 50,000 | Use it as it is | ”No change needed” and the source. For a FlatGeobuf without a spatial index or a GeoParquet file not sorted by location, also the command that would make it readable by view, if you want that |
| 50,000 to 250,000 | Read it by view | For a sorted GeoParquet file with a bbox column or an indexed FlatGeobuf, “no change needed” and the source with load: viewport. Otherwise the gpio sort hilbert command (GeoParquet), the ogr2ogr index command (FlatGeobuf) or the gpio convert command (GeoJSON) first. For polygons, PMTiles as the alternative |
| Over 250,000 | PMTiles (polygons), or an overview GeoParquet file (points and lines) | The tylertoo command and the source: type: vector for PMTiles, load: viewport for the overview file, which is experimental. The other route as the alternative |
A file in a projected CRS gets the ogr2ogr -t_srs EPSG:4326 command first,
and a GeoParquet file with GeoArrow geometry the WKB rewrite; the rest of the
route then applies to the file those write. The commands are the ones the
map’s own notices print, so a notice and prepare name the same command.
npx maplibre-yaml@latest prepare parcels.geojsonparcels.geojson: GeoJSON, 300 MB features about 1,480,000 (an estimate, from the first 8.0 MB: the file is over the 200 MB it parses in full) geometry Polygon CRS EPSG:4326 (longitude/latitude)
About 1,480,000 polygons, over 250,000: PMTiles draws it at every zoom, citywide included.
Run:
Convert it to PMTiles with tylertoo (the parcel recipe; MapLibre draws zoom 14’s tiles further in): uvx --from geoparquet-io==1.7.0 gpio convert geoparquet parcels.geojson parcels.parquet uv run --with tylertoo==0.8.0 python -c "import tylertoo as t; t.overview('parcels.parquet', 'parcels-ov.parquet', min_zoom=0, max_zoom=14, collapse_square=True); t.export_pmtiles('parcels-ov.parquet', 'parcels.pmtiles', layer_name='parcels', tile_size_limit=None)"
Then paste:
sources: parcels: type: vector url: parcels.pmtiles # layers draw it with source-layer: parcels…The block pastes into the document’s sources:, and the url: is relative
to the document (for a URL argument, the URL itself):
sources: parcels: type: vector url: parcels.pmtiles # layers draw it with source-layer: parcelsThe command exits 1 with no file argument, a bad --format, a file of none
of the four formats (a Parquet file without geometry metadata included), or a
server without range requests. A path that can’t be read (missing, a folder,
no permission) or a URL that answers 404, can’t be reached, or doesn’t
answer within 30 seconds exits 2.
npx maplibre-yaml@latest prepare data/trees.fgbnpx maplibre-yaml@latest prepare https://data.example.org/lots.parquetnpx maplibre-yaml@latest prepare lots-overviews.parquet -f jsonProject settings file
Section titled “Project settings file”A maplibre-yaml.config.json file in the folder you run the CLI from sets
default options for validate and preview. A .yaml, .yml, .js or
.mjs file of the same name works too.
{ "validate": { "format": "human", "strict": false, "ignorePatterns": ["**/node_modules/**", "**/dist/**", "**/*.backup.yaml"] }, "preview": { "port": 4000, "open": false }}An option you type on the command line wins, then the settings file, then
the built-in default. With the file above, preview map.yaml serves on port
4000 without opening a browser, and preview map.yaml --port 5000 serves on
5000. The preview settings need CLI 0.6.1 or later; 0.6.0 ignores them.
| Setting | Type | Default | Description |
|---|---|---|---|
validate.format | string | human | Output format |
validate.strict | boolean | on in CI | Treat warnings as errors |
validate.ignorePatterns | string[] | ["**/node_modules/**", "**/dist/**"] | Glob patterns to skip. Setting it replaces the default, so list those two again if you still want them skipped |
preview.port | number | 3000 | Server port |
preview.open | boolean | true | Open a browser tab |
preview.debug | boolean | false | Show the debug panel |
Exit codes
Section titled “Exit codes”| Code | Meaning | Commands |
|---|---|---|
0 | Success. For emit, the style was written; warnings, if any, are on stderr | All |
1 | Invalid input: validation or syntax errors, warnings in strict mode, an unknown template or option, a non-empty init folder, an emit --strict or convert --strict refusal, a breaks field that is missing or not numeric, a file prepare doesn’t read or a server without range requests | validate, emit, convert, init, schema, bake, breaks, prepare |
2 | File not found, no file matched a pattern, a folder held no map or story document, or a prepare URL answered 404, couldn’t be reached or didn’t answer in 30 s | validate, preview, emit, convert, breaks, prepare |
4 | Unexpected error (for emit, for example, a basemap or image that could not be fetched) | All |
Environment variables
Section titled “Environment variables”When CI is set (GitHub Actions and other CI services set it), validate
runs in strict mode unless you pass --no-strict.
Set it to any value to print debug messages and the stack trace of an unexpected error, for a bug report:
DEBUG=1 npx maplibre-yaml@latest validate "**/*.yaml"NO_COLOR
Section titled “NO_COLOR”Disable colored output:
NO_COLOR=1 npx maplibre-yaml@latest validate map.yamlIntegration examples
Section titled “Integration examples”npm scripts
Section titled “npm scripts”With the CLI installed in the project (npm install --save-dev @maplibre-yaml/cli), scripts can use the short name:
{ "scripts": { "validate": "mlym validate 'maps/**/*.yaml'", "validate:watch": "mlym validate 'maps/**/*.yaml' --watch", "preview": "mlym preview map.yaml", "export": "mlym emit map.yaml --strict --out dist/" }}GitHub Actions
Section titled “GitHub Actions”validate is strict in CI, so warnings fail the job too:
name: Validate mapson: [push, pull_request]jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npx maplibre-yaml@latest validate "**/*.yaml"GitHub code scanning
Section titled “GitHub code scanning”SARIF output shows each finding as an annotation on the pull request:
name: Code scanningon: [push, pull_request]jobs: analyze: runs-on: ubuntu-latest permissions: security-events: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npx maplibre-yaml@latest validate "**/*.yaml" --format sarif > results.sarif - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarifPre-commit hook
Section titled “Pre-commit hook”With husky, check the YAML files staged for a commit (it does nothing when there are none):
#!/bin/shgit diff --cached --name-only --diff-filter=ACM -- '*.yaml' '*.yml' \ | xargs -r npx maplibre-yaml@latest validateTroubleshooting
Section titled “Troubleshooting”Command not found
Section titled “Command not found”mlym and maplibre-yaml exist only after an install, and npx mlym fails
with a 404 without one. Use npx maplibre-yaml@latest, or install the CLI (see
Running the CLI). After a global install, check that
your PATH includes npm’s global bin folder (npm prefix -g).
Glob patterns not matching files
Section titled “Glob patterns not matching files”- Quote the pattern,
"maps/**/*.yaml", so your shell doesn’t expand it. - Patterns are relative to the folder you run the command in.
- Check
validate.ignorePatternsin the project settings file:validateprintsSkipped …: excluded by validate.ignorePatternsfor a file it skipped.
Watch mode not detecting changes
Section titled “Watch mode not detecting changes”- Check that the file matches the pattern you are watching.
- On Linux, a large tree can exceed the file-watcher limit; raise it
(
fs.inotify.max_user_watches) or watch a smaller pattern.
Settings file ignored
Section titled “Settings file ignored”The CLI reads maplibre-yaml.config.* from the folder you run it in, not
from the YAML file’s folder. An option typed on the command line overrides
it.
See also
Section titled “See also”- The CLI (mlym): a walkthrough of the four main commands.
- Editor setup: checks as you type, and validation on save in VS Code.
- Export to style.json: using what
emitwrites. - YAML reference: every key of the format.
Moved sections
Section titled “Moved sections”Aliases
Section titled “Aliases”The command names are listed under Running the CLI.