# How to Create Custom MapLibre Style Expressions in GeoLibre: A Complete Guide

> Master MapLibre style expressions with GeoLibre. Build, validate, and compile custom expressions using validateMapExpression for type safety and live previews.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-01

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts#L48)). 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"]`.

```typescript
// 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`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts) ([source](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts#L96)) performs comprehensive validation before compilation:

1. **Tolerant JSON parsing** – removes stray trailing commas via `removeTrailingJsonCommas` ([source](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts#L40))
2. **Variable substitution** – replaces `@variable` tokens with actual values via `substituteExpressionVariables`
3. **Type-checked compilation** – invokes `createExpression` from `@maplibre/maplibre-gl-style-spec` with optional `expectedType` enforcement

The function returns a result object indicating success or detailed error information.

```typescript
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.

```typescript
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](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts#L80)) evaluates expressions against actual feature data and zoom levels without rendering to a map.

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) ([source](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts#L2648)), the compiled `StyleExpression` merges into the style JSON for the target layer property.

```typescript
// 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:

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/expressions.ts) | Core expression validation, variable substitution, and preview evaluation |
| [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) | Integration point for applying validated expressions to MapLibre layers |
| [`tests/expressions.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/expressions.test.ts) | Comprehensive test coverage for parsing edge cases and type enforcement |

## Summary

- **GeoLibre centralizes expression handling** in `@geolibre/core` with `validateMapExpression` and `evaluateMapExpression` as primary APIs
- **Type-safe validation** catches errors before runtime via `expectedType` parameter and MapLibre's official `createExpression`
- **Variable substitution** enables dynamic expressions with `@token` syntax resolved at validation time
- **Live preview** through `evaluateMapExpression` supports interactive development without map rendering overhead
- **Layer integration** in [`packages/map/src/layer-sync.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/layer-sync.ts) ensures 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.