How to Create and Register a Custom Plugin in GeoLibre: A Complete Guide
To create and register a custom plugin in GeoLibre, implement the GeoLibrePlugin interface with required id, activate, and deactivate methods, then pass the plugin instance to PluginManager.register().
GeoLibre provides a store-driven, extensible architecture that allows developers to add functionality through a formal plugin system. The framework centers on two core components: the GeoLibrePlugin interface, which defines the plugin contract, and the PluginManager class, which handles registration, lifecycle management, and state persistence. This guide walks through the exact implementation patterns used in the opengeos/GeoLibre repository, referencing specific source files and method signatures.
Understanding the GeoLibre Plugin Architecture
At the heart of GeoLibre’s extensibility lies a strict interface-based design. The GeoLibrePlugin interface, defined in [packages/plugins/src/types.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts), mandates that every plugin expose a unique identifier and lifecycle hooks. The PluginManager class, implemented in [packages/plugins/src/plugin-manager.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugin-manager.ts), maintains an internal Map<string, GeoLibrePlugin> registry and orchestrates activation flows.
Plugins receive a scoped app API (GeoLibreAppAPI) during activation. This scoped API ensures that UI elements—such as map controls and toolbar menus—are correctly tagged with plugin ownership, preventing conflicts and enabling automatic cleanup when the plugin deactivates.
Step 1: Implement the GeoLibrePlugin Interface
To create a custom plugin in GeoLibre, you must define an object that conforms to the GeoLibrePlugin interface. This contract ensures the host application can consistently initialize, run, and tear down your extension.
Required Plugin Fields
Every plugin must provide three essential properties:
id: A unique string identifier used as the key in the manager’s registry.activate: A function receivingGeoLibreAppAPIthat runs when the plugin is turned on. It may returnbooleanorPromise<boolean>to indicate success or failure.deactivate: A function receivingGeoLibreAppAPIthat cleans up resources when the plugin is turned off.
Optional Lifecycle Callbacks
The interface also defines optional hooks for deeper integration:
activeByDefault: Boolean indicating whether the plugin should activate automatically when a project loads.handleUrlParameters: Callback for parsing query strings on app startup.getMapControlPosition/setMapControlPosition: Methods for managing map control layouts.applyProjectState: Hook for restoring plugin-specific state from saved projects.
Here is a complete implementation example:
// src/custom-plugins/myPlugin.ts
import type { GeoLibrePlugin, GeoLibreAppAPI } from '@geolibre/plugins';
export const myPlugin: GeoLibrePlugin = {
id: 'my-plugin',
activeByDefault: true,
activate: (app: GeoLibreAppAPI) => {
// Create a custom map control
const ctrl = {
onAdd: () => {
const el = document.createElement('div');
el.textContent = 'My Plugin 🎉';
el.style.cssText = 'padding:4px;background:#fff;border-radius:4px;';
return el;
},
onRemove: () => {},
};
// Register the control using the scoped API
app.addMapControl(ctrl, 'top-right');
return true; // Synchronous activation success
},
deactivate: (app: GeoLibreAppAPI) => {
// Clean up the control added during activation
app.removeMapControl?.('my-plugin-control');
},
registerToolbarMenu: (menu) => {
// Menu registration is automatically scoped to this plugin
// via the `scopeAppToPlugin` helper in plugin-manager.ts
},
};
export default myPlugin;
Step 2: Register Your Plugin with PluginManager
Once you have defined your plugin object, you must register it with the PluginManager instance. The manager exposes two primary methods for adding plugins to the registry:
register(plugin): Adds a singleGeoLibrePluginto the internalMap<string, GeoLibrePlugin>.registerAll(plugins): Accepts an array of plugins and registers each atomically.
In a typical GeoLibre application, the entry point creates a single PluginManager instance and stores it in the global state (often a Zustand store in @geolibre/core). You then call the registration methods before the app renders:
// src/main.ts
import { PluginManager } from '@geolibre/plugins';
import { myPlugin } from './custom-plugins/myPlugin';
// Instantiate the manager once per application lifecycle
const pluginManager = new PluginManager();
// Register a single custom plugin
pluginManager.register(myPlugin);
// Or register multiple plugins at once
// pluginManager.registerAll([myPlugin, analyticsPlugin, dataImportPlugin]);
// Expose the manager through your React context or state store
// so UI components can subscribe to plugin state changes
Step 3: Handle Activation and Deactivation
The PluginManager.activate(id, app) method triggers the plugin lifecycle. When called, the manager looks up the plugin by its id in the internal registry and invokes the activate method, passing the scoped GeoLibreAppAPI.
If activate returns a Promise, the manager monitors its resolution:
- If the promise resolves to
true, the plugin state updates to active. - If the promise resolves to
falseor rejects, the manager rolls back the activation and keeps the plugin inactive.
Deactivation follows a symmetric path through PluginManager.deactivate(id, app), which calls the plugin’s deactivate hook to release resources and remove UI components.
Integration Points and the Scoped App API
The GeoLibreAppAPI object passed to lifecycle hooks is the primary mechanism for extending GeoLibre’s UI and map functionality. This API is scoped to your plugin via the scopeAppToPlugin helper in plugin-manager.ts, ensuring proper ownership tracking.
Adding Map Controls
Use app.addMapControl(control, position) to inject custom DOM elements into the map canvas. The control object must implement onAdd() and onRemove() methods. Positions follow the MapLibre GL convention: 'top-left', 'top-right', 'bottom-left', or 'bottom-right'.
Registering Toolbar Menus
The optional registerToolbarMenu callback receives a menu configuration object following the GeoLibreToolbarMenu shape. Because the API is pre-scoped, the framework automatically tags these UI elements with your plugin’s id, allowing the manager to disable them if the plugin is deactivated.
Loading External Plugins (Drop-in Extensions)
GeoLibre supports external plugins without recompiling the core application. Place a zip file containing your bundled code and a plugin.json manifest in apps/geolibre-desktop/public/plugins/. The usePlugins hook (typically found in apps/geolibre-desktop/src/hooks/usePlugins.ts) discovers these manifests, dynamically imports the modules, and registers them with the same PluginManager used for built-in plugins.
Summary
- Implement
GeoLibrePlugin: Defineid,activate, anddeactivatein an object conforming to the interface inpackages/plugins/src/types.ts. - Register via
PluginManager: Callregister()orregisterAll()on the manager instance defined inpackages/plugins/src/plugin-manager.ts. - Leverage the scoped API: Use
GeoLibreAppAPIto add map controls and toolbar menus; the manager handles ownership and cleanup automatically. - Handle async activation: Return a boolean or Promise from
activate(); the manager rolls back onfalseor rejection. - Deploy externally: Package plugins as zips with
plugin.jsonmanifests for drop-in installation without code changes.
Frequently Asked Questions
What interface must a GeoLibre plugin implement?
Every custom plugin must implement the GeoLibrePlugin interface declared in packages/plugins/src/types.ts. This requires an id string, an activate method receiving GeoLibreAppAPI, and a deactivate method for cleanup. Optional fields like activeByDefault and handleUrlParameters enable deeper integration with the host application.
How does PluginManager handle asynchronous plugin activation?
When PluginManager.activate(id, app) invokes a plugin’s activate method, it checks the return type. If the method returns a Promise<boolean>, the manager awaits resolution. A resolved value of true commits the activation, while false or a rejected promise triggers a rollback, keeping the plugin inactive and preventing partial state corruption.
Can I load external plugins without modifying the core codebase?
Yes. GeoLibre supports drop-in plugins through the usePlugins hook. Bundle your plugin as a zip file with a plugin.json manifest and place it in apps/geolibre-desktop/public/plugins/. The hook dynamically imports your code and registers it with the existing PluginManager, requiring no changes to the core repository.
Where does GeoLibre store the active state of plugins?
The PluginManager maintains plugin state in an internal Map<string, GeoLibrePlugin> and exposes a subscribe method for reactive UI updates. For persistence across sessions, the manager serializes active plugin IDs and their states into the project file, restoring them via applyProjectState callbacks when the project reloads.
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 →