Meratyn Project Format
Projects are saved as .meratyn.json files.
Meratyn writes project JSON compactly. This keeps embedded GeoJSON practical for large local datasets while preserving the same JSON schema and load compatibility; use a JSON formatter when reviewing a project file by hand.
When a project needs an explicit local-save choice (such as embedding local vector data or keeping credentials), Meratyn remembers that choice only for repeat saves of the current open project. It is never serialized, shared, or reused after opening a different project. Turning off optional data embedding still preserves canonical GeoJSON already stored on a layer and authored camera formulas. A local save acknowledges its captured authoring revision; edits made while it is running remain unsaved.
Before an upload, the Share dialog warns when a project still points to local files, private-network hosts, or credential-bearing URLs. The warning does not block sharing; it helps authors identify data a recipient may be unable to load.
The file is a recursive override of Meratyn’s canonical default project, not a
full dump of runtime state. An untouched project therefore exports as {}.
Objects contain only changed keys; ordered project content such as layers,
dashboard widgets, and story chapters remains as complete arrays when present.
Opening a file overlays its values on the same defaults before the project is
loaded into the app.
Layer styles omit properties equal to the built-in style defaults, including empty expressions and disabled options. Changed nested style objects and rule arrays retain their replacement semantics. Layer visibility and opacity, group settings, secondary cameras, story chapters, and print settings also omit their known defaults. Array order and item identities remain intact. Feature data and extension settings keep meaningful nulls, empty strings, and empty arrays; these values are not removed indiscriminately.
Extrusion height fields store feature thickness in meters above the configured
base. The renderer places the roof at base + height; changing the base moves
the feature without changing its thickness. This applies to both property-based
heights and height expressions.
Override schema
Every field below is optional in the file. An omitted field keeps its canonical default.
| Field | Type | Description |
|---|---|---|
version | string | Format version (0.2.0) |
name | string | Project display name |
mapView | object | center, zoom, bearing, pitch, optional bbox |
mapControls | object | Optional visibility and corner-position overrides for project-owned built-in map controls |
basemapStyleUrl | string | MapLibre style JSON URL, or an empty string for a blank background |
basemapVisible | boolean | Whether the Background layer is visible |
basemapOpacity | number | Background layer opacity from 0 to 1 |
layers | array | Layer definitions (see below) |
styles | object | Map of layer id → LayerStyle |
plugins | object | Optional external plugin manifest URLs, active plugin IDs, plugin map-control positions, and plugin settings |
legend | object | Optional Print Layout legend customizations (title, grouping, ordering, per-item rename/hide) |
printLayout | object | Optional normalized Print Layout composer settings, including atlas and data-block selection |
storymap | object | Optional story chapters and presentation settings; omitted only when there is no authored content |
widgets | array | Optional Dashboard panel chart widgets (see below); omitted when there are none |
dashboardColumns | number | Optional Dashboard widget-grid column count (1-6, default 2); omitted when default |
styleLibrary | array | Optional project-scoped Style Manager entries (name, tags, kind, LayerStyle subset); omitted when empty |
comments | array | Optional map review threads anchored to a point or feature (see below); omitted when there are none |
metadata | object | Free-form project metadata |
Public showcase workspace
Managed public examples can opt into a focused, project-scoped workbench. This metadata opens one relevant feature surface and allowlists only the menus, commands, data sources, and plugins used by the scenario:
{
"metadata": {
"publicShowcase": true,
"showcase": {
"surface": "processing-history",
"workspace": {
"allowedMenus": ["project", "view", "processing", "controls"],
"allowedMenuItems": [
"project.share",
"view.zoomIn",
"processing.whitebox",
"processing.history"
],
"allowedDataSources": [],
"allowedPlugins": ["maplibre-gl-components", "maplibre-gl-swipe"],
"allowedComponentControls": ["terrain"]
}
}
}
}
The runtime honors showcase.workspace only when publicShowcase is exactly
true. It decodes the allowlists into disposable project-session state and
intersects them with the user’s local UI profile. The workspace can therefore
hide additional discovery surfaces but cannot reveal an item hidden by the
user or overwrite their preferences. Settings → Interface remains reachable.
Ordinary projects ignore this presentation metadata.
Map preferences may include coordinateFormat (dd, dms, ddm, or utm),
which controls the live pointer readout. The field is optional for backwards
compatibility and defaults to dd when omitted or invalid. The selected feature
set remains runtime capability state rather than authored project data; selected
feature statistics therefore follow the current session selection and are not
serialized into the project.
allowedComponentControls is an optional complete allowlist for the
MapLibre Components control grid. An empty list hides the grid while preserving
the plugin runtime needed by restored COG, Zarr, and other plugin-owned layers;
omitting the field preserves the full grid for ordinary projects.
showcase.guide is the bilingual project guide opened from the map. It holds a
title, introduction, ordered steps (title and body), requirements,
and an optional glossary of { term, definition } entries, each an
{ en, es } pair. Glossary terms that appear in the introduction, step bodies,
or requirements become inline explanations, matched case-insensitively as
whole words so that buyers without GIS training can follow the guide.
showcase.translations shows the rest of the project in the interface
language. It maps a language code to a table keyed by the project’s English
display text, for example { "es": { "Fatal crashes": "Siniestros fatales" } }.
When a project opens, the app replaces display text (layer, group, and model
node names; widget, legend, and story titles; field, class, and legend labels;
descriptions; and provenance methods) with the table’s entry for the current
language. Ids, URLs, field names that styles and filters read, and data values
never change, so a translated project renders and queries exactly like the
original. A widget category may map to a field holding the same categories
in the target language, such as "pattern_en": "pattern_es". Text already in
another language maps back to English first, so a project saved while
translated reopens correctly; a language without a table falls back to English.
Explore showcases keep their tables in
scripts/assets/showcases/translations/<slug>.es.json, and
bun scripts/assets/explore-catalog.ts --check fails when a display string has
no translation or an entry is no longer used.
Plugin state
{
"manifestUrls": ["https://example.com/plugins/example-plugin/plugin.json"],
"activePluginIds": ["maplibre-layer-control", "maplibre-gl-swipe"],
"mapControlPositions": {
"maplibre-layer-control": "top-right",
"maplibre-gl-swipe": "top-left"
},
"settings": {
"maplibre-gl-swipe": {
"orientation": "vertical",
"position": 50,
"collapsed": false,
"active": true,
"leftLayers": ["layer-a"],
"rightLayers": ["layer-b"]
}
}
}
Projects without a plugins section open with the built-in default plugin state.
Built-in map controls
mapControls stores only values that differ from the built-in defaults for
Navigation, Fullscreen, Compass, Geolocate, Globe, Terrain, Scale, Attribution,
Logo, and Maptoolkit Logo. The section is omitted when every control is at its
default. On open, these sparse overrides are merged onto the canonical defaults,
so the Map menu, saved JSON, and restored map all read one contract.
{
"visibility": { "geolocate": true },
"positions": {
"geolocate": "bottom-left"
}
}
Plugin-owned controls remain under plugins; they are not duplicated in
mapControls.
Credentials at external egress
Project files can contain service URLs and plugin configuration. Before a project
is shared, exported as HTML, sent to an external embed host, or saved into the
saved layer library (My Data), Meratyn creates a detached copy that removes
credential fields, signed URL parameters and userinfo. Runtime
environment variables and geocoder keys are cleared, and opaque plugins.settings
is cleared wholesale. Inline GeoJSON remains data and is copied unchanged.
An ordinary local Save offers an explicit choice to keep credentials in that local project file; the external egress paths never offer that override.
The existing co-located Python widget may request full project fidelity through
trustedWidget: true on its accepted host message. This narrow widget bridge is
not a general trust grant: ordinary external embeds remain credential-redacted.
Standalone HTML exports additionally sandbox their viewer iframe. Only the capabilities needed for script execution, same-origin storage/handshake, form submits, and anchor downloads are enabled; popups and top-level navigation are withheld.
Comments
comments stores bounded review threads. Every comment has an id, an author
(name and hex color), non-empty body, valid timestamp createdAt,
resolved flag, and replies; each reply has its own id, author, body,
and createdAt. Point anchors store lngLat; feature anchors store layerId
and string or numeric featureId, with optional lngLat for a fallback map
position.
{
"comments": [
{
"id": "comment-1",
"anchor": { "type": "point", "lngLat": [-75.5636, 6.2442] },
"author": { "name": "Ada", "color": "#123abc" },
"body": "Verify this boundary.",
"createdAt": "2026-08-04T05:00:00.000Z",
"resolved": false,
"replies": [
{
"id": "reply-1",
"author": { "name": "Lin", "color": "#0f766e" },
"body": "Confirmed.",
"createdAt": "2026-08-04T05:05:00.000Z"
}
]
},
{
"id": "comment-2",
"anchor": {
"type": "feature",
"layerId": "parcels",
"featureId": "42",
"lngLat": [-74.071, 4.712]
},
"author": { "name": "Ada", "color": "#123abc" },
"body": "Check the owner attribute.",
"createdAt": "2026-08-04T05:10:00.000Z",
"resolved": true,
"replies": []
}
]
}
Legend
The map and Print Layout legends derive their classes from visible layers’
symbology. The legend object stores presentation edits and class identity, so
customizations survive layer additions and removals.
{
"title": "Legend",
"groupByLayer": true,
"order": ["layer-b", "layer-a"],
"overrides": {
"layer-a": { "label": "Roads" },
"layer-b::0": { "label": "Low" },
"layer-b::1": { "hidden": true }
}
}
title— heading drawn above the legend entries.groupByLayer— whentrue, graduated/categorized classes are grouped under a per-layer heading; whenfalse, classes are listed flat.order— top-level entry order by layer id (top-first); layers not listed keep their default order after the listed ones.overridesstores per-itemlabelandhiddenedits. A layer id names a whole entry. Historical${layerId}::${index}keys name displayed classes;${layerId}::stop:${sourceIndex}keys retain generated classes by authored stop position when sampling changes.
Reserved ${layerId}::identity:stop:${sourceIndex} entries contain
{ "classIdentity": true }. They retain generated-class ownership after its last
label or visibility edit is cleared. These entries are metadata, never visible
labels or translation inputs. Keep them when hand-editing a saved project.
Removing the layer’s legend customizations removes its identity entries too.
Projects without a legend section open with the default legend (auto-generated
from the layers, titled “Legend”).
Story map
A story map turns the project into a scroll-driven narrative. Each chapter captures a camera view plus text, and can fade project layers in or out on enter/exit. Settings entered before the first chapter are saved and restored. The section is omitted only when it has no chapters and all settings are at their defaults.
{
"title": "A Tour of Three Cities",
"subtitle": "Built with Meratyn",
"byline": "By the Meratyn team",
"footer": "Source: OpenStreetMap",
"theme": "dark",
"showMarkers": true,
"markerColor": "#3fb1ce",
"inset": false,
"insetPosition": "bottom-right",
"chapters": [
{
"id": "intro",
"title": "Cartagena",
"description": "A walled city on the Caribbean coast. <em>HTML allowed.</em>",
"image": "https://example.com/cartagena.jpg",
"alignment": "left",
"hidden": false,
"location": {
"center": [-75.5144, 10.391],
"zoom": 11,
"pitch": 45,
"bearing": 0
},
"mapAnimation": "flyTo",
"rotateAnimation": false,
"onChapterEnter": [{ "layerId": "layer-a", "opacity": 1, "duration": 2000 }],
"onChapterExit": [{ "layerId": "layer-a", "opacity": 0 }]
}
]
}
alignment is one of left, center, right, full; mapAnimation is
flyTo, easeTo, or jumpTo. Layer opacity changes reference project layer
ids. Build and present story maps from Project → Story Map, or export a
self-contained HTML page for static hosting.
Relations
relations links features of two layers one-to-many, such as the constructions
registered on a cadastral lot. Nothing is copied between layers; a feature’s
related records are resolved when they are shown. Keys match as persistent
joins do: empty values never match, and non-empty values compare as strings.
Relations to a layer that no longer exists, and malformed entries, are dropped
when the project is read. The section is omitted when the project has none.
{
"relations": [
{
"id": "lot-constructions",
"name": "Constructions on this lot",
"parent": { "layerId": "igac-lots", "field": "CODIGO" },
"child": { "layerId": "igac-constructions", "field": "TERRENO_CO" }
}
]
}
A feature on the parent layer lists its children under the relation’s name;
a feature on the child layer shows its parent.
World
world gives a project a walkable 3D world. It opens inside the map area of
the same shell, so the layer list, docks, dashboard, and selection keep working
on the same project: visibility, styles, and filters set there show in the
world, and a feature selected in the world is selected everywhere. A 3D
world button on the map opens it and Map returns; a showcase whose
surface is world opens with it. The world draws the project’s own visible
layers with their own styles: vector layers become geometry (extruded polygons
use the layer’s extrusion height, base, and color, and the 3D wall and roof
colors), web tile layers are draped on the ground, and each feature’s record
comes from its layer’s featurePresentation and the project’s relations.
{
"world": {
"origin": { "lng": -75.7917, "lat": 8.8846 },
"radius": 350,
"place": { "name": "Cereté", "region": "Córdoba" },
"start": {
"lng": -75.7915,
"lat": 8.884672,
"heading": 345,
"home": { "layerId": "lots", "featureId": "231620101000001960008000000000" }
},
"streetLayer": { "layerId": "streets", "nameField": "name", "surfaceField": "surface" },
"toggles": ["imagery", "not-in-cadastre"],
"viewpointFields": { "heading": "view_heading", "lat": "view_lat", "lng": "view_lng" },
"model": {
"url": "https://storage.example.org/worlds/cerete/cerete-world.glb",
"featureLayerId": "buildings",
"idField": "bid",
"roofGroupField": "roof_group",
"frame": {
"origin": { "lng": -75.7917, "lat": 8.8846 },
"metersPerDegree": { "lat": 111320, "lng": 109985 }
},
"drape": ["Ground", "Lots"],
"surfaces": [{ "layerId": "lots", "height": 0.18 }]
}
}
}
originanchors the world’s local meter frame;radiusis the half-width of the square world in meters (50 to 2,000, or up to 8,000 with a tiled model).placenames the world in its header;regionstands in for the street name while no street is near.startis where a visitor first stands and Home returns to, withheadingin degrees clockwise from north.homeis the feature Home selects.streetLayernames the line layer whosenameFieldsays which street the visitor is on;surfaceField(concrete,asphalt, orunpaved) paves the draped ground.toggleslists layers offered as quick switches over the world. They switch the same visibility as the Layers panel.viewpointFieldsnames feature properties holding an authored street-level viewpoint, used by Go there and Street View instead of one derived from the feature’s extent.modelis a glTF neighbourhood (optionally Draco-compressed) drawn instead of the extrusions offeatureLayerId. Meshes whose node hasextras.barrio = "building"carry each feature’sidFieldvalue in their second UV set, so picking a mesh selects the feature, and hover, selection, and tints of other visible layers containing the same ids paint the model.framestates the model’s own equirectangular frame so it lines up with the layers; nodes indrapetake the ground imagery;surfacesare walkable heights above the ground;roofGroupFieldgroups records under one roof so the roof takes one color from the imagery. The map draws the same model in place of that layer’s extrusions: the layer stays as invisible footprints for clicks and selection, the model’s flat ground surfaces give way to the map’s own imagery and basemap, and features another visible layer lists (byidField) are tinted as in the world.model.formatisgltf(the default:urlis one file loaded whole) or3d-tiles:urlis a 3D Tilestileset.jsonwhose glTF tiles follow the same conventions, in the model’s Z-up metric frame (x east, y north). Only the tiles around the viewer load at full detail and distant ground loads as coarser levels, so a model may cover a whole town. A tiled model brings its own ground.model.collisionnames its collision manifest (collision.json: acellSizegrid of glTF collision cells); the walker loads the cells around the visitor, follows their floors, and is stopped by their walls instead of using footprints andsurfaces. The map keeps drawingfeatureLayerIdas its own extrusions for a tiled model, since a town-wide model stands on terrain heights the flat map cannot seat. Both files are produced byscripts/assets/worlds/blender_cells.pyandscripts/assets/worlds/tileset.ts.
With relations, a feature on a relation’s parent layer is a place (a lot): the
selection outlines and tags its place, marks everything on it as related, and
choosing a place with a single feature on it selects that feature. A malformed
world is dropped and the project opens as a map only.
Dashboard widgets
{
"widgets": [
{
"id": "w1",
"layerId": "layer-a",
"type": "histogram",
"field": "pop",
"bins": 12
},
{
"id": "w2",
"layerId": "layer-a",
"type": "bar",
"category": "kind",
"aggregation": "sum",
"valueField": "pop",
"title": "Population by kind"
},
{
"id": "w3",
"layerId": "layer-a",
"type": "selector",
"category": "kind",
"multiple": true,
"selection": ["residential", "commercial"]
}
]
}
Each widget may set placement to dashboard, centerDock, leftDock,
rightDock, or headerDock. Omitting it keeps the widget in the standalone
Dashboard below the workspace (ShellSurfaces), just like dashboard.
centerDock belongs to the Dashboard tab of the central attribute workspace;
its widgets and opening state are independent from the standalone Dashboard.
The editor and Map Tools → Dashboard offer all five locations, and each dock
displays only its assigned widgets. The header dashboard uses the shared
HeaderDock host, including its panel switcher, collapse control, and adjustable height.
Widgets can move between visible dashboards by dragging or through the
widget’s Move to menu. Closed destinations have temporary drag targets, except
for centerDock, which accepts drops only when its dashboard is visible.
Each widget binds a chart to a layer’s attributes. type is one of histogram,
scatter, bar, line, box, pie, indicator, or selector. Which other keys apply depends on the
type: field (histogram/line/box), xField/yField (scatter), category +
aggregation + valueField (bar/pie), bins (histogram). Bar aggregation is
count/sum/mean; pie is count/sum only. title is an optional label and
color an optional hex (#rgb/#rrggbb) for the chart’s marks (the series
color for single-series charts; the base of a monochromatic ramp for bar/pie).
An indicator can use indicatorAggregation (count, sum, mean, min,
max, or median) and reads field for every aggregation except count, plus optional
prefix and suffix. A selector requires category; its optional multiple
flag permits more than one value, and its persisted selection is normalized to
unique non-empty strings (or one value when multiple is omitted or false).
Unused keys are ignored. The Dashboard panel (Tools → Dashboard, or the
Dashboard button in the attribute table) also stores dashboardColumns, the
widget-grid column count (1-6, default 2), at the top level of the project.
Charts read from GeoJSON-backed vector layers and DuckDB query layers; widgets
bound to a missing or non-attribute layer are shown as empty.
Layer object
{
"id": "uuid",
"name": "My Layer",
"type": "geojson",
"source": { "type": "geojson" },
"visible": true,
"opacity": 1,
"style": {
"minZoom": 0,
"maxZoom": 24,
"fillColor": "#3b82f6",
"strokeColor": "#1e40af",
"strokeWidth": 2,
"strokeWidthUnit": "pixels",
"strokeOpacity": 1,
"fillOpacity": 0.6,
"circleRadius": 6,
"rasterBrightnessMin": 0,
"rasterBrightnessMax": 1,
"rasterSaturation": 0,
"rasterContrast": 0,
"rasterHueRotate": 0
},
"metadata": {},
"geojson": { "type": "FeatureCollection", "features": [] },
"editorTracking": {
"enabled": true,
"createdByField": "created_by",
"createdAtField": "created_at",
"editedByField": "edited_by",
"editedAtField": "edited_at"
},
"sourcePath": "/path/to/file.geojson"
}
Vector styles may also store typed MapLibre expression strings in fillOpacityExpression, strokeColorExpression, strokeOpacityExpression, strokeWidthExpression, and circleRadiusExpression. Each expression overrides its corresponding literal value while it is valid. The project decoder validates these fields independently with Effect Schema, so one invalid hand-edited expression is dropped without discarding the rest of the layer style.
A custom point marker (markerShape: "custom") stores its source in markerSvg: inline SVG
markup, or a data: or http(s): URL of an SVG, PNG, JPEG, or GIF image. Any other URL scheme
is ignored. A graduated style whose breaks were edited by hand records
vectorStyleClassificationScheme: "manual"; the stored breaks are used as they are.
vectorStyleColorRampReversed: true runs vectorStyleColorRamp from its last color to its
first whenever classes are regenerated; it defaults to false. A categorized style with
vectorStyleAllCategories: true classifies every distinct value, so its class count follows
the field’s complete category count; it defaults to false. Labels
that format a numeric field store labels.numberFormatEnabled, labels.numberDecimals (a whole
number from 0 to 10; any other value restores the default), and labels.numberLocale (a BCP 47
tag, or empty to follow the app language).
Editor tracking
An in-memory GeoJSON layer may include editorTracking to maintain author and
timestamp fields automatically. enabled controls whether the policy is active;
the four optional field names default to created_by, created_at, edited_by,
and edited_at. Enabled configurations must use four distinct, non-empty names.
Malformed or colliding names are treated as tracking-off by read-only queries so
an imported project remains usable; write paths refuse to stamp until the policy
is corrected. Tracking columns are read-only in the Attribute Table and are not
valid targets for rename, delete, or field-calculator operations.
Layer operation capabilities
A layer may include a capabilities object with optional boolean query, create, update, delete, and export fields. Missing fields use the source-kind default: ordinary in-project GeoJSON is queryable and editable; derived queries, tiles, and raster layers are read-only; export remains enabled unless explicitly disabled. Invalid or empty objects are discarded while loading.
These flags are product policy enforced by the Layer panel, Attribute Table, GeoEditor, sharing/export preparation, and PostGIS write-back. export: false removes embedded GeoJSON snapshots from a shared/exported project but deliberately leaves a live source descriptor intact. The PostGIS sidecar’s echoed check is consistency protection for the project policy, not authorization; database grants must enforce access against untrusted callers.
Quick filters
quickFilters stores editable control state on a vector layer. It never stores
the compiled MapLibre expression. Each entry has a stable id, a field, a
kind (categorical, range, date, or text), and an optional enabled
flag. Its value fields depend on the kind:
{
"quickFilters": [
{
"id": "status",
"field": "status",
"kind": "categorical",
"values": ["open", "planned"]
},
{
"id": "height",
"field": "height_m",
"kind": "range",
"min": 10,
"max": 80
},
{
"id": "survey",
"field": "surveyed_at",
"kind": "date",
"dateKind": "iso",
"start": "2026-08-01",
"end": "2026-08-26"
},
{
"id": "name",
"field": "name",
"kind": "text",
"operator": "contains",
"text": "port"
}
]
}
Categorical values retain string, finite-number, and Boolean types. Range
bounds are inclusive and may be null. Date bounds are inclusive ISO calendar
days; dateKind is iso, epochMs, or epochS. Text operator is
contains, startsWith, or equals. Empty or disabled controls compile to no
constraint. At layer sync, active quick filters are combined with native, time,
and rule-based filters under a single conjunction.
Attribute-table export exclusions
For a vector layer, metadata.columnSettings.excluded may contain property
names that must be omitted from external data egress. This is distinct from
hidden, which controls only the local attribute-table view and never removes
data. Local project saves retain the full property set. Meratyn applies
excluded only to detached external copies for vector exports, standalone HTML,
Share uploads, Python to_project(), and other external egress; the in-memory
layer and its geometry, feature IDs, null properties, and unrelated metadata
remain unchanged.
{
"metadata": {
"columnSettings": {
"hidden": ["internal_note"],
"excluded": ["personal_email"]
}
}
}
Online layer references
Layers added through the built-in Catalog plugin keep their current
MapLibre source plus a stable Share reference in metadata:
{
"metadata": {
"hostedSourceKind": "mappview-hosted",
"hostedLayerId": "8ab66014-6c72-42b5-b2f5-c1aa7909c77b",
"hostedServiceId": "wms",
"hostedServiceType": "wms",
"hostedStableUrl": "https://share.example/api/layers/8ab66014-6c72-42b5-b2f5-c1aa7909c77b/gateway/wms",
"shareBaseUrl": "https://share.example"
}
}
The app may temporarily use a short-lived access URL or in-memory
authorization while a private layer is open. Project serialization removes
those runtime credentials and restores hostedStableUrl and
hostedStableSource. A consumer therefore resolves access with their own Share
session; a project file never grants access by carrying its author’s token.
The stable layer ID survives data replacement, metadata changes, style changes, and provider migrations. Do not persist an implementation server’s resource ID or internal service URL in a project.
For WFS GetFeature and GeoJSON URL layers, metadata.refresh can persist an
optional auto-refresh interval. intervalMs can be any positive interval in
milliseconds:
{
"metadata": {
"refresh": { "enabled": true, "intervalMs": 60000 }
}
}
Manual refresh uses the same saved source URL without requiring this metadata.
GeoJSON URL and ArcGIS feature-query layers load after the project opens: the
map and every other layer appear at once, and each service layer shows as
loading until its rows arrive. A layer can therefore omit geojson and ship
only its source URL. When it does embed rows, they display immediately and are
replaced by the service’s rows if its refresh is enabled; with refresh disabled
they are used as-is. A service that fails reports an issue on its layer and
never stops the project from opening.
For local-file vector layers on the desktop app, metadata.watch can persist a
“watch this file for changes” toggle. When enabled, the desktop app registers a
filesystem watcher that reloads the layer’s features from sourcePath whenever
the file changes on disk:
{
"metadata": {
"watch": { "enabled": true }
}
}
The key is omitted when watching is off, and it has no effect off the desktop host (the browser cannot watch a local filesystem path).
A layer may carry persistent attribute joins (Layer properties → Joins): live left joins that materialize columns from another layer’s attribute table — typically a geometry-less table added via Delimited Text with no coordinate fields — into this layer’s feature properties, matched on a key field:
{
"joins": [
{
"id": "uuid",
"joinLayerId": "other-layer-uuid",
"targetField": "name",
"joinField": "state_name",
"fields": ["pop_2025", "median_income"],
"prefix": "census_",
"enabled": true,
"addedFields": ["census_pop_2025", "census_median_income"],
"stats": {
"matchedCount": 50,
"unmatchedTargetCount": 2,
"unmatchedJoinCount": 2
}
}
]
}
fields (subset to bring over; omitted = every field except the key), prefix,
and enabled are optional. addedFields and stats are engine bookkeeping,
rewritten on every apply: addedFields lists the output columns the join added
(so re-applying can strip them first, keeping the operation idempotent), and
stats records the last match counts shown in the Joins UI. Joins re-resolve
against the loaded layer set on project open, so a saved copy of the joined
output self-heals if the join table changed.
When a geojson layer enables style.simpleStyleEnabled, individual features
may override the layer style with simplestyle-spec
properties (stroke, fill, stroke-width, fill-opacity, …). Meratyn also
honors a per-feature text-color on text-marker points (used by the Annotations
layer), falling back to style.textColor when a feature does not set it.
Layer types
| Type | v1.0 status |
|---|---|
geojson | Supported for imported files and GeoJSON URLs |
xyz | Supported for raster tile templates |
wms | Supported as tiled WMS GetMap layers |
raster | Supported for raster tile templates |
vector-tiles | Supported for MapLibre vector tile sources |
mbtiles | Supported in the desktop app through a local MapLibre protocol |
arcgis | Supported for ArcGIS FeatureServer and VectorTileServer layers; rendered MapServer/ImageServer services are persisted as raster layers with ArcGIS metadata |
pmtiles | Supported through the Components plugin |
cog | Supported for COG and GeoTIFF raster layers |
flatgeobuf | Supported through the Components plugin and imported as GeoJSON when loaded as a local vector file |
zarr | Supported through the Components plugin |
lidar | Supported through the Components plugin |
gaussian-splat | Supported through the Components plugin |
geoparquet | Imported as GeoJSON via DuckDB-WASM |
duckdb-query | Supported for SQL query-result layers |
3d-tiles | Supported through the maplibre-gl-3d-tiles plugin |
A geojson layer loaded page by page from an ArcGIS FeatureServer or MapServer
query stores source.arcgisQueryUrl, source.pageSize, and
source.maxFeatures, plus an optional source.arcgisWhere SQL filter. The
filter is applied to every page and to the feature count, so a layer such as
“fatal crashes only” stays filtered when it is refreshed or reopened.
API
import { createEmptyProject, parseProject, serializeProject } from "@mappview/core/project";
Feature presentation
Vector layers may carry an optional featurePresentation policy. Its clickEnabled and
hoverEnabled flags control authored interaction surfaces; titleField selects a title;
and ordered fields provide field, optional label, visible, and one of text,
number, date, link, or image formats. Omit the object to preserve legacy identify
presentation. Links and images are restricted to credential-free HTTP(S) URLs; internal,
object, and unsafe values are not rendered.
Blank background appearance
When basemapStyleUrl is the Blank basemap, blankBackgroundColor may contain a CSS hex color. Omit it or set it to null to use the active theme default; theme colors are not copied into a project.