# How Built‑In Plugins Register with the GeoLibre PluginManager and What Lifecycle Hooks Are Available

> Discover how built-in plugins register with the GeoLibre PluginManager and explore available lifecycle hooks like onRegister, onActivate, and onDeactivate. Optimize your plugin development.

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

---

**Built‑in plugins in GeoLibre register automatically at application startup through a React hook that iterates over a static map of plugins and calls `pluginManager.registerPlugin()`, then respond to six lifecycle hooks defined in the `Plugin` interface: `onRegister`, `onActivate`, `onDeactivate`, `onUnload`, `onUpdate`, and render‑time hooks `onBeforeRender`/`onAfterRender`.**

The **GeoLibre** mapping platform implements a modular plugin architecture in its `@geolibre/plugins` workspace. This system allows both built‑in and external extensions to integrate cleanly with the MapLibre‑based renderer while maintaining predictable initialization and cleanup behavior.

## How Built‑In Plugins Register with PluginManager

Built‑in plugin registration follows a three‑stage pipeline that separates definition, aggregation, and runtime registration.

### Plugin Definition in Individual Modules

Each built‑in plugin is authored as a TypeScript module under `packages/plugins/src/plugins/`. Every module exports a default object conforming to the `Plugin` interface defined in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts). These modules contain the plugin's lifecycle implementations and metadata.

### Aggregation in the Built‑In Plugins Map

The central index file [`packages/plugins/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/index.ts) imports all individual plugins and constructs the `builtInPlugins` map. This static structure ensures the PluginManager receives a complete, type‑safe registry of available extensions.

```ts
// packages/plugins/src/index.ts
import { osmBasemap } from "./plugins/osm-basemap";
import { rasterLayerSync } from "./plugins/raster-layer-sync";
// …additional plugin imports

export const builtInPlugins = {
  osmBasemap,
  rasterLayerSync,
  // …all other built‑in plugins
} as const;

```

### PluginManager Singleton and Registration Methods

The `PluginManager` class in [`packages/plugins/src/plugin-manager.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugin-manager.ts) implements a singleton pattern. It maintains an internal `Map<string, Plugin>` registry and exposes methods for lifecycle management.

```ts
// packages/plugins/src/plugin-manager.ts
export class PluginManager {
  private plugins = new Map<string, Plugin>();

  registerPlugin(id: string, plugin: Plugin): void {
    this.plugins.set(id, plugin);
    // Triggers onRegister hook if implemented
  }

  unregisterPlugin(id: string): void {
    // Triggers onUnload hook before removal
    this.plugins.delete(id);
  }

  activatePlugin(id: string, context: PluginContext): void {
    // Triggers onActivate hook
  }

  deactivatePlugin(id: string, context: PluginContext): void {
    // Triggers onDeactivate hook
  }

  getPlugin(id: string): Plugin | undefined {
    return this.plugins.get(id);
  }
}

export const pluginManager = new PluginManager();

```

### React Hook‑Based Registration at Startup

The desktop application initiates registration through [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts). This hook executes once during component mount, iterating over `builtInPlugins` and invoking `registerPlugin()` for each entry.

```ts
// apps/geolibre-desktop/src/hooks/usePlugins.ts
import { useEffect } from "react";
import { builtInPlugins } from "@geolibre/plugins";
import { pluginManager } from "@geolibre/plugins/plugin-manager";

export function usePlugins() {
  useEffect(() => {
    Object.entries(builtInPlugins).forEach(([id, plugin]) => {
      pluginManager.registerPlugin(id, plugin);
    });
  }, []); // Empty dependency array ensures single execution
}

```

This pattern guarantees that:

- All built‑in plugins exist in the registry before any UI component attempts activation
- Registration occurs exactly once per application session
- External plugins added later follow the same interface contract

## Lifecycle Hooks Available to GeoLibre Plugins

The `Plugin` interface in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) specifies optional callback methods that the PluginManager invokes at defined execution points. All hooks receive a `PluginContext` object containing the Zustand store, MapLibre map instance, API helpers, and logging utilities.

### Core Lifecycle Hooks

| Hook | Invocation Timing | Typical Implementation |
|------|-------------------|------------------------|
| **`onRegister(context)`** | Immediately after `registerPlugin()` completes | Initialize internal state; attach global event listeners needed regardless of active status |
| **`onActivate(context)`** | When user enables plugin or auto‑activation occurs | Create MapLibre sources and layers; start background workers; allocate GPU resources |
| **`onDeactivate(context)`** | When user disables plugin | Remove map layers and sources; stop timers; detach UI components |
| **`onUnload(context)`** | Application shutdown or permanent plugin removal | Release all remaining resources; deregister global listeners; persist final state |
| **`onUpdate(prevConfig, newConfig, context)`** | Configuration change detected | Re‑apply settings to running map sources; restart processes with new parameters |

