# How Built-In Plugins Like maplibre‑3d‑tiles Integrate with the GeoLibre Store

> Discover how GeoLibre plugins like maplibre-3d-tiles integrate with the store using Zustand, accessing the GeoLibreAppAPI to sync state and dispatch mutations for seamless MapLibre and Deck.gl overlay control.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: internals
- Published: 2026-08-04

---

**Built-in plugins in GeoLibre integrate with the store through a Zustand-based `useAppStore`, receiving a `GeoLibreAppAPI` during activation that enables reading state, subscribing to changes, and dispatching layer mutations while MapLibre controls and Deck.gl overlays remain synchronized.**

GeoLibre's plugin architecture depends on a centralized **Zustand store** (`@geolibre/core`) that serves as the single source of truth for layers, UI state, and map view. The **maplibre‑3d‑tiles** plugin demonstrates how built-in plugins register, interact with this store, and maintain bidirectional synchronization between user controls and application state.

## How the Plugin Registration Flow Works

Plugins reside in `packages/plugins/src/plugins/` and activate during app initialization. The registration process creates the bridge between plugin code and the core store.

### Plugin Manager Initialization

In [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts), the desktop app instantiates a `PluginManager` and registers built-in plugins:

```ts
// apps/geolibre-desktop/src/hooks/usePlugins.ts
import { maplibre3dTilesPlugin } from "@geolibre/plugins";

const manager = new PluginManager();
manager.registerAll([
  maplibreLayerControlPlugin,
  maplibre3dTilesPlugin,   // ← built-in 3D-Tiles plugin
  // ...
]);

```

Each plugin receives `manager.activate(id, appAPI)`, passing a **`GeoLibreAppAPI`** object that exposes safe, high-level methods: `addMapControl`, `addTileLayer`, `registerExternalNativeLayer`, `getDeckGL`, and others.

### The Plugin Interface Contract

All plugins implement the `GeoLibrePlugin` interface defined in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts):

```ts
export interface GeoLibrePlugin {
  id: string;
  name: string;
  version: string;
  activate(app: GeoLibreAppAPI): void | Promise<void>;
  deactivate(app: GeoLibreAppAPI): void | Promise<void>;
}

```

The `activate` method is where plugins establish store subscriptions, create UI controls, and register event listeners.

## Store Integration Patterns in maplibre‑3d‑tiles

