# How to Implement Virtual Fields in the GeoLibre Attribute Table: A Complete Developer Guide

> Learn how to implement virtual fields in the GeoLibre attribute table using MapLibre expressions. This guide details integration for attribute tables and data-driven styling.

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

---

**Virtual fields in GeoLibre are expression-backed columns computed on-the-fly using MapLibre expressions, persisted in layer configuration, and integrated throughout the application from attribute tables to data-driven styling.**

GeoLibre's **virtual fields** system provides a powerful way to derive computed columns without modifying underlying data sources. Similar to QGIS's "Create virtual field" feature, these fields evaluate MapLibre expressions for every feature and appear seamlessly across attribute tables, the Expression Builder, styling rules, labels, selection queries, and exports. This article walks through the complete implementation pipeline based on the opengeos/GeoLibre source code.

## Virtual Field Architecture Overview

The virtual field system follows a four-stage pipeline designed for reliability and idempotency. According to the GeoLibre core implementation in [`packages/core/src/virtual-fields.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/virtual-fields.ts), each stage serves a distinct purpose:

1. **Definition storage** — Virtual fields live in `LayerVirtualField[]` arrays within layer configuration
2. **Stripping** — Previous virtual columns are removed to ensure clean re-computation
3. **Evaluation** — MapLibre expressions compile and execute against each feature
4. **Integration** — Joins engine orchestrates the order of operations

This architecture ensures that virtual fields can reference joined columns and that repeated application produces consistent results.

## Defining Virtual Fields in Layer Configuration

Virtual fields are declared using the `LayerVirtualField` interface defined in [`packages/core/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/types.ts). Each field requires a unique ID, display name, MapLibre expression, and enabled flag.

```ts
const populationDensityField: LayerVirtualField = {
  id: "vf-pop-density-001",
  name: "pop_per_km2",
  expression: `["/", ["get", "population"], ["get", "area_km2"]]`,
  enabled: true,
};

// Add to layer configuration
layer.virtualFields = [
  ...(layer.virtualFields ?? []),
  populationDensityField
];

```

**Key properties:**

- `id` — Unique identifier for the field definition
- `name` — Column header displayed in attribute tables
- `expression` — Valid MapLibre expression string
- `enabled` — Toggle for temporary deactivation without deletion

## Stripping and Reapplying Virtual Fields

Before re-computing virtual fields, GeoLibre **always strips previously added columns** using `stripVirtualFieldColumns`. This function—implemented in [`packages/core/src/virtual-fields.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/virtual-fields.ts)—restores the original feature set by removing properties that match virtual field IDs.

```ts
import { stripVirtualFieldColumns } from "@geolibre/core/virtual-fields";

// Clean slate: remove all virtual field properties
const cleanFeatures = stripVirtualFieldColumns(
  featuresWithVirtuals,
  layer.virtualFields
);

```

This idempotent approach prevents duplicate columns and ensures that virtual field updates take full effect. The source code includes an explicit trade-off comment noting this behavior when layers receive replacement GeoJSON datasets.

## Evaluating Expressions with applyLayerVirtualFields

The core computation happens in `applyLayerVirtualFields`, also from [`packages/core/src/virtual-fields.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/virtual-fields.ts). This function:

1. Compiles each enabled field's MapLibre expression
2. Evaluates against every feature's properties
3. Normalizes results (non-finite numbers → `null`)
4. Adds computed values as `addedField` properties
5. Captures errors per-field without aborting

```ts
import { applyLayerVirtualFields } from "@geolibre/core/virtual-fields";

const { features: withVirtuals, fields: updatedFields } =
  applyLayerVirtualFields(originalFeatures, layer.virtualFields);

// updatedFields contains errorCount for each field
const fieldsWithErrors = updatedFields.filter(f => f.errorCount > 0);

```

**Error handling behavior:**

- Syntax errors surface immediately through expression compilation
- Runtime failures (undefined references, division by zero) increment `errorCount`
- Zero-feature layers still validate expression syntax
- Individual field failures do not block other virtual fields

## Integrating Virtual Fields with Table Joins

Virtual fields gain significant power from their integration with GeoLibre's joins system. In [`packages/core/src/joins.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/joins.ts), the pipeline explicitly applies joins **before** virtual fields, allowing expressions to reference joined columns.

```ts
import { applyLayerVirtualFields, stripVirtualFieldColumns } from "./virtual-fields";
import { applyLayerJoins } from "./joins";

export function applyJoinsAndVirtuals(layer: Layer): ProcessedLayer {
  // Step 1: Execute table joins
  const joinedResult = applyLayerJoins(layer);
  
  // Step 2: Strip any stale virtual columns
  const strippedFeatures = stripVirtualFieldColumns(
    joinedResult.features,
    layer.virtualFields
  );
  
  // Step 3: Compute virtual fields with access to joined data
  const { features, fields } = applyLayerVirtualFields(
    strippedFeatures,
    layer.virtualFields
  );
  
  return { ...joinedResult, features, virtualFields: fields };
}

```

