How to Create Custom Symbology and Styling for Vector Layers in GeoLibre
GeoLibre stores vector symbology in a Zustand-managed style object that follows the MapLibre-GL spec, allowing you to customize appearance via the Style panel UI, programmatic store updates, or Mapbox-GL JSON imports.
In the open-source GeoLibre repository, every map layer’s visual properties live inside a central store defined in @geolibre/core. Whether you are adjusting colors interactively or applying complex data-driven expressions, understanding how to manipulate the layer’s style object is essential for creating professional cartography.
Understanding the Style Architecture
Vector symbology in GeoLibre is governed by a plain JSON document that adheres to the MapLibre-GL / Mapbox-GL style specification.
The Zustand Store and Layer Metadata
When you select a vector layer in the left-hand Layer list, GeoLibre reads layer.metadata.style from the global Zustand store. This object contains two primary keys:
paint– Rules for visual appearance (color, opacity, width).layout– Rules for visibility and positioning (icon size, text offset).
Any change made to this object triggers the setLayerStyle action located in packages/core/src/store.ts. Immediately after the store updates, the MapController invokes syncLayers to push the new symbology into the underlying MapLibre source and layer definitions.
Type Definitions
The structure of the style object is strictly typed. Reference packages/core/src/types.ts for the VectorLayer and LayerMetadata definitions to see exactly which paint and layout properties are available for different geometry types.
Using the Style Panel UI
The Style panel provides a no-code interface for editing vector symbology.
When you open the panel for a selected layer, it reads the current layer.metadata.style and presents two tabs:
- Paint – Controls fill color, line width, circle radius, etc.
- Layout – Controls visibility, text fields, and symbol placement.
Changes made in these tabs are written back to the store via setLayerStyle. The UI dynamically reflects the capabilities of the current style; for example, a Color Ramp selector appears only when the style contains a ramp property.
Importing External Styles
The Import Style button in the UI triggers the importMapboxStyle function (found in packages/map/src/mapbox-style-import.ts). This utility parses a provided Mapbox-GL JSON file, extracts only the paint and layout rules relevant to the selected vector layer, and writes them into layer.metadata.style.
See the implementation gate in apps/geolibre-desktop/src/components/panels/LayerPanel.tsx (lines 17–22) to understand when the import option is enabled for a given layer type.
Programmatic Style Management
For automation and advanced use cases, you can manipulate symbology directly through the store API or helper utilities.
Direct Store Updates
Advanced users can bypass the UI and call the store directly using the useAppStore hook:
import { useAppStore } from '@geolibre/core';
function setRedFill(layerId: string) {
const newStyle = {
type: 'fill',
paint: {
'fill-color': '#ff0000',
'fill-opacity': 0.5,
},
};
useAppStore.getState().setLayerStyle(layerId, newStyle);
}
This approach accepts any valid MapLibre-GL paint or layout property, including data-driven expressions for graduated symbols and categorical classes.
Importing Mapbox-GL Style JSON
To apply a complete external style programmatically:
import { importMapboxStyle } from '@geolibre/map';
import styleJson from './my-custom-style.json';
async function applyStyle(layerId: string) {
// Returns only the rules applicable to the target layer
const transformed = await importMapboxStyle(styleJson, layerId);
useAppStore.getState().setLayerStyle(layerId, transformed);
}
The importMapboxStyle function filters the input JSON so that unrelated layers are excluded before the style is persisted.
Copy-Paste Symbology Between Layers
GeoLibre includes a built-in clipboard system for replicating styles across layers. The logic resides in apps/geolibre-desktop/src/lib/assistant/symbology.ts:
import { copyLayerStyle, pasteLayerStyle } from '../lib/assistant/symbology';
function copyAndPaste(sourceId: string, targetId: string) {
copyLayerStyle(sourceId); // Clones style to temporary clipboard
pasteLayerStyle(targetId); // Merges clipboard into target metadata
}
This workflow is used by the Copy Style and Paste Style commands in the desktop application’s UI.
Data-Driven Styling and Advanced Techniques
Because the style object is a plain JSON document compatible with the MapLibre-GL spec, you can implement sophisticated cartography:
- Color ramps – Interpolate colors based on numeric attributes.
- Graduated symbols – Scale circle radius by data values using
interpolateexpressions. - Categorical classes – Assign distinct colors based on string attributes using
matchexpressions. - Conditional opacity – Set visibility rules based on zoom levels.
These expressions are stored directly in the paint object and are fully supported by the setLayerStyle action in packages/core/src/store.ts.
All custom symbology is automatically persisted as part of a GeoLibre project (.geolibre.json), ensuring that styles survive reloads, exports, and sharing.
Summary
- GeoLibre uses a Zustand store (
@geolibre/core) where vector symbology lives inlayer.metadata.style. - The Style panel UI reads from and writes to this store via
setLayerStyle, withsyncLayersupdating the MapLibre renderer. - Use
importMapboxStyle(packages/map/src/mapbox-style-import.ts) to apply external Mapbox-GL JSON files. - Use
copyLayerStyleandpasteLayerStyle(apps/geolibre-desktop/src/lib/assistant/symbology.ts) to clone symbology between layers. - Direct store access via
useAppStore.getState().setLayerStyle()supports full MapLibre-GL syntax, including data-driven expressions.
Frequently Asked Questions
How is custom symbology persisted in GeoLibre projects?
Custom symbology is stored inside the layer.metadata.style object, which is serialized into the project's .geolibre.json file. When you save a project, the Zustand store state is written to disk, preserving all paint and layout rules for every vector layer.
Can I use data-driven expressions for styling in GeoLibre?
Yes. Because the style object follows the MapLibre-GL specification, you can include any valid expression in the paint or layout properties. For example, you can set 'fill-color': ['interpolate', ['linear'], ['get', 'population'], 0, '#ffffcc', 100000, '#800026'] to create a choropleth map.
What is the difference between Paint and Layout properties in the Style panel?
Paint properties control how the layer is rendered visually (colors, opacity, widths), while Layout properties control how the layer is positioned and whether it is visible (text anchors, icon offsets, and visibility toggles). Both are stored in the same style object but are grouped separately in the UI for clarity.
Where can I find the type definitions for vector layer styles?
The TypeScript definitions are located in packages/core/src/types.ts. Look for the VectorLayer interface and its LayerMetadata property to see the exact structure of the style object, including supported paint and layout keys for different geometry types.
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 →