# How GeoLibre Distinguishes Built-In vs External Plugins

> Discover how GeoLibre separates built-in and external plugins. Learn about static import for built-ins and dynamic discovery with plugin.json manifests for external extensions.

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

---

**GeoLibre distinguishes built-in plugins from external plugins through separate discovery mechanisms: built-ins are statically imported at compile time from `packages/plugins/src/plugins/`, while external plugins are dynamically discovered at runtime from `apps/geolibre-desktop/public/plugins/` with validation via [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) manifests.**

In the **GeoLibre** open-source geospatial platform, the plugin system implements a dual-path architecture that seamlessly integrates core functionality with user-extensible features. This article examines how the source code differentiates between **built-in plugins** (shipped with the application) and **external plugins** (user-installed), covering their discovery, registration, loading, and security models.

## Discovery Mechanisms: Static vs Dynamic

The first distinction appears in how each plugin type is found. Built-in and external plugins follow fundamentally different discovery paths.

### Built-In Plugin Discovery

Built-in plugins reside in `packages/plugins/src/plugins/` as TypeScript modules. The `registerBuiltInPlugins()` function in [`packages/plugins/src/plugins/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/index.ts) uses a static import pattern to collect all plugins at build time:

```typescript
// packages/plugins/src/plugins/index.ts
import { registerPlugin } from '@geolibre/core';
import { ExamplePlugin } from './example-plugin';

// All built‑in plugins are imported statically.
registerPlugin(ExamplePlugin);

```

This approach leverages ES module static analysis, allowing bundlers like Vite to tree-shake and optimize the final bundle.

### External Plugin Discovery

External plugins use a dynamic discovery process via the `usePlugins` hook in [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts). This hook scans the `public/plugins/` directory, parses each [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) manifest, and constructs runtime `PluginDefinition` objects:

```typescript
// apps/geolibre-desktop/src/hooks/usePlugins.ts
async function loadExternalPlugins() {
  const pluginManifests = await fetch('/plugins/').then(r => r.json());

  for (const manifest of pluginManifests) {
    const moduleUrl = `/plugins/${manifest.id}/${manifest.main}`;
    const pluginModule = await import(/* @vite-ignore */ moduleUrl);
    registerPlugin(pluginModule.default);
  }
}

```

Runtime discovery enables users to install plugins without rebuilding the application.

## Registration Timing: Compile-Time vs Runtime

The **registration timing** fundamentally separates the two plugin types.

| Aspect | Built-In Plugins | External Plugins |
|--------|------------------|------------------|
| **When** | Once at application startup | Dynamically when user adds plugin |
| **Trigger** | Monorepo build process | `POST /plugins/add` API call or app startup |
| **Validation** | TypeScript compiler | Runtime [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) schema validation |

For built-in plugins, registration happens implicitly through static imports. External plugins require explicit registration through `loadExternalPlugins()`, which validates required manifest fields before calling `pluginsRegistry.register(externalDef)`.

## Loading Strategies: Bundled vs Lazy-Loaded

Loading behavior differs significantly between built-in and external plugins in GeoLibre.

- **Built-in plugins** are **bundled** with the application. Build tools include their code in the main JavaScript bundle, eliminating additional HTTP requests.

- **External plugins** are **lazy-loaded** on demand. When a layer using an external plugin activates, the system fetches the JavaScript entry point specified in `plugin.json.main`, evaluates it via dynamic `import()`, then registers the exported object.

This lazy-loading pattern prevents external plugins from impacting initial application load time.

## Security Model: Trusted vs Sandboxed

Security represents the most critical distinction. Built-in and external plugins operate under different trust boundaries.

**Built-in plugins** execute with full application privileges. As core project code maintained in the opengeos/GeoLibre repository, they undergo code review and are trusted by default.

**External plugins** are **sandboxed** through multiple mechanisms:

1. **Same-origin policy**: Served from `public/plugins/`, isolated from core codebase
2. **Manifest trust flag**: [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) declares a `trusted` boolean
3. **Capability restrictions**: Untrusted plugins lose filesystem access and other sensitive operations

```typescript
// Conceptual check within plugin execution context
if (!plugin.manifest.trusted) {
  disableFilesystemAccess();
  restrictNetworkRequests();
}

```

## Unified Registry: The Abstraction Layer

Despite divergent paths, both plugin types converge at [`packages/core/src/pluginsRegistry.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/pluginsRegistry.ts). This central registry provides a unified interface:

```typescript
// Any part of the UI can request a plugin by id:
import { pluginsRegistry } from '@geolibre/core';

const myPlugin = pluginsRegistry.get('my-plugin-id');
myPlugin?.run();   // Works whether the plugin is built‑in or external.

```

The registry's `getAll()` method returns mixed collections, allowing UI components to remain origin-agnostic. This design pattern—**separate discovery, unified consumption**—enables GeoLibre's flexible extensibility without complicating downstream code.

## Upgrade and Version Management

Each plugin type follows distinct versioning workflows.

- **Built-in plugins**: Version tied to [`packages/plugins/package.json`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/package.json). Updates require source modification and workspace rebuild (`npm run build`).

- **External plugins**: Self-contained versioning in [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json). The UI provides "Check for updates" functionality that re-downloads manifests and refreshes registration without application restart.

## Summary

- **Built-in plugins** live in `packages/plugins/src/plugins/`, discovered via static imports at compile time, bundled with the application, and run with full trust.

- **External plugins** reside in `apps/geolibre-desktop/public/plugins/`, discovered dynamically via `usePlugins`, lazy-loaded on activation, and sandboxed by origin and manifest flags.

- Both types register through `pluginsRegistry.register()` in [`packages/core/src/pluginsRegistry.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/pluginsRegistry.ts), providing a unified consumption interface.

- The [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) manifest drives external plugin metadata, validation, and trust determination.

## Frequently Asked Questions

### What file defines the structure for external plugin manifests?

The [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) file in each external plugin directory defines the structure. It specifies the plugin ID, version, entry point (`main`), and trust status. GeoLibre validates this manifest at discovery time before registration.

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

External plugins access the same plugin registry APIs but face runtime restrictions. The `trusted` flag in [`plugin.json`](https://github.com/opengeos/GeoLibre/blob/main/plugin.json) determines capability grants—untrusted plugins lose filesystem and certain network privileges despite sharing the registration interface.

### How does GeoLibre prevent malicious external plugins from harming the system?

Multiple layers protect the system: browser same-origin policy isolates plugin code; the `public/plugins/` directory structure prevents path traversal; manifest validation rejects malformed plugins; and the trust flag disables sensitive capabilities for unverified sources. Review [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts) for implementation details.

### Where should I place a new built-in plugin during development?

Create your TypeScript module under `packages/plugins/src/plugins/`, then import and register it in [`packages/plugins/src/plugins/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/index.ts). The static import system will automatically include it in the next build.