Types
TypeScript type reference for all Element Editor data structures.
Import
The data types live in @element-editor/core. @element-editor/react re-exports the most common ones (FeatureCollection, GeoJSONFeature, Schema, SchemaField, Group, Geometry, EditingMode). The adapters don't re-export them.
// With @element-editor/react:
import type { FeatureCollection, GeoJSONFeature, Schema } from '@element-editor/react'
// With an adapter, or for the other types on this page:
import type { FeatureCollection, GeoJSONFeature, Schema } from '@element-editor/core'Note:
@element-editor/coreis installed automatically with every Element package. To import from it, add it to your ownpackage.jsonat the same version as your other@element-editor/*packages; strict package managers (pnpm) don't resolve undeclared packages.
FeatureCollection
The root data structure returned by onChange, getFeatureCollection(), exportGeoJSON() (serialised), and exportAs() (download).
interface FeatureCollection {
type: 'FeatureCollection'
metadata: {
schemas: Schema[]
groups: Group[]
}
features: GeoJSONFeature[]
}metadata is an Element extension that travels with the data so schemas and groups survive a round-trip through a backend or file system. metadata is always present (never undefined) on objects produced by Element.
Note:
metadata.schemasandmetadata.groupsare the wire format. At runtime, schemas and groups live at the top level of the data store (inside the editor tree:useDataStore((s) => s.schemas)/useGeoStore().dataStore.getState().groupsfrom@element-editor/ui;useDataStoreis a selector hook, not a store, so it has nogetState()), not undermetadata. The collection is reassembled when you callgetFeatureCollection()/exportGeoJSON().
GeoJSONFeature
interface GeoJSONFeature {
type: 'Feature'
id: string // UUID; always present
geometry: Geometry
properties: Record<string, unknown>
schemaId?: string // RFC 7946 §6.1 — links feature to a Schema
groupId?: string // RFC 7946 §6.1 — links feature to a Group
hidden?: boolean // RFC 7946 §6.1 — visibility toggle
_drawType?: DrawType // RFC 7946 §6.1 — original drawing tool
bbox?: [number, number, number, number] // RFC 7946 §5 — [w, s, e, n]
}id is required and always a string UUID. New features get one via crypto.randomUUID().
Warning: Do not strip
_drawTypewhen round-tripping feature data through a backend. Element uses it to warn before an edit turns a circle (64-sided polygon approximation), rectangle or route (road-network alignment) into a plain polygon or line; the first committed point change removes it. It is a valid RFC 7946 §6.1 extension member — any compliant GeoJSON consumer must preserve unknown members.
Features inside the store can also carry _validation ({ errors, warnings }), the editor's own validation state. It is marked @internal (not part of the public data shape, may change), is stripped from the output of getFeatureCollection(), exportGeoJSON(), onChange and exportAs(), and should never be written. Use ref.validate() or validateFeature() instead.
FeatureCollectionInput / GeoJSONFeatureInput
What Element accepts: any RFC 7946 GeoJSON, so data typed with @types/geojson (GeoJSON.FeatureCollection, GeoJSON.Feature) type-checks as is under strict, with no cast. Used by initialData (which also takes a single feature), ref.setFeatureCollection(), ref.importGeoJSON() and ref.addFeature(), on both drop-ins and both adapters. What Element hands back (getFeatureCollection(), onChange, events) is always the strict FeatureCollection / GeoJSONFeature above. Exported by @element-editor/core and both @element-editor/react entries.
interface FeatureCollectionInput {
type: 'FeatureCollection'
metadata?: { schemas?: Schema[]; groups?: Group[] } // missing: the default schema, no groups
features: GeoJSONFeatureInput[]
bbox?: BBoxInput // ignored
}
interface GeoJSONFeatureInput {
type: 'Feature'
id?: string | number // missing or '': a UUID; a number becomes a string ('0' included)
geometry: GeometryInput | null // null: left out on import, reported (see below)
properties?: Record<string, unknown> | null // missing or null: {}
bbox?: BBoxInput // recomputed by Element
schemaId?: string
groupId?: string
hidden?: boolean
_drawType?: DrawType
}
type PositionInput = number[] // [lng, lat] or [lng, lat, altitude, …]
type BBoxInput = [number, number, number, number] | [number, number, number, number, number, number]
type GeometryInput = // every RFC 7946 geometry; each may carry a bbox
| { type: 'Point'; coordinates: PositionInput }
| { type: 'MultiPoint' | 'LineString'; coordinates: PositionInput[] }
| { type: 'MultiLineString' | 'Polygon'; coordinates: PositionInput[][] }
| { type: 'MultiPolygon'; coordinates: PositionInput[][][] }
| { type: 'GeometryCollection'; geometries: GeometryInput[] }import type { FeatureCollectionInput } from '@element-editor/react'
const data: FeatureCollectionInput = {
type: 'FeatureCollection',
features: [{ type: 'Feature', id: 1, geometry: { type: 'Point', coordinates: [14.42, 50.09] }, properties: null }],
}import type * as GeoJSON from 'geojson'
const fc: GeoJSON.FeatureCollection = await (await fetch('/parcels.geojson')).json()
<ElementEditor initialData={fc} /> // no cast needed
ref.current?.setFeatureCollection(fc)hidden and _drawType are kept on import, so data exported by Element round-trips unchanged. Altitudes in positions are kept as given (a vertex the user moves becomes [lng, lat]). A feature with geometry: null (an "unlocated" feature, valid GeoJSON) can't be shown on a map: initialData and setFeatureCollection leave it out and report it once through onError / the error event (code: 'invalid-data', source: 'import'), importGeoJSON counts it in ImportResult.skipped, and addFeature returns null. PositionInput, BBoxInput and GeometryInput are exported by @element-editor/core (GeometryInput also by @element-editor/react).
DrawType
type DrawType = 'circle' | 'rectangle' | 'route'Set on GeoJSONFeature._drawType when the feature was drawn with a tool that produces a geometry type GeoJSON cannot natively express:
| Value | Geometry type produced | Significance |
|---|---|---|
'circle' | Polygon (64-sided approximation) | Editing preserved as circle; vertex edit converts to plain polygon. |
'rectangle' | Polygon (4 corners) | Kept by the transform box; vertex edit converts to plain polygon. |
'route' | LineString | Path follows road network. Vertex/transform edits may misalign from roads. |
Schema
A named set of fields for feature properties. A feature follows the schema its schemaId names (none: the first schema). Several schemas can coexist; they travel with the data as metadata.schemas. Removing a field never deletes values already stored.
interface Schema {
id: string // features reference it in `schemaId`
name: string // shown in the schema editor and the feature panel
fields: SchemaField[] // in display order
}import type { Schema } from '@element-editor/react'
const parcel: Schema = {
id: 'parcel',
name: 'Parcel',
fields: [
{ key: 'owner', label: 'Owner', type: 'text', required: true },
{ key: 'area_ha', label: 'Area (ha)', type: 'number', required: false, min: 0 },
],
}Validation (ref.validate(), ref.submit()): a required field with a missing, null or blank value, a number outside min / max, and a zero-area polygon are errors. Errors never block editing.
SchemaField
interface SchemaField {
key: string // auto-generated slug from label; used as the properties key
label: string // display name shown in forms
type: FieldType
required: boolean
options?: string[] // only for type 'select'
min?: number // only for type 'number'
max?: number // only for type 'number'
}key is the property name written to feature.properties[key]. label is what the user sees in forms and column headers. They differ because labels are free text and keys must be JSON-safe.
FieldType
type FieldType = 'text' | 'textarea' | 'number' | 'url' | 'select' | 'date' | 'boolean'| Value | Renders as | Additional options |
|---|---|---|
'text' | Single-line input | — |
'textarea' | Multi-line input | — |
'number' | Number input | min, max |
'url' | URL input | — |
'select' | Dropdown | options: string[] |
'date' | Date picker | — |
'boolean' | Checkbox | — |
Group
interface Group {
id: string
name: string
collapsed: boolean
hidden?: boolean
}Groups are containers for features in the layers panel. A feature belongs to at most one group (GeoJSONFeature.groupId). Hiding a group hides all its member features.
Groups travel with the data on FeatureCollection.metadata.groups. Removing a group (via dataStore.deleteGroup(id, mode)) can either ungroup or delete the member features depending on mode.
Geometry types
All geometry types use GeoJSON coordinate order: [longitude, latitude].
PointGeometry
interface PointGeometry {
type: 'Point'
coordinates: [number, number] // [longitude, latitude]
}LineStringGeometry
interface LineStringGeometry {
type: 'LineString'
coordinates: [number, number][] // [[lng, lat], ...]
}PolygonGeometry
interface PolygonGeometry {
type: 'Polygon'
coordinates: [number, number][][] // [outer ring, ...optional inner rings (holes)]
}The first ring is the exterior boundary. Additional rings are holes.
MultiPointGeometry
interface MultiPointGeometry {
type: 'MultiPoint'
coordinates: [number, number][]
}MultiLineStringGeometry
interface MultiLineStringGeometry {
type: 'MultiLineString'
coordinates: [number, number][][]
}MultiPolygonGeometry
interface MultiPolygonGeometry {
type: 'MultiPolygon'
coordinates: [number, number][][][]
}GeometryCollectionGeometry
interface GeometryCollectionGeometry {
type: 'GeometryCollection'
geometries: SimpleGeometry[]
}SimpleGeometry
Union of all geometry types except GeometryCollection:
type SimpleGeometry =
| PointGeometry
| MultiPointGeometry
| LineStringGeometry
| MultiLineStringGeometry
| PolygonGeometry
| MultiPolygonGeometryGeometry
type Geometry = SimpleGeometry | GeometryCollectionGeometryCoordinateGeometry
type CoordinateGeometry = SimpleGeometryAlias for SimpleGeometry. Use when a function only handles geometries with a coordinates property (i.e., not GeometryCollection).
isSimpleGeometry(g)
import { isSimpleGeometry } from '@element-editor/core'
if (isSimpleGeometry(feature.geometry)) {
feature.geometry.coordinates // narrowed to SimpleGeometry
}isSimpleGeometry is a type guard: it's false only for a GeometryCollection.
EditingMode
The FSM states for the drawing and editing interaction machine:
type EditingMode =
| 'none'
| 'place-point'
| 'draw-line'
| 'draw-polygon'
| 'draw-circle'
| 'draw-rect'
| 'draw-route'
| 'draw-freehand'
| 'picker'
| 'measure'
| 'cut-hole'
| 'slice-shape'
| 'simplify-shape'
| 'smooth-shape'
| 'buffer-shape'
| 'measure'| Value | Active when |
|---|---|
'none' | Selection mode; no active drawing. |
'place-point' | Point tool active. |
'draw-line' | Line tool active. |
'draw-polygon' | Area tool active. |
'draw-circle' | Circle tool active. |
'draw-rect' | Rectangle tool active. |
'draw-route' | Route tool active. |
'draw-freehand' | Freehand tool active (click-and-drag). |
'pick-shape' | Picker tool active (Eyedropper-style shape pick). |
'cut-hole' | Cut hole tool active. |
'slice-shape' | Slice shape tool active. |
'simplify-shape' | Simplify slider preview is open. |
'smooth-shape' | Smooth slider preview is open. |
'buffer-shape' | Buffer slider preview is open. |
'measure' | Measure tool active. |
NetworkConfig
The shape of config.network (@element-editor/core): the servers one editor talks to (vectorTiles, dem, osmRaster, glyphs, sprite, overpass, routing, nominatim, fetch). NetworkSource is a tile URL (template with {z}, else TileJSON) or MapLibre source fields merged over the built-in source; RoutingConfig is { provider?: 'ors' | 'osrm', url?, apiKey?, attribution? }; NominatimConfig is { url?, email?, attribution? }. Every field is described in Third-party services and self-hosting.
DataStore
The interface for the Zustand data store, accessible via useDataStore and useGeoStore. The store state types (DataStore, UIStore, UndoEntry, FeaturePatch, StoreFeaturePatch, DataStoreOptions, UIStoreOptions, DialogState, CompletedMeasurement, PanelPrefs) are exported from @element-editor/core and covered by semver; @element-editor/ui re-exports DataStore and UIStore next to the store hooks.
interface DataStore {
// State
features: GeoJSONFeature[]
schemas: Schema[]
groups: Group[]
selectedIds: string[]
selectedGroupId: string | null // the explicitly selected group (Cmd+G); cleared by every feature-selection action
lastUsedSchemaId: string | null
lastActiveGroupId: string | null
undoStack: UndoEntry[] // UndoEntry = { features: GeoJSONFeature[]; groups?: Group[]; schemas?: Schema[]; extras?: unknown }
redoStack: UndoEntry[]
lastModifiedAt: number | null
clipboard: GeoJSONFeature[]
maxFeatures?: number // undefined = unlimited
currentZoom: number
geometryMode: 'visual' | 'spatial'
// Feature actions
// Refused by `maxFeatures` (the error is reported via onError): addFeature returns null, the other adding actions false
addFeature: (
feature: GeoJSONFeature,
options?: { insertAfterId?: string; skipSnapshot?: boolean; programmatic?: boolean; undoable?: boolean },
) => GeoJSONFeature | null // the stored feature
updateFeature: (
id: string,
updates: Partial<Pick<GeoJSONFeature, 'properties' | 'geometry' | 'schemaId' | 'hidden'>> & { _drawType?: undefined }, // _drawType: undefined clears it
options?: { skipValidation?: boolean },
) => void
/** updateFeature for many features: one array copy, one notification, at most one undo step. */
updateFeatures: (
updates: ReadonlyArray<{ id: string; patch: FeaturePatch }>, // FeaturePatch = updateFeature's `updates`
options?: { skipValidation?: boolean; undoable?: boolean },
) => void
updateFeatureValidation: (id: string, warnings: string[], errors: string[]) => void
deleteFeature: (id: string) => void
/** Removes many features in one step; returns how many were removed. `undoable: false` also drops them from undo/redo. */
deleteFeatures: (ids: Iterable<string>, options?: { undoable?: boolean }) => number
/** Removes `removeIds` and adds `added` (at the top) in one commit and ONE undo step, then selects the added features (boolean ops). */
replaceFeatures: (removeIds: Iterable<string>, added: GeoJSONFeature[]) => boolean
duplicateFeature: (id: string) => boolean
duplicateFeaturesAtPosition: (entries: Array<{ sourceId: string; geometry: GeoJSONFeature['geometry'] }>) => boolean
duplicateSelection: () => boolean
copySelection: () => Promise<boolean> // true when the system clipboard was written too
pasteSelection: () => boolean // all-or-nothing under maxFeatures
takeSnapshot: () => void
/** Lets the host keep its own state in each undo entry (`extras`): `capture` runs on every snapshot, `restore` on undo/redo. */
setUndoExtrasHandlers: (capture: () => unknown, restore: (extras: unknown) => void) => void
// Selection
selectFeature: (id: string | null) => void
toggleSelection: (id: string) => void
setSelection: (ids: string[]) => void
clearSelection: () => void
selectGroup: (id: string | null) => void // marks the selected group; doesn't touch selectedIds
deleteSelection: () => void
// Multi-geometry
breakMultiGeometry: (id: string) => boolean
mergeFeatures: () => void
// History
undo: () => void
redo: () => void
// Groups
addGroup: (name: string) => void
updateGroup: (id: string, updates: Partial<Pick<Group, 'name' | 'collapsed' | 'hidden'>>) => void
deleteGroup: (id: string, mode: 'ungroup' | 'delete-features') => void
toggleGroupCollapsed: (id: string) => void
toggleGroupHidden: (id: string) => void
toggleFeatureHidden: (id: string) => void
moveFeatureToGroup: (featureId: string, groupId: string | null) => void
reorderFeatures: (orderedIds: string[]) => void
reorderGroups: (orderedIds: string[]) => void
groupSelection: (name?: string) => string | null // new group from the selection; null when it was empty
ungroupSelection: () => void
// Schemas
getDefaultSchema: () => Schema
getSchemaForFeature: (featureId: string) => Schema
getVisibleSchemas: () => Schema[]
setLastUsedSchemaId: (id: string) => void
addSchema: (name: string, fields?: SchemaField[]) => string // returns the new id
updateSchema: (id: string, updates: { name: string }) => void
deleteSchema: (id: string) => void // no-op on the last schema
addSchemaField: (schemaId: string, field: SchemaField) => void
updateSchemaField: (schemaId: string, key: string, updates: Partial<SchemaField>) => void
removeSchemaField: (schemaId: string, key: string) => void
// Import / export
exportGeoJSON: () => string
importGeoJSON: (
raw: unknown,
mode: 'replace' | 'merge',
limits?: { maxFeatures?: number; maxInputSize?: number; undoable?: boolean },
) => { skipped: number; duplicates: number; truncated: boolean } // limits.maxFeatures defaults to the store's maxFeatures; undoable: false (replace) adds no undo step and clears the history
updateFeaturesFromEditor: (raw: unknown) => void // applies the GeoJSON inspector's edited text
// Feature limit
setMaxFeatures: (max?: number) => void
canAddFeatures: (count: number) => boolean // false = over the limit; also reports the error
// Geometry mode
setGeometryMode: (mode: 'visual' | 'spatial') => void
setCurrentZoom: (z: number) => void
}The undo stack holds up to 25 snapshots of features, groups and schemas, so schema edits (add, rename, delete schema; add, edit, remove field) undo too. Repeated edits of the same field property within a second (typing a Min value) share one step. updateFeature pushes one step when properties or schemaId actually change (no-op edits push nothing; same-feature, same-keys edits within a second, such as typing a map label, share one step). It never pushes for hidden (view state) or for updates that include geometry: geometry callers take their own snapshot (takeSnapshot() or at drag start), so a geometry + properties update is one step. updateFeatures applies the same rule to a whole batch as one step: it pushes once when any patch changes properties or schemaId without geometry. Undo and redo clear selectedIds.
UIStore
The interface for the Zustand UI store (useUIStore, useGeoStore().uiStore): the active tool and drawing state, panel and modal visibility, selection-related view state (vertex and part selection, hover), the readonly / tool / layout settings the editor props sync into it, units and measurement results. Import the type from @element-editor/core (or @element-editor/ui) to type your selectors:
import type { UIStore } from '@element-editor/core'
const selectTableOpen = (s: UIStore) => s.tableOpenPrefer the editor props and the ref API for changing the editor; write to the UI store only for view state your own UI owns (for example toggling a panel you render).
Shortcut types
ShortcutId (customizable)
type ShortcutId =
| 'place-point'
| 'draw-line'
| 'draw-polygon'
| 'draw-route'
| 'draw-circle'
| 'draw-rect'
| 'draw-freehand'
| 'picker'
| 'measure'
| 'cut-hole'
| 'slice-shape'
| 'break-apart'
| 'toggle-table'
| 'toggle-transform'
| 'show-shortcuts'
| 'toggle-geojson'
| 'toggle-schema'
| 'toggle-sidebar'
| 'set-zoom'Default key bindings:
ShortcutId | Default key | Label |
|---|---|---|
'place-point' | p | Place point |
'draw-line' | l | Draw line |
'draw-polygon' | g | Draw area |
'draw-route' | c | Draw route |
'draw-circle' | o | Draw circle |
'draw-rect' | r | Draw rectangle |
'draw-freehand' | f | Draw freehand |
'picker' | x | Picker |
'measure' | m | Measure |
'cut-hole' | h | Cut hole |
'slice-shape' | j | Slice shape |
'break-apart' | b | Break apart |
'toggle-sidebar' | / | Toggle layers panel |
'toggle-table' | t | Toggle table |
'toggle-geojson' | i | Toggle GeoJSON |
'toggle-transform' | a | Toggle transformation |
'toggle-schema' | s | Toggle schema editor |
'show-shortcuts' | k | Toggle key shortcuts |
'set-zoom' | z | Set zoom level |
Custom bindings are persisted to localStorage under the key elm_shortcuts.
FixedShortcutId (not customizable)
type FixedShortcutId =
| 'undo'
| 'redo'
| 'select-all'
| 'delete-selected'
| 'search-places'
| 'finish-drawing'
| 'cancel'
| 'group-selection'
| 'ungroup-selection'
| 'edit-selected'
| 'toggle-sticky-tool'
| 'import-geojson'
| 'export-geojson'FixedShortcutId | Display | Aliases |
|---|---|---|
'undo' | ⌘ Z | — |
'redo' | ⌘ ⇧ Z | — |
'select-all' | ⌘ A | — |
'delete-selected' | Del | ⌫ |
'search-places' | ⌘ F | — |
'finish-drawing' | ↵ | — |
'cancel' | Esc | — |
'group-selection' | ⌘ G | — |
'ungroup-selection' | ⌘ ⇧ G | — |
'edit-selected' | ⌘ E | — |
'toggle-sticky-tool' | ⌘ J | — |
'import-geojson' | ⌘ O | — |
'export-geojson' | ⌘ S | — |
Helpers getFixedShortcutDisplay(id) and getFixedShortcutFullDisplay(id) are exported from @element-editor/core for rendering.
Accent palette
From @element-editor/core. See Theming for usage.
type AccentShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950
type AccentPalette = Record<AccentShade, string> // hex colours, e.g. '#3b82f6'
const DEFAULT_ACCENT = '#3b82f6'
function accentPalette(accent?: string): AccentPalette // any CSS colour; unrecognised → default blue
function readAccentPalette(el: Element | null | undefined): AccentPalette // inherited --elm-accent of el
function resolveColor(input: string): [number, number, number] | null // any CSS colour → RGBACCENT_STEPS holds the per-shade factors (white-mix share for 50–500, lightness and chroma multipliers for 600–950). parseColor (hex and rgb() only) and mixOklab are exported as lower-level helpers.