This ordering enables sophisticated workflows: join demographic data from a CSV, then compute per-capita metrics as virtual fields without permanently altering either source.

## Handling Name Collisions and Edge Cases

The GeoLibre implementation enforces specific precedence rules for column names:

| Scenario | Behavior |
|----------|----------|
| Virtual field matches base column | Virtual field skipped — base data wins |
| Virtual field matches joined column | Virtual field skipped — joined data wins |
| Duplicate virtual field IDs | Last defined field takes precedence |
| Expression returns non-finite number | Stored as `null` |
| Expression references missing property | Evaluates to `null`, may increment `errorCount` |

These rules ensure predictable behavior when layer schemas evolve or joins introduce unexpected column names.

## Persisting Virtual Field Configuration

Virtual field definitions survive project saves through [`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json) serialization. The layer library in [`packages/core/src/layer-library.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/layer-library.ts) handles persistence, while [`apps/geolibre-desktop/src/components/panels/VirtualFieldsSection.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/VirtualFieldsSection.tsx) provides the user interface for creating, editing, and deleting fields.

On project load, the same evaluation pipeline runs automatically, re-deriving computed columns from stored expressions. This separation of **definition** (persisted) from **computed values** (ephemeral) keeps project files compact while maintaining full analytical capability.

## Complete Working Example

```ts
import { 
  applyLayerVirtualFields, 
  stripVirtualFieldColumns,
  LayerVirtualField
} from "@geolibre/core/virtual-fields";

// Configuration: define computed columns
const virtualFields: LayerVirtualField[] = [
  {
    id: "vf-revenue-per-customer",
    name: "revenue_per_customer",
    expression: `["/", ["get", "total_revenue"], ["get", "customer_count"]]`,
    enabled: true
  },
  {
    id: "vf-category-label",
    name: "display_category",
    expression: `["concat", ["get", "category"], ": ", ["get", "subcategory"]]`,
    enabled: true
  }
];

// Processing pipeline for updated source data
function refreshVirtualFields(
  newSourceFeatures: Feature[],
  existingVirtualFields: LayerVirtualField[]
) {
  // Always strip first for clean state
  const clean = stripVirtualFieldColumns(newSourceFeatures, existingVirtualFields);
  
  // Apply current virtual field definitions
  const { features, fields } = applyLayerVirtualFields(clean, existingVirtualFields);
  
  // Log any evaluation issues
  fields.forEach(f => {
    if (f.errorCount > 0) {
      console.warn(`Virtual field "${f.name}" has ${f.errorCount} errors`);
    }
  });
  
  return { features, virtualFieldStatus: fields };
}

```

## Summary

- **Virtual fields** are expression-backed columns defined in `LayerVirtualField` configuration and stored in [`packages/core/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/types.ts)
- **Idempotent application** requires `stripVirtualFieldColumns` before `applyLayerVirtualFields` to prevent duplicate columns
- **Join integration** in [`packages/core/src/joins.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/joins.ts) sequences operations so virtual expressions can reference joined data
- **MapLibre expressions** power all computations, with errors captured per-field without blocking other fields
- **Persistence** saves definitions to [`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json); values are recomputed on load

## Frequently Asked Questions

### What expression syntax do GeoLibre virtual fields use?

Virtual fields use **MapLibre expression syntax**—JSON arrays with operators like `["get", "propertyName"]`, `["/", ["get", "a"], ["get", "b"]]`, or `["concat", "prefix", ["get", "field"]]`. These compile and execute through the same engine that powers data-driven styling. Invalid syntax surfaces during compilation; runtime issues like missing properties evaluate to `null`.

### Can virtual fields reference columns from joined tables?

Yes. The joins engine in [`packages/core/src/joins.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/joins.ts) applies joins **before** virtual fields, making joined columns available in expressions. This enables workflows like joining external CSV data, then computing derived metrics without permanent schema changes. Name collisions with joined columns resolve in favor of the joined data.

### How does GeoLibre handle errors in virtual field expressions?

Errors are **isolated per field**. Syntax errors prevent compilation and surface immediately. Runtime errors—undefined properties, non-finite math results—produce `null` values and increment the field's `errorCount` property. Other virtual fields continue evaluating normally. Check `updatedFields` from `applyLayerVirtualFields` to monitor field health programmatically.

### Where do virtual field definitions persist in GeoLibre projects?

Definitions store in the [`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json) project file through [`packages/core/src/layer-library.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/layer-library.ts). Only the **configuration** (ID, name, expression, enabled flag) persists—computed values are ephemeral and re-derived on project load. This keeps files compact while preserving analytical logic.