The 3D-Tiles plugin ([`packages/plugins/src/plugins/maplibre-3d-tiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-3d-tiles.ts)) implements three core interaction patterns with `useAppStore`.

### Reading Current State with getState()

Plugins access the store snapshot directly for initial reads:

```ts
import { useAppStore } from "@geolibre/core";

// Inside activation or event handlers
const currentLayers = useAppStore.getState().layers;
const mapView = useAppStore.getState().mapView;

```

This pattern enables plugins to hydrate their UI from existing state—for example, restoring 3D-Tiles layers when a project file loads.

### Subscribing to State Changes

The plugin registers a **Zustand subscription** to react to layer mutations from any source (other plugins, the layer panel, undo/redo, etc.):

```ts
threeDTilesStoreUnsubscribe ??= useAppStore.subscribe((state, previous) => {
  if (state.layers !== previous.layers) {
    updateGooglePhotorealisticTilesPanelList(control);
  }
  // Synchronize visibility, opacity, removals
  syncLayerVisibility(state.layers);
  syncLayerOpacity(state.layers);
});

```

The subscription callback receives both current and previous state, allowing fine-grained change detection. When layers change, the plugin updates its control UI and reconciles MapLibre/Deck.gl layer states.

### Writing Mutations Through Store Helpers

User interactions in the plugin's control panel dispatch actions to the store:

```ts
// Adding a new 3D-Tiles layer
useAppStore.getState().addLayer({
  id: crypto.randomUUID(),
  name: "Google Photorealistic 3D Tiles",
  type: "3d-tiles",
  source: { type: "3d-tiles", url, altitudeOffset: 0 },
  visible: true,
  opacity: 1,
  metadata: {
    sourceKind: "3d-tiles-url",
    externalNativeLayer: true,
    nativeLayerIds: [],
  },
});

// Updating visibility from checkbox toggle
useAppStore.getState().updateLayer(layerId, { visible: isChecked });

// Removing a layer
useAppStore.getState().removeLayer(layerId);

```

All mutations flow through the store, ensuring that **layer panel, style editor, time slider, and persistence layer** receive identical updates.

## MapLibre Control Integration

The plugin creates a `ThreeDTilesControl` from the `maplibre-gl-3d-tiles` library and mounts it through the app API:

```ts
const threeDTilesControl = new ThreeDTilesControl({ /* options */ });
app.addMapControl(threeDTilesControl, "top-left");

```

Control event listeners bridge UI actions to store mutations:

```ts
threeDTilesControl.on("statechange", (event) => {
  const { layerId, visible, opacity, url } = event.detail;
  
  if (visible !== undefined) {
    useAppStore.getState().updateLayer(layerId, { visible });
  }
  if (opacity !== undefined) {
    useAppStore.getState().updateLayer(layerId, { opacity });
  }
  // URL changes trigger layer replacement
});

```

On deactivation, the plugin cleans up:

```ts
app.removeMapControl(threeDTilesControl);
threeDTilesStoreUnsubscribe?.();

```

## Deck.gl Overlay for Google Photorealistic Tiles

Google Photorealistic 3D-Tiles require **Deck.gl** rendering instead of MapLibre's native 3D-Tiles support. The plugin obtains the shared Deck.gl instance:

```ts
const deck = await app.getDeckGL();

```

It then registers layers in the shared overlay system:

```ts
setSharedDeckLayers("google-3d-tiles", [
  new Tile3DLayer({
    id: "google-photorealistic",
    data: url,
    loadOptions: { fetch: { headers: { "X-GOOG-API-KEY": key } } },
    // ...
  }),
]);

```

The overlay is **ref-counted**—the plugin acquires a Mercator projection lock when active, forcing the map into a compatible coordinate system. When the plugin deactivates or layers are removed, it releases the lock:

```ts
releaseSharedDeckLayers("google-3d-tiles");

```

## Layer Persistence and Restoration

The plugin enables **project file round-trips** through store-schema conformance. Layer objects include `metadata.sourceKind` for identification:

```ts
const THREE_D_TILES_SOURCE_KIND = "3d-tiles-url";

// In layer definition
metadata: {
  sourceKind: THREE_D_TILES_SOURCE_KIND,
  externalNativeLayer: true,
  nativeLayerIds: [],  // Populated by registerExternalNativeLayer
}

```

On app start or project load, `restoreThreeDTilesLayers(app)` scans the store:

```ts
function restoreThreeDTilesLayers(app: GeoLibreAppAPI) {
  const layers = useAppStore
    .getState()
    .layers.filter(l => l.metadata?.sourceKind === THREE_D_TILES_SOURCE_KIND);
  
  for (const layer of layers) {
    recreateControlForLayer(layer);
    syncToMapLibreOrDeckGL(layer);
  }
}

```

Because the canonical data lives in the store, restoration requires no external state—the plugin rehydrates UI and rendering layers from `GeoLibreLayer` objects.

## Complete Layer Addition Example

Here's the full pattern for adding a 3D-Tiles layer from plugin code:

```ts
import { useAppStore } from "@geolibre/core";
import type { GeoLibreLayer, GeoLibreAppAPI } from "@geolibre/plugins";

const DEFAULT_LAYER_STYLE = {
  color: [255, 255, 255],
  opacity: 1.0,
};

function addThreeDTilesLayer(
  app: GeoLibreAppAPI,
  url: string,
  name: string
): string {
  const id = crypto.randomUUID();
  
  const layer: GeoLibreLayer = {
    id,
    name,
    type: "3d-tiles",
    source: {
      sourceId: id,
      type: "3d-tiles",
      url,
      altitudeOffset: 0,
    },
    visible: true,
    opacity: 1,
    style: { ...DEFAULT_LAYER_STYLE },
    metadata: {
      sourceKind: "3d-tiles-url",
      externalNativeLayer: true,
      nativeLayerIds: [],
    },
    sourcePath: url,
  };
  
  // Single source of truth update
  useAppStore.getState().addLayer(layer);
  
  // Register with app API for native layer tracking
  app.registerExternalNativeLayer(id, {
    // native layer configuration
  });
  
  return id;
}

```

## Key Design Principles

- **Store as source of truth**: No plugin maintains parallel layer state; all read from `useAppStore`
- **API abstraction**: Plugins never call MapLibre or Deck.gl directly—only through `GeoLibreAppAPI` methods
- **Bidirectional sync**: Store subscriptions update plugin UI; plugin events write store mutations
- **Schema conformance**: `metadata.sourceKind` enables generic restoration without plugin-specific serialization

## Key Files

| File | Role |
|------|------|
| [`packages/plugins/src/plugins/maplibre-3d-tiles.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-3d-tiles.ts) | Full 3D-Tiles plugin implementation with store subscriptions, control handling, Deck.gl integration |
| [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts) | Plugin registration and `GeoLibreAppAPI` instantiation |
| [`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) | Zustand store definition (`useAppStore`) |
| [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) | `GeoLibrePlugin` and `GeoLibreAppAPI` type definitions |
| [`apps/geolibre-desktop/src/components/panels/LayerPanel.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/LayerPanel.tsx) | Store consumer example—UI that reads layer state |

## Summary

- **maplibre‑3d‑tiles** integrates with the GeoLibre store through direct `useAppStore` imports and a subscription-based reactivity model
- The plugin receives a **`GeoLibreAppAPI`** during activation, providing safe abstractions over MapLibre and Deck.gl
- **Read pattern**: `useAppStore.getState()` for snapshot access
- **Listen pattern**: `useAppStore.subscribe()` for reactive UI updates
- **Write pattern**: Store helpers (`addLayer`, `updateLayer`, `removeLayer`) dispatched from control event handlers
- `metadata.sourceKind` enables **persistence and restoration** without custom serialization logic
- Separation of concerns keeps plugin code focused on UI translation while the store maintains canonical application state

## Frequently Asked Questions

### What is the GeoLibreAppAPI and why do plugins use it?

**`GeoLibreAppAPI`** is an abstraction layer that exposes controlled methods for plugins to interact with the map and UI. Instead of calling MapLibre or Deck.gl directly—which could cause state synchronization bugs—plugins use methods like `addMapControl`, `getDeckGL`, and `registerExternalNativeLayer`. This ensures all modifications flow through or are tracked by the central store, maintaining consistency across the application.

### How does the 3D-Tiles plugin handle Google Photorealistic Tiles differently?

Google Photorealistic 3D-Tiles are rendered **through Deck.gl** rather than MapLibre's native 3D-Tiles renderer. The plugin calls `app.getDeckGL()` to access a shared overlay instance, then uses `setSharedDeckLayers()` to register `Tile3DLayer` instances. The overlay system manages reference counting and projection locking, automatically switching the map to Mercator projection when these layers are active.

### Can external plugins access the store the same way as built-in plugins?

Yes. Any plugin—built-in or external—can `import { useAppStore } from "@geolibre/core"` and use the same `getState()`, `subscribe()`, and helper methods. The `GeoLibreAppAPI` interface is stabilized in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts), and the plugin registration in [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts) treats built-in and external plugins identically during activation.

### How does layer state survive when a project is saved and reopened?

Layer objects stored in `useAppStore` conform to the **`GeoLibreLayer`** schema, which includes `metadata.sourceKind` identifiers. When a project loads, the 3D-Tiles plugin's `restoreThreeDTilesLayers()` function filters layers by this kind and reconstructs the control UI and rendering layers from the stored properties. No plugin-specific serialization is required—the generic layer library handles persistence, and plugins rehydrate from canonical store data.