Skip to main content

Style file format

Last updated: October 13, 2026 | 17 minutes read

For developers: the JSON file that Style (JSON) in the Download dialog gives you, and the JSON Schema that checks it. Your app does not need this file; it loads the .style file (see Download and use in your app). Use the JSON to keep a copy of a style, upload it to the portal, or write or check a style with your own tools.

To have an AI assistant write a style, give it the Studio llms.txt, which lists the four files it needs: this page, Map data sources, the layer types reference and the JSON Schema. Check what it writes with the schema (see The JSON Schema), then upload it to the portal and open it in Studio (see Start from a style file). An assistant cannot write a font: leave fonts_section out, and add the font in Studio afterwards (see Fonts).

Coming from other map style formats​

The format is not the JSON style format that other web map libraries use. If you, or an AI assistant, have written styles in that format, these are the differences that matter:

In other map style formatsIn a Magic Lane style file
layers at the top of the fileLayers sit in map_schemes_section.map_schemes[0].definition.layers.
"source-layer": "water" names the dataThe layer's type and sourcedata_id together name the data. See Map data sources.
Expressions such as ["interpolate", ...]Every paint property is a stop list, even a constant. See Paint values.
Colours as "#ff8800" or rgba()Colours are integers. See Colours.
The first layer is drawn at the bottomThe first layer is drawn on top; background is the last layer.
"id": "water" names the layerid is a number that goes down from layer to layer and ends at 1; the name goes in COMMENT.
Zoom 0 to 22Zoom 0 to 17.
"text-field": "{name}"Layers on map data draw their names by themselves; label_text is for your own data. See Text.
"filter": ["==", ...]A filter object. See Filters.
A sprite sheetImages sit inside the file, in icons_section. See Images.
text-font on each layer, with a glyphs URLOne font for the whole style, as font files inside the file, in fonts_section. See Fonts.

The file​

A style file is one JSON object with three sections, and a fourth when the style has its own font:

KeyWhat it holds
sources_list{ "sources": [...] }. Usually empty: layers use built-in sources. The external satellite source, satelliteExternal, is declared here.
map_schemes_section{ "COMMENT": "...", "map_schemes": [ scheme ] }, with exactly one scheme.
icons_section{ "COMMENT": "...", "icons": [ ... ] }, the style's images. See Images.
fonts_sectionOptional. { "regular": "data:font/ttf;base64,...", ... }, the style's font. See Fonts.

The scheme:

KeyWhat it holds
idAlways 1.
nameThe style's name.
groups_section{ "groups": [ { "id", "name", "collapsed"?, "parent_id"? } ] }, the groups of the layer list. parent_id names the parent of a nested group.
definition{ "layers": [ layer, ... ] }. The first layer is drawn last, on top.

Drawing order​

The order of definition.layers is the drawing order: the first layer is drawn on top, the last at the bottom. The official templates use this order, from top to bottom:

  1. atmosphere, if the style has one, then POIs, road names, one-way arrows and road shields
  2. City, area, country and water labels
  3. The layers an app draws at run time: routes, traffic, drawings
  4. Borders, then roads
  5. Buildings
  6. Lines that are not roads (rail, runways, narrow water lines), then areas: lakes and rivers (Water) above parks and other land use
  7. Island, Water Sea and Land
  8. background, always last. The templates give it the water colour.

A layer​

KeyRequiredWhat it holds
COMMENTyesThe layer's name.
idyesA number in drawing order: the first layer has the highest id, the last has 1, and each id is lower than the one before. Gaps are allowed; the official templates have them.
typeyesThe layer type, such as polygon, line, symbol, street or areaname. See the layer types reference.
sourcenoA built-in source: map, overlay, satellite, satelliteExternal, elevation, weather (wind) or contourpolyline (contour lines).
sourcedata_idnoThe data the layer draws: a public data source's number, or the tileset number of one of your data sources. The same number means different data in different layer types; see Map data sources. One number per layer.
minzoom, maxzoomnoThe zoom range the layer is drawn in, 0 to 17.
group_idnoThe id of the layer's group.
filternoWhich features are drawn. See Filters.
label_textnoThe text a symbol layer draws. See Text.
metaCategoryIDnoThe point-of-interest category a POI layer draws; the engine draws that category's icon.
contour_intervalnoThe interval between lines, on contour layers.
isTemplateno1 on layers an app draws at run time (routes, traffic). Leave it out on other layers.
paintyesThe paint properties. See Paint values.

Paint values​

Every paint property is a stop list, even when it does not change with zoom:

