How to Implement Virtual Fields in the GeoLibre Attribute Table: A Complete Developer Guide
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, each stage serves a distinct purpose:
- Definition storage — Virtual fields live in
LayerVirtualField[]arrays within layer configuration - Stripping — Previous virtual columns are removed to ensure clean re-computation
- Evaluation — MapLibre expressions compile and execute against each feature
- 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. Each field requires a unique ID, display name, MapLibre expression, and enabled flag.
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 definitionname— Column header displayed in attribute tablesexpression— Valid MapLibre expression stringenabled— 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—restores the original feature set by removing properties that match virtual field IDs.
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. This function:
- Compiles each enabled field's MapLibre expression
- Evaluates against every feature's properties
- Normalizes results (non-finite numbers →
null) - Adds computed values as
addedFieldproperties - Captures errors per-field without aborting
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, the pipeline explicitly applies joins before virtual fields, allowing expressions to reference joined columns.
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 serialization. The layer library in packages/core/src/layer-library.ts handles persistence, while 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
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
LayerVirtualFieldconfiguration and stored inpackages/core/src/types.ts - Idempotent application requires
stripVirtualFieldColumnsbeforeapplyLayerVirtualFieldsto prevent duplicate columns - Join integration in
packages/core/src/joins.tssequences 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; 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 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 project file through 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.
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 →