Skip to content

Develop a plugin

Plugins are trusted code that runs with full app privileges, so the registry is curated: open a pull request and a maintainer reviews it before it ships.

Start from the template

The geolibre-plugin-template is the recommended starting point. It includes a MapLibre control wrapper, a plugin.json manifest, a GeoLibre plugin entry point, and a build that produces the bundle layout below. The plugins/sample/ plugin in this repo is a minimal in-repo example.

1. Build a plugin entry

Your entry is a single self-contained ES module — bundle your dependencies in; relative imports are not resolved by the loader. It must export a GeoLibrePlugin as the default export or a named plugin export, and its id / name / version must match the plugin.json manifest:

export const plugin = {
  id: "my-plugin",
  name: "My Plugin",
  version: "1.0.0",
  activate(app) {
    // add a control, layer, etc. using the app API
  },
  deactivate(app) {
    // tear down whatever activate added
  },
};
export default plugin;

External plugins must not set activeByDefault. The optional style CSS is injected globally, so scope your selectors (e.g. a plugin-specific class prefix); hsl(var(--foreground)) and the other GeoLibre design tokens are available so a control can match the in-app light/dark theme.

2. Add the plugin folder

Create plugins/<id>/ with plugin.json, the built entry JS, and any style CSS. Keep entry/style paths relative and inside the folder:

plugins/
  my-plugin/
    plugin.json
    index.js
    style.css

Commit the bundle exactly as your build emits it — CI takes care of the size. The Minify plugin bundles workflow whitespace-minifies every committed plugins/**/*.js, which is worth roughly 73% of the line count here (most plugin builds mangle identifiers but leave the whitespace in, so a 3.5 MB bundle can land as a 92,000-line diff). Nothing else changes: no identifier mangling, no syntax rewriting, no tree shaking, and dependency license headers are preserved.

For a branch in this repository the workflow pushes the minified bundles back to your branch automatically. From a fork it cannot — GitHub's Actions token has no write access to your fork — so it fails instead and you run it yourself:

npm ci
npm run minify        # rewrite the bundles in place
npm run minify:check  # what CI checks

The failing run also uploads a minified-bundles artifact you can download and commit if you would rather not install Node locally.

3. Register it

Add an entry to plugin-registry.json with manifestUrl pointing at plugins/my-plugin/plugin.json (relative) — or an absolute HTTPS URL if you host the plugin elsewhere. Set minGeoLibreVersion to the lowest GeoLibre version you support.

4. Test locally

Point a local GeoLibre build at your branch's registry, then open Settings → Manage Plugins and install it:

# serve this repo with CORS on http://localhost:8090, then build GeoLibre with:
VITE_GEOLIBRE_PLUGIN_REGISTRY_URL=http://localhost:8090/plugin-registry.json

http://localhost and HTTPS registries are accepted; other schemes are rejected.

5. Format and check

This repo uses pre-commit to format code (Prettier for JS/CSS/JSON/YAML, Ruff for Python) and to reject files larger than 10 MB:

pip install pre-commit
pre-commit install   # run automatically on `git commit`
pre-commit run --all-files

Built plugin bundles (plugins/*/index.js, plugins/*/style.css) are size checked but never reformatted by pre-commit — the only thing that rewrites them is the whitespace minification in step 2, which pre-commit leaves alone.

The same hooks run on every pull request in the Lint workflow, so a branch that has not been formatted will fail CI.

6. Open a pull request

Every pull request that touches plugins/<id>/ gets a live preview: CI builds GeoLibre with your plugin baked in and posts the URL as a comment.

https://opengeos.org/pages-preview/geolibre-plugins/pr-<N>/

Your plugin loads automatically there, so you can exercise it in the real app before review. The preview rebuilds on every push and is deleted when the pull request closes.

Previews run unreviewed code

A preview executes the pull request's plugin with full app privileges in your browser. Open previews only for pull requests you are reviewing.

On merge to main, the Pages workflow publishes the update to plugins.geolibre.app and the new plugin appears in the catalog and in GeoLibre's Manage Plugins dialog.

Updating a plugin

Bump version in both the plugin's plugin.json and its registry entry, update the built assets, and open a pull request. GeoLibre shows an Update action to users whose installed version is older than the registry version; uninstalling removes the plugin at runtime.

Security model

  • The registry is an allowlist — only curated entries are offered for install.
  • Manifest URLs must be HTTPS (or HTTP on localhost); other schemes are dropped.
  • homepage must be http(s); other schemes are dropped before rendering.
  • A plugin's entry executes with the same privileges as GeoLibre itself, which is why entries are reviewed and the registry is curated.