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:
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:
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.
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.
homepagemust behttp(s); other schemes are dropped before rendering.- A plugin's
entryexecutes with the same privileges as GeoLibre itself, which is why entries are reviewed and the registry is curated.