### Render‑Synchronization Hooks

| Hook | Invocation Timing | Typical Implementation |
|------|-------------------|------------------------|
| **`onBeforeRender(context)`** | Prior to each map render frame | Update camera‑dependent calculations; prepare deck.gl layers |
| **`onAfterRender(context)`** | Immediately following render completion | Synchronize overlay positions; capture frame statistics |

Plugins omitting any hook are handled gracefully—the PluginManager checks for method existence before invocation.

## Complete Plugin Implementation Example

The following demonstrates a raster layer plugin exercising multiple lifecycle stages:

```ts
// packages/plugins/src/plugins/example-raster.ts
import type { Plugin, PluginContext } from "../types";

export const exampleRaster: Plugin = {
  id: "example-raster",
  
  onRegister({ logger }: PluginContext) {
    logger.info("example‑raster: registered");
    // Initialize plugin‑specific state
  },
  
  onActivate({ map, logger }: PluginContext) {
    logger.info("example‑raster: activated");
    
    map.addSource("example-raster", {
      type: "raster",
      tiles: ["https://example.com/tiles/{z}/{x}/{y}.png"],
      tileSize: 256,
    });
    
    map.addLayer({
      id: "example-raster-layer",
      type: "raster",
      source: "example-raster",
      paint: { "raster-opacity": 0.8 },
    });
  },
  
  onDeactivate({ map, logger }: PluginContext) {
    logger.info("example‑raster: deactivated");
    
    if (map.getLayer("example-raster-layer")) {
      map.removeLayer("example-raster-layer");
    }
    if (map.getSource("example-raster")) {
      map.removeSource("example-raster");
    }
  },
  
  onUpdate(prevConfig, newConfig, { map }: PluginContext) {
    if (prevConfig.opacity !== newConfig.opacity) {
      map.setPaintProperty(
        "example-raster-layer",
        "raster-opacity",
        newConfig.opacity
      );
    }
  },
  
  onUnload({ logger }: PluginContext) {
    logger.info("example‑raster: unloaded");
  },
};

```

This plugin:

1. Logs registration via `onRegister`
2. Creates map resources on `onActivate`
3. Updates opacity reactively through `onUpdate`
4. Cleans up layers and sources in `onDeactivate`
5. Performs final logging in `onUnload`

## PluginContext Object Structure

All lifecycle hooks receive identical context:

- **`store`**: Zustand state management store for reactive data access
- **`map`**: Active MapLibre GL JS map instance
- **`api`**: GeoLibre‑specific utility methods for common operations
- **`logger`**: Structured logging interface with severity levels

## Summary

- **Registration path**: Built‑in plugins export from `packages/plugins/src/plugins/`, aggregate in [`packages/plugins/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/index.ts) as `builtInPlugins`, and register via `usePlugins` hook calling `pluginManager.registerPlugin()`
- **PluginManager location**: Singleton in [`packages/plugins/src/plugin-manager.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugin-manager.ts) with methods for registration, activation, deactivation, and retrieval
- **Six lifecycle hooks**: `onRegister`, `onActivate`, `onDeactivate`, `onUnload`, `onUpdate`, plus frame hooks `onBeforeRender`/`onAfterRender`
- **Hook parameter**: All receive `PluginContext` with store, map, API, and logger
- **Graceful degradation**: Unimplemented hooks are skipped; no runtime errors for missing methods

## Frequently Asked Questions

### How does GeoLibre distinguish between built‑in and external plugins?

Built‑in plugins are statically imported and bundled with the application through the `builtInPlugins` map, while external plugins are loaded dynamically at runtime. The PluginManager handles both identically after registration—no runtime distinction exists in the registry.

### Can a plugin activate itself automatically on registration?

The PluginManager does not auto‑activate plugins during registration. Activation requires an explicit `activatePlugin()` call, typically triggered by user interaction in the Plugin panel or by persistent state restoration at application startup.

### What happens if `onActivate` throws an error?

The PluginManager wraps hook invocations in try‑catch blocks. Errors are logged via the context logger, and the plugin state remains consistent—partial activation does not corrupt the registry or affect other plugins.

### Are the render hooks `onBeforeRender`/`onAfterRender` limited to visual plugins?

These hooks are available to all plugins but incur performance costs when registered. Non‑visual plugins should omit them to avoid unnecessary per‑frame overhead. The PluginManager only attaches render listeners for plugins implementing these methods.