# How to Create Custom Symbology and Styling for Vector Layers in GeoLibre

> Learn to create custom symbology and styling for vector layers in GeoLibre. Customize appearance using the Style panel, programmatic updates, or Mapbox-GL JSON.

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

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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:

1. **Paint** – Controls fill color, line width, circle radius, etc.
2. **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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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:

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

```typescript
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`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/assistant/symbology.ts):

```typescript
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 `interpolate` expressions.
- **Categorical classes** – Assign distinct colors based on string attributes using `match` expressions.
- **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`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts).

All custom symbology is automatically persisted as part of a GeoLibre project ([`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json)), ensuring that styles survive reloads, exports, and sharing.

## Summary

- **GeoLibre** uses a Zustand store (`@geolibre/core`) where vector symbology lives in `layer.metadata.style`.
- The **Style panel** UI reads from and writes to this store via `setLayerStyle`, with `syncLayers` updating the MapLibre renderer.
- Use **`importMapboxStyle`** ([`packages/map/src/mapbox-style-import.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/mapbox-style-import.ts)) to apply external Mapbox-GL JSON files.
- Use **`copyLayerStyle`** and **`pasteLayerStyle`** ([`apps/geolibre-desktop/src/lib/assistant/symbology.ts`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/.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`](https://github.com/opengeos/GeoLibre/blob/main/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.