"line-width": {
"line_width_stops": {
"line_width_stop": [
{ "level_id": 5, "width": 0.5 },
{ "level_id": 10, "width": 1 },
{ "level_id": 13, "width": 1.75 }
],
"easing_type": 1
}
}

These are the first three stops of the Residential street line in Basic 1.

Text sizes and icon sizes are millimetres on screen. Line widths are not screen units: the map engine scales them with the camera height, so start from the values in the official templates. A border width (line-outline-width) is added on each side of the line.

level_id is the zoom of the stop, 0 to 17; a constant is a single stop. easing_type sets how the value moves between stops (see Easings). The one exception is 3dMesh (0 for 2D, 1 for 3D), written as a bare number under paint.

Colours are integers; see Colours.

Each property has its own wrapper, stop array and value key:

PropertyWrapperStop arrayValue key
fill-color, fill-outline-color, line-inner-color, line-outer-color, text-color, text-outline-color, background-symbol-colorcolor_stopscolor_stopcolor
fill-opacity, fill-outline-opacityopacity_stopsopacity_stopopacity
line-width, line-outline-widthline_width_stopsline_width_stopwidth
line-blurline_blur_stopsline_blur_stopblur
text-size, text-outline-sizetext_size_stopstext_size_stopsize
text-paddingtext_padding_stopstext_padding_stopsize
text-max-widthtext_max_width_stopstext_max_width_stopsize
text-row-heighttext_row_height_stopstext_row_height_stopsize
icon-sizeicon_size_stopsicon_size_stopsize
icon-paddingicon_padding_stopsicon_padding_stopsize
iconicon_stopsicon_stopcustom_icon
text-rotation-angleangle-stops (with a hyphen)angle_stopangle
icon-rotation-angleangle_stopsangle_stopangle
translate-on-X, translate-on-Ytranslate_stopstranslate_stopvalue
offset-on-X, offset-on-Yoffset_stopsoffset_stopvalue
text-anchortext_anchor_stopstext_anchor_stopintvalue
text-transformtext_transform_stopstext_transform_stopintvalue
text-styletext_style_stopstext_style_stopintvalue
text-justifytext_justify_stopstext_justify_stopintvalue
text-overlap, text-optional, text-placementtext_overlap_stops, text_optional_stops, text_placement_stopstext_overlap_stop, text_optional_stop, text_placement_stopboolvalue
icon-overlap, icon-optional, icon-placementicon_overlap_stops, icon_optional_stops, icon_placement_stopsicon_overlap_stop, icon_optional_stop, icon_placement_stopboolvalue

Options are stored as numbers:

PropertyValues
text-anchorleft-top 6, left-center 4, left-bottom 7, center-top 2, center-center 9, center-bottom 1, right-top 8, right-center 3, right-bottom 5
text-transformnone 0, lowercase 1, uppercase 2
text-stylenormal 0, bold 1, italic 2
text-justifyleft 1, center 4, right 7

Which properties each layer type takes is in the layer types reference.

Colours​

A colour is one 32-bit integer in ABGR order: (a << 24) | (b << 16) | (g << 8) | r, where a is the opacity (255 opaque, 0 invisible). To turn #rrggbb into the integer: take the red, green and blue values as numbers from 0 to 255, then compute a × 16777216 + b × 65536 + g × 256 + r.

Coloura, b, g, rInteger
white #ffffff255, 255, 255, 2554294967295
black #000000255, 0, 0, 04278190080
orange #ff5a1f255, 31, 90, 2554280244991
light blue #aadaff255, 255, 218, 1704294957738
pink #ff69b4255, 180, 105, 2554290013695
red at half opacity #ff0000128, 0, 0, 2552147483903

Note the order: blue comes before red, the reverse of #rrggbb. Studio shows colours as hex; the file stores only integers.

Easings​

easing_typeNameeasing_typeName
1linear12easeOutQuart
2easeInSine13easeInOutQuart
3easeOutSine14easeInQuint
4easeInOutSine15easeOutQuint
5easeInQuad16easeInOutQuint
6easeOutQuad17easeInExpo
7easeInOutQuad18easeOutExpo
8easeInCubic19easeInOutExpo
9easeOutCubic20easeInCirc
10easeInOutCubic21easeOutCirc
11easeInQuart22easeInOutCirc

Filters​

"filter": {
"condition": "all",
"expression": [
{ "key": "category", "condition": "in", "values": ["culture", "food"] },
{ "key": "name", "condition": "has" }
]
}

