How to Create Custom MapLibre Style Expressions in GeoLibre: A Complete Guide
Use validateMapExpression from @geolibre/core to build, validate, and compile custom MapLibre style expressions with full type safety and live preview capabilities.
Creating custom MapLibre style expressions in GeoLibre follows a structured workflow centered in the packages/core/src/expressions.ts module. This guide walks through the complete process from operator selection to live preview, with runnable code examples from the opengeos/GeoLibre source code.
Understanding the Expression Architecture
GeoLibre's expression system sits on top of MapLibre's native style specification. The packages/core/src/expressions.ts file provides the central implementation, exposing three main capabilities: expression building, validation with type checking, and runtime evaluation.
The system assumes MapLibre's array-based JSON syntax: ["operator", arg1, arg2, ...]. GeoLibre wraps this with developer-friendly utilities that catch errors early and enable interactive workflows.
Step 1: Choose a MapLibre Operator
GeoLibre organizes available operators into categories via EXPRESSION_FUNCTION_CATEGORIES (source). Each category entry provides a ready-to-paste snippet guaranteed to compile.
Common categories include:
- Decision operators –
==,!=,<,>,<=,>=,in,!in,match,case,coalesce - Math operators –
+,-,*,/,%,abs,round,floor,ceil,sqrt,log,ln,log10,sin,cos,tan,asin,acos,atan - String operators –
concat,downcase,upcase,is-supported-script,resolved-locale - Color operators –
rgb,rgba,to-color,interpolate - Data operators –
get,has,properties,geometry-type,id
Select an operator appropriate for your layer property—filters require boolean expressions, while paint properties accept colors, numbers, or strings depending on the specific property.
Step 2: Write the Expression as JSON
MapLibre expressions are JSON arrays. The first element is always the operator name (a string), followed by its arguments. Arguments can be literals, nested expressions, or data field references using ["get", "fieldname"].
// Simple comparison referencing a data property
["gt", ["get", "population"], 1000]
// Nested expression with arithmetic
["interpolate",
["linear"],
["zoom"],
0, ["*", ["get", "density"], 2],
10, ["*", ["get", "density"], 10]
]
GeoLibre accepts these expressions as strings or JSON objects, with built-in tolerance for formatting issues.
Step 3: Validate the Expression
The validateMapExpression function in packages/core/src/expressions.ts (source) performs comprehensive validation before compilation:
- Tolerant JSON parsing – removes stray trailing commas via
removeTrailingJsonCommas(source) - Variable substitution – replaces
@variabletokens with actual values viasubstituteExpressionVariables - Type-checked compilation – invokes
createExpressionfrom@maplibre/maplibre-gl-style-specwith optionalexpectedTypeenforcement
The function returns a result object indicating success or detailed error information.
import { validateMapExpression } from '@geolibre/core';
// Expression snippet from the "decision" category
const snippet = '["gt", ["get", "population"], 1000]';
// Validate with explicit expected return type
const result = validateMapExpression(snippet, { expectedType: 'boolean' });
if (result.ok) {
// result.expression is a compiled StyleExpression ready for MapLibre
console.log('Expression valid:', result.expression);
} else {
// result.errors contains specific validation failures
console.error('Validation failed:', result.errors);
}
Available expectedType values include: 'string', 'number', 'boolean', 'color', 'object', 'array', 'value', and 'image'. Specifying this parameter catches type mismatches at validation time rather than runtime.
Step 4: Apply Variables for Dynamic Expressions
GeoLibre supports @variable tokens for expressions that need runtime values. Common variables include @zoom for zoom-level dependent styling.
import { validateMapExpression, ExpressionVariable } from '@geolibre/core';
const expr = `
["interpolate",
["linear"],
["zoom"],
5, ["to-color", "#2563eb"],
12, ["to-color", "#dc2626"]
]
`;
const variables: ExpressionVariable[] = [
{ token: '@zoom', value: 8 },
{ token: '@feature-state', value: { selected: true } }
];
const { ok, expression, errors } = validateMapExpression(expr, {
variables,
expectedType: 'color'
});
if (ok) {
// expression is ready for layer paint property
console.log('Compiled color expression:', expression);
}
Variable substitution occurs before type checking, ensuring the resolved expression compiles correctly.
Step 5: Preview Expressions with Sample Data
For interactive UI development, evaluateMapExpression (source) evaluates expressions against actual feature data and zoom levels without rendering to a map.
import { evaluateMapExpression } from '@geolibre/core';
import type { Feature } from 'geojson';
const expression = '["concat", ["get", "name"], " - Park Area"]';
const sampleFeature: Feature = {
type: 'Feature',
geometry: { type: 'Point', coordinates: [-122.4194, 37.7749] },
properties: { name: 'Golden Gate', area_acres: 1017 }
};
const preview = evaluateMapExpression(expression, {
feature: sampleFeature,
zoom: 12
});
if (preview.kind === 'value') {
console.log('Result:', preview.value); // "Golden Gate - Park Area"
} else {
console.error('Evaluation error:', preview.errors);
}
The preview result distinguishes between successful evaluation ({ kind: 'value', value: ... }) and runtime errors ({ kind: 'error', errors: [...] }). This powers GeoLibre's Expression Builder dialog for live feedback.
Step 6: Integrate into Layer Definitions
Validated expressions propagate to MapLibre through the layer synchronization system. In packages/map/src/layer-sync.ts (source), the compiled StyleExpression merges into the style JSON for the target layer property.
// Typical integration pattern (simplified from layer-sync.ts)
const layerUpdate = {
id: 'parks-layer',
paint: {
'fill-color': validatedExpression.result // compiled StyleExpression
},
filter: filterExpression?.result // optional filter expression
};
The sync engine ensures expressions are only applied after validation succeeds, preventing broken map states.
Complete Working Example
Here's a full workflow combining operator selection, validation with variables, and preview evaluation:
import {
validateMapExpression,
evaluateMapExpression,
ExpressionVariable
} from '@geolibre/core';
import type { Feature } from 'geojson';
// 1. Define expression with dynamic variables
const radiusExpr = `
["interpolate",
["exponential", 1.5],
["zoom"],
5, ["*", ["get", "magnitude"], 2, @baseMultiplier],
15, ["*", ["get", "magnitude"], 20, @baseMultiplier]
]
`;
// 2. Configure runtime variables
const variables: ExpressionVariable[] = [
{ token: '@baseMultiplier', value: 1.5 },
{ token: '@zoom', value: 10 }
];
// 3. Validate with expected number output
const validation = validateMapExpression(radiusExpr, {
variables,
expectedType: 'number'
});
if (!validation.ok) {
console.error('Expression validation failed:', validation.errors);
process.exit(1);
}
// 4. Preview with sample earthquake data
const earthquake: Feature = {
type: 'Feature',
geometry: { type: 'Point', coordinates: [-157.837, 21.616] },
properties: { magnitude: 4.5, location: 'Kilauea' }
};
const preview = evaluateMapExpression(radiusExpr, {
feature: earthquake,
variables,
zoom: 10
});
console.log('Circle radius at zoom 10:', preview.value); // 13.5
Key Source Files Reference
| File | Purpose |
|---|---|
packages/core/src/expressions.ts |
Core expression validation, variable substitution, and preview evaluation |
packages/map/src/layer-sync.ts |
Integration point for applying validated expressions to MapLibre layers |
tests/expressions.test.ts |
Comprehensive test coverage for parsing edge cases and type enforcement |
Summary
- GeoLibre centralizes expression handling in
@geolibre/corewithvalidateMapExpressionandevaluateMapExpressionas primary APIs - Type-safe validation catches errors before runtime via
expectedTypeparameter and MapLibre's officialcreateExpression - Variable substitution enables dynamic expressions with
@tokensyntax resolved at validation time - Live preview through
evaluateMapExpressionsupports interactive development without map rendering overhead - Layer integration in
packages/map/src/layer-sync.tsensures validated expressions compile correctly in final MapLibre styles
Frequently Asked Questions
What format does MapLibre expect for style expressions?
MapLibre expressions are JSON arrays where the first element is the operator name as a string, followed by arguments. GeoLibre accepts these as strings or objects, with automatic cleanup of trailing commas and other JSON formatting issues via removeTrailingJsonCommas.
How do I debug a failing expression?
Call validateMapExpression with your expression string. On failure, the returned errors array contains specific messages about syntax problems, unknown operators, or type mismatches. For runtime behavior, use evaluateMapExpression with sample data to isolate logic errors from data issues.
Can I use GeoLibre expressions with standard MapLibre GL JS?
Yes. GeoLibre's validateMapExpression returns a compiled StyleExpression from @maplibre/maplibre-gl-style-spec, which is fully compatible with standard MapLibre. The validation layer adds type safety and developer experience without changing the underlying expression semantics.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →