How GeoLibre Plugins Work and How They Are Registered: A Deep Dive into the Architecture
GeoLibre plugins work through a dynamic module discovery system in the packages/plugins workspace, where each plugin exports a PluginDefinition object that the Plugin Manager automatically aggregates and registers into specialized UI registries for toolbars, side panels, and floating windows.
GeoLibre, an open-source geospatial platform maintained by the opengeos/GeoLibre repository, provides a modular plugin architecture that allows developers to extend functionality without modifying core application code. Understanding how GeoLibre plugins are discovered and registered is essential for customizing the desktop application or integrating new geospatial capabilities. The system leverages TypeScript interfaces, wildcard module imports, and a centralized state management pattern to seamlessly inject new features into the user interface.
The PluginDefinition Interface
Every GeoLibre plugin must implement the PluginDefinition interface located in packages/plugins/src/types.ts. This contract mandates specific metadata fields and optional factory functions that determine how the plugin integrates with the application.
The interface requires:
id: A unique string identifier for the plugintitle: The display name shown in UI menusicon: An emoji or icon string for visual identificationToolbarComponent: Optional React component for toolbar integrationRightPanelComponent: Optional React component for side panel renderingcreateLayer: Optional function to instantiate map layers
How the Plugin Manager Discovers Plugins
The Plugin Manager (packages/plugins/src/plugin-manager.ts) orchestrates the automatic discovery process. Rather than requiring manual imports, the manager uses a wildcard import statement (import * as plugins from './plugins') to dynamically load every module placed in the packages/plugins/src/plugins/ directory.
During initialization, the manager iterates through these modules and constructs a pluginsById map, keyed by each plugin's unique identifier. This aggregation happens automatically at build time, meaning any new file added to the plugins directory is immediately eligible for registration without updating import statements.
The Three Registry Types
Once aggregated, the Plugin Manager distributes plugin definitions to three specialized registries that handle different UI contexts:
Toolbar Menu Registry (packages/plugins/src/toolbar-menu-registry.ts): Manages plugins that add buttons or controls to the top toolbar. It exposes the useToolbarMenu hook for React components to access registered toolbar items.
Right-Panel Registry (packages/plugins/src/right-panel-registry.ts): Handles plugins appearing in the right-hand side panel, such as layer controls or time-slider settings. This registry provides the useRightPanel hook for component rendering.
Floating-Panel Registry (packages/plugins/src/floating-panel-registry.ts): Registers plugins that create floating dialog windows, including "Add Data" or processing tool dialogs. Access these through the useFloatingPanel hook.
Each registry filters the master plugin map based on which UI components the plugin defines, ensuring efficient rendering of only relevant interface elements.
UI Integration and State Management
The desktop application initializes the entire plugin system through the usePlugins.ts hook located at apps/geolibre-desktop/src/hooks/usePlugins.ts. This hook executes initializePlugins() once during application startup, populating a Zustand store with the collections of registered plugins.
UI components throughout the application subscribe to this store using the registry-specific hooks. When a user interacts with the toolbar or opens a panel, the components query the appropriate registry to retrieve the React components defined in each plugin's PluginDefinition.
Creating a Custom GeoLibre Plugin
To add new functionality, create a TypeScript file in packages/plugins/src/plugins/ that exports a constant conforming to PluginDefinition:
// packages/plugins/src/plugins/example-plugin.ts
import { PluginDefinition } from '../types';
export const examplePlugin: PluginDefinition = {
id: 'example-analysis',
title: 'Example Analysis Tool',
icon: '🔧',
// Component rendered in the top toolbar
ToolbarComponent: () => (
<button onClick={() => console.log('Analysis running')}>
Run Analysis
</button>
),
// Component rendered in the right panel when active
RightPanelComponent: () => (
<div>
<h3>Analysis Settings</h3>
<p>Configure your processing parameters here.</p>
</div>
),
// Function to create a custom map layer
createLayer: (store) => {
// Return layer configuration object
return {
id: 'analysis-layer',
type: 'vector',
source: store.getState().currentSource
};
}
};
After saving the file, the Plugin Manager automatically includes it in the next build. The toolbar will display the new button, the right panel will show the settings UI when activated, and the map will instantiate the specified layer according to the definition.
Summary
- Plugin Discovery: The Plugin Manager in
packages/plugins/src/plugin-manager.tsautomatically discovers modules using wildcard imports from thepackages/plugins/src/plugins/directory. - Type Safety: All plugins must implement the
PluginDefinitioninterface frompackages/plugins/src/types.ts, ensuring consistent metadata and component structure. - Registry Pattern: Specialized registries for toolbars, right panels, and floating windows handle UI integration, each exposing dedicated hooks for component access.
- State Management: The
usePlugins.tshook initializes the system and populates a Zustand store, making plugin data available throughout the React component tree. - Zero-Config Addition: Simply adding a properly structured TypeScript file to the plugins directory automatically registers the functionality without manual imports.
Frequently Asked Questions
What is the minimum required code for a valid GeoLibre plugin?
A valid plugin requires only a file in packages/plugins/src/plugins/ that exports an object with id, title, and icon properties conforming to the PluginDefinition interface. Optional properties like ToolbarComponent or RightPanelComponent enable specific UI integrations, but a plugin consisting solely of metadata is technically valid and will be registered, though it will not render visible UI elements.
How does automatic plugin discovery work under the hood?
The Plugin Manager utilizes TypeScript's wildcard import capability (import * as plugins from './plugins') combined with the build system's module resolution. When the application compiles, this import captures all modules in the plugins directory. The manager then iterates over the imported namespace, extracting each exported PluginDefinition and validating the required fields before adding them to the pluginsById map.
Can a single plugin register components in multiple UI areas simultaneously?
Yes, a single PluginDefinition can include a ToolbarComponent, RightPanelComponent, and layer creation logic simultaneously. The Plugin Manager passes the complete definition to all three registries, and each registry extracts only the components relevant to its specific UI context. This allows cohesive features that span multiple interface areas while maintaining a single source of truth in one plugin file.
Where should third-party or custom plugins be placed in a GeoLibre fork?
Custom plugins should reside in packages/plugins/src/plugins/ following the naming convention your-feature-plugin.ts. Since the Plugin Manager uses automatic discovery rather than a static import list, placing files in this directory ensures registration without modifying core framework code. For distribution, plugins can be maintained as separate packages that export PluginDefinition objects, then imported into the plugins directory index.
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 →