The top condition is all, any or none. Each entry's condition is one of has, hasnot, in, notin, ==, !=, >, <, >=, <=; has and hasnot take no values. On the tutorial's sample file, bucharest-places.geojson, this filter keeps four of the eight places: the three culture places and the food market, which all have a name.

Every value of your own data is stored as text, and text comparisons ignore upper and lower case. >, <, >= and <= compare whole numbers and work only on the map data's number fields, Lanes count on roads and Height on buildings. On text, including every field of your own data, they let every feature through. The fields of the map data are listed in Map data sources.

Text​

Layers that draw map data, such as city labels (symbol), road names (street), area labels (areaname), country labels (country) and POIs, draw their text by themselves: leave label_text out. None of the official templates sets it on these layers. Use label_text on layers that draw your own data sources, to choose what they show.

A symbol layer's text is an expression, stored as a JSON string in label_text.label_expression. Its pieces are { "text": "..." }, { "number": 20 }, { "get_v": "name" } for a value of the feature, and { "concat": [ ... ] } or { "*": [ ... ] } over their arguments. For example, the label concat(|name|, " (", |category|, ")") in Studio, which reads "Obor Market (food)", is stored as:

"label_text": {
"label_expression": "{\"concat\":[{\"get_v\":\"name\"},{\"text\":\" (\"},{\"get_v\":\"category\"},{\"text\":\")\"}]}"
}

The result of * is shown with two decimals: product(|rating|, 20) on a rating of 4.6 reads 92.00.

Images​

Each image in icons_section.icons is { "id": "bike.png", "value": "data:image/png;base64,..." }: a unique name and the image as a PNG, JPEG or SVG data URI. Images can be up to 400 KB each and 4 MB in total.

A layer uses an image through the icon property, whose custom_icon holds the image's id; "" means no icon. POI layers draw their category's icon from metaCategoryID instead.

Fonts​

fonts_section holds the style's own font for its labels. It comes after icons_section and is left out when the style uses the map's built-in font:

"fonts_section": {
"regular": "data:font/ttf;base64,AAEAAAAQAQAABAAA...",
"bold": "data:font/ttf;base64,AAEAAAAQAQAABAAA..."
}
KeyWhat it holds
regularThe face for layers whose text-style is normal (0).
boldThe face for layers whose text-style is bold (1).
italicThe face for layers whose text-style is italic (2).

Each key is optional and works on its own: a face that is missing is drawn in the map's built-in font.

Each value is a whole TTF or OTF file as a base64 data URI, data:font/ttf;base64,... or data:font/otf;base64,...; there is no size limit, but the file is part of every save and download of the style, and the engine reads it whole when the style is opened. WOFF and WOFF2 web fonts and font collections (.ttc) are not read. There is no font per layer: every label uses this font, and a character the font does not have is drawn in the built-in font.

The font is part of the file, so it is in the .style file too. Font data cannot be written by hand or generated: a file Studio cannot read as a font is left out when the style is opened. Add fonts in Settings › Typography (see The style's font).

A complete small style​

A whole style with nine layers: city labels, motorway names, motorways, residential streets, parks, water, sea, land and the background. Its colours and sizes come from the Basic 1 template. Download it as example-small-style.json.

{
"sources_list": { "sources": [] },
"map_schemes_section": {
"COMMENT": "Define all available map schemes.",
"map_schemes": [{
"id": 1,
"name": "Small example",
"groups_section": { "groups": [
{"id": 1, "name": "Labels"},
{"id": 2, "name": "Roads"},
{"id": 3, "name": "Areas"}
] },
"definition": { "layers": [
{ "COMMENT": "City labels (over 500k)", "id": 9, "type": "symbol", "source": "map", "sourcedata_id": 0, "minzoom": 3, "maxzoom": 13, "group_id": 1, "paint": {
"text-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4281807665}], "easing_type": 1}},
"text-size": {"text_size_stops": {"text_size_stop": [{"level_id": 4, "size": 3}, {"level_id": 13, "size": 6}], "easing_type": 1}},
"text-outline-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294967295}], "easing_type": 1}},
"text-outline-size": {"text_size_stops": {"text_size_stop": [{"level_id": 0, "size": 1}], "easing_type": 1}}
} },
{ "COMMENT": "Motorway names", "id": 8, "type": "street", "source": "map", "sourcedata_id": 0, "minzoom": 11, "maxzoom": 17, "group_id": 1, "paint": {
"text-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4280829573}], "easing_type": 1}},
"text-size": {"text_size_stops": {"text_size_stop": [{"level_id": 12, "size": 2.5}, {"level_id": 17, "size": 6}], "easing_type": 1}},
"text-outline-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294967295}], "easing_type": 1}},
"text-outline-size": {"text_size_stops": {"text_size_stop": [{"level_id": 0, "size": 1}], "easing_type": 1}}
} },
{ "COMMENT": "Motorway", "id": 7, "type": "line", "source": "map", "sourcedata_id": 0, "minzoom": 4, "maxzoom": 17, "group_id": 2, "paint": {
"line-inner-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4288801791}], "easing_type": 1}},
"line-outer-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4284862454}], "easing_type": 1}},
"line-width": {"line_width_stops": {"line_width_stop": [{"level_id": 4, "width": 7}, {"level_id": 10, "width": 3}, {"level_id": 13, "width": 2.5}, {"level_id": 17, "width": 5}], "easing_type": 1}},
"line-outline-width": {"line_width_stops": {"line_width_stop": [{"level_id": 4, "width": 1}, {"level_id": 11, "width": 0.5}, {"level_id": 16, "width": 0.1}], "easing_type": 1}}
} },
{ "COMMENT": "Residential", "id": 6, "type": "line", "source": "map", "sourcedata_id": 6, "minzoom": 11, "maxzoom": 17, "group_id": 2, "paint": {
"line-inner-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294967295}], "easing_type": 1}},
"line-outer-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4292662998}], "easing_type": 1}},
"line-width": {"line_width_stops": {"line_width_stop": [{"level_id": 10, "width": 1}, {"level_id": 13, "width": 1.75}, {"level_id": 15, "width": 1.5}], "easing_type": 1}},
"line-outline-width": {"line_width_stops": {"line_width_stop": [{"level_id": 0, "width": 0.15}], "easing_type": 1}}
} },
{ "COMMENT": "Green Park", "id": 5, "type": "polygon", "source": "map", "sourcedata_id": 23, "minzoom": 0, "maxzoom": 17, "group_id": 3, "paint": {
"fill-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4291160517}], "easing_type": 1}}
} },
{ "COMMENT": "Water", "id": 4, "type": "polygon", "source": "map", "sourcedata_id": 9, "minzoom": 0, "maxzoom": 17, "group_id": 3, "paint": {
"fill-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294957738}], "easing_type": 1}}
} },
{ "COMMENT": "Water Sea", "id": 3, "type": "polygon", "source": "map", "sourcedata_id": 94, "minzoom": 0, "maxzoom": 17, "group_id": 3, "paint": {
"fill-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294957738}], "easing_type": 1}}
} },
{ "COMMENT": "Land", "id": 2, "type": "polygon", "source": "map", "sourcedata_id": 0, "minzoom": 0, "maxzoom": 17, "group_id": 3, "paint": {
"fill-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294638072}], "easing_type": 1}}
} },
{ "COMMENT": "Background", "id": 1, "type": "background", "sourcedata_id": 0, "paint": {
"fill-color": {"color_stops": {"color_stop": [{"level_id": 0, "color": 4294957738}], "easing_type": 1}}
} }
] }
}]
},
"icons_section": { "COMMENT": "Define all available icons for schemes.", "icons": [] }
}

For a full style to study or start from, download the Basic 1 template, as the portal offers it: template-basic-1.json (222 layers and 6 images). One value is corrected so that the file passes the schema: the portal's copy stores the text anchor of the Narrow Water Line Label layer as the text "2", and this file has the number 2.

The JSON Schema​

The rules above are published as a JSON Schema (draft 2020-12): engine-style.schema.json.

It checks the sections and the single scheme; every layer field and its type; the layer type and source names; every paint property's wrapper, stop array, value key and range (no negative sizes, opacity 0 to 1, zoom 0 to 17, easing_type 1 to 22); which properties each layer type takes; colours as integers; filters, text, images and the font's faces. Each property also carries its help text from the Properties column. Fields the engine reads but Studio does not write are accepted and marked "x-written-by-studio": false.

A few rules link one part of the file to another, which a schema cannot express. They are listed in the schema under x-checks-not-in-schema: layer ids go down from each layer to the next and end at 1; every group_id and parent_id names an existing group; every custom_icon names an image; minzoom is not above maxzoom; each fonts_section value is a real TTF or OTF file.

To check a file, save the schema next to it and run, with Python and jsonschema 4.18 or later (pip install "jsonschema>=4.18"):

python3 -c "import json, jsonschema; jsonschema.validate(json.load(open('my-style.json')), json.load(open('engine-style.schema.json')))"

No output means the file passed; otherwise the first problem is printed with its place in the file.

See also: Map data sources · Layer types reference · Download and use in your app