#Plugins

A GeoSpaX plugin is a folder with a manifest and one or more tools. Tools land in the Toolbox (right pane, searchable, both modes), on the ribbon and in the Plugins pane (bottom dock, Pro); commands land on the ribbon and in the command palette (Ctrl+K). A plugin with more than 40 tools gets one ribbon button that opens the Toolbox on it.

Bundled: plugins/geolibre-tools brings 1,062 GeoLibre tools (raster 544, vector 342, LiDAR 68, conversion 53, hydrology 32, terrain 19, other 4), see §1a.

The point of the design: GeoSpaX reads the manifest formats of the four ecosystems people already write for, so the same tool works whether it was authored for GeoSpaX, RustGIS, GeoLibre, QGIS or ArcGIS Pro. Where a tool cannot run here, GeoSpaX says so instead of pretending.


#1. What is compatible with what

Ecosystem What it is Manifest GeoSpaX reads Runs?
GeoSpaX folder with plugin.yaml plugin.yaml / plugin.yml ✅ fully — process, python, r, wasm, recipe
RustGIS (opengeos/RustGIS, Apache-2.0) .rgplugin = zip of plugin.json + plugin.wasm plugin.json (commands, tools, params, permissions) ✅ the WASM module, through a WASI runner (wasmtime/wasm3/deno)
GeoLibre (opengeos/GeoLibre, MIT) TS/React plugin (plugin.json) + WASM tool bundles (geolibre-rust, WASI over /work) plugin.json, tools.mjs; the whole tool runner through the bundled geolibre-tools plugin ✅ its 1,062 tools, bundled (§1a); ❌ its UI (React components)
QGIS Python plugin; metadata.txt + __init__.py metadata.txt (hasProcessingProvider=yes) ✅ its processing algorithms, through qgis_process list/help/run --json; ❌ its PyQt UI
ArcGIS Pro .esriAddInX = zip of Config.daml + .NET assembly (+ Toolboxes/*.pyt) Config.daml, *.pyt ✅ Python toolbox (.pyt) tools through python3 + arcpy; ❌ the .NET assembly (no CLR)

Honest limits


#1a. GeoLibre tools (bundled)

plugins/geolibre-tools wraps GeoLibre's tool runner, geolibre-cli.wasm from the npm package geolibre-wasm (MIT; opengeos/geolibre-rust, built on John Lindsay's whitebox_next_gen), as an ordinary GeoSpaX process plugin:

Measured here: Buffer Vector on 1,500 points → 1,500 polygons added to the map in 0.35 s through the API; the first-use download verified and byte-identical to the npm package.


#1b. Conservation, raster and species tools (bundled)

plugins/gsx brings the conservation, biodiversity, raster and species-distribution tools into GeoSpaX 3 as a Node tool library (library: true: its tools run from recipes, the API and MCP but are not listed). The app shows them through the 22 default domain plugins plugins/geospax-<domain> (default: true), whose manifests plugins/gsx/gen-manifest.mjs writes and whose run: is the library's run.mjs. See GSX.md.

Two manifest keys serve this: library: true (a tool set other plugins run; hidden from the ribbon, Toolbox and plugin list) and default: true (honoured only for plugins shipped with GeoSpaX: listed as a default plugin, not uninstallable).

#2. Plugin folders

bundled plugins               $GEOSPAX_PLUGINS, else ./plugins (source checkout), plugins\ next to the
                              .exe (Windows) or /usr/share/geospax/plugins (.deb, Docker)
<state>/.geospax/plugins/     always read (state = the first --data folder); imports go here
--plugins DIR                 extra folder (repeatable)
~/GeoSpaX/plugins             the installed desktop app reads this too

geospax-server plugins list prints what it found:

example-python         native     python    2 tool(s)  runnable
field-recipes          native     recipe    2 tool(s)  runnable
hello-rustgis          rustgis    wasm      1 tool(s)  needs plugin.wasm next to plugin.json
save_attributes        qgis       process   0 tool(s)  needs qgis_process
Field Tools            arcgis     python    1 tool(s)  needs arcpy (for .pyt tools)

#3. Writing a GeoSpaX plugin

plugin.yaml:

plugin: example-python          # id: lowercase, no spaces
version: 0.1.0
title: Example Python tools
description: Two tools that show the contract.
author: You
license: MIT
homepage: https://example.com
kind: python                    # process | python | r | wasm | recipe
run: python3 ${plugin}/tools.py ${tool}
tools:
  - id: layer-report
    name: Layer report
    summary: Row count, field list and a value count for one layer.
    group: Example               # ribbon group / toolbox name
    icon: chart                  # any icon name in web/src/commands.ts ICONS
    params:
      - { name: layer, label: Layer, kind: layer, required: true }
      - { name: field, label: Count values of, kind: field }
  - id: point-grid
    name: Point grid
    summary: Hexagon or square grid over a layer's extent.
    group: Example
    icon: grid
    params:
      - { name: layer, label: Over, kind: layer, required: true }
      - { name: size, label: Cell size (m), kind: number, default: 500, required: true }
      - { name: shape, label: Shape, kind: choice, default: hex, choices: [hex, square] }
commands:                        # optional ribbon buttons
  - { id: about, title: About this plugin, icon: check, group: Example }

Parameter kind: number · text · bool · layer · field · file · choice. ${plugin} expands to the plugin folder, ${tool} to the tool id, ${workdir} to a scratch dir.

#Run-time contract

GeoSpaX runs run: with this JSON on stdin:

{ "plugin": "example-python", "tool": "layer-report",
  "params": { "layer": "field_samples.geojson", "field": "land_use" },
  "layers": ["field_samples.geojson", "incidents.geojson"],
  "layer_sources": { "field_samples.geojson": "/home/me/GeoSpaX/Data/field_samples.geojson", "…": "…" },
  "workdir": "/tmp/geospax-plugin-work",
  "out_dir": "/home/me/GeoSpaX/Data/.geospax/plugin-results/example-python" }

and expects JSON on stdout:

{ "ok": true, "message": "report for field_samples.geojson", "outputs": ["…/out.geojson"] }

A tool can name its ribbon tab with tab: (home, data, style, analyze, imagery, modelling, automate, view, help); tools that name none are listed in the Explorer's Plugins and the Tools tab, grouped by their group.

A plugin that reads GeoJSON sets geojson: true in plugin.yaml: every layer named by a layer parameter is then written to the workdir as GeoJSON, whatever its source format, and passed in layer_geojson ({"layer id": "/tmp/…/gsx-layer.geojson"}).

Rasters in and out. A parameter of kind raster (one) or rasters (several) is a raster on the map. GeoSpaX hands each one over as a plain float32 grid in raster_grids ({"raster id": "/tmp/…/gsx-climate.grid.json"}): the JSON header has width, height, bands, band_names, the GDAL transform, the crs (WKT), title, kind, units, and data, the path of the bands as little-endian float32, row-major, north up, band after band, NaN = no data. All rasters of one run are resampled onto the first one's grid (at most 1,500 cells a side), so a tool can stack them cell by cell with no GDAL of its own. To return a raster, write the same pair (<name>.grid.json + its float32 band) into out_dir and list the .grid.json in outputs; GeoSpaX turns it into a GeoTIFF and adds it to the map, using the header's title, kind (suitability, change, classes, slope, ndvi … pick the styling) and classes ([{value, label, color}]).

The answer may also carry table ([["label", value], …]) and notes (strings): the tool's dialog shows them under the form, so a tool can report numbers without making a layer.

Files listed in outputs are added to the map: GeoTIFFs as rasters, anything GDAL reads as vector layers. The reply to POST …/run names them in "added", and the app styles them (AutoMap) and zooms to them. Write results into out_dir. A kind: process plugin whose program (the first word of run:) is not on PATH is listed as needs <program>.

A non-zero exit turns the first line of stderr into the error shown in Explorer › Plugins › Manage plugins…. That is the whole ABI, so the same plugin shape works for a Rust binary, an R script (run: Rscript ${plugin}/tools.R ${tool}) or a WASI module (run: wasmtime run --dir ${workdir} ${plugin}/tools.wasm -- ${tool}).

kind: recipe plugins ship ordinary GeoSpaX recipes in recipes/ and their tools run through the automation engine — so they get triggers, run history, artifacts and Auto switches for free (see plugins/field-recipes).


#4. Installing plugins


#5. HTTP API

Route What
GET /api/plugins {plugins, runtimes, dirs} — installed plugins and which runtimes this machine has
POST /api/plugins/reload re-scan the plugin folders
POST /api/plugins/import {"path": "…"} → install a folder or package
GET /api/plugins/{id} one plugin
POST /api/plugins/{id}/tools/{tool}/run {"params": {…}} → run a tool; {"result", "added", "stderr"}

#6. Credits

The plugin model follows RustGIS (docs/plugins.md, Apache-2.0): a manifest plus a sandboxed WASM module, hash-pinned packages, permissions, and commands that the ribbon, the palette and the CLI all call. The WASI "tools over /work" contract follows GeoLibre's geolibre-rust tool runner (MIT). QGIS and ArcGIS Pro compatibility uses their public, documented CLI surfaces (qgis_process, Python toolboxes). See THIRD_PARTY_NOTICES.md.

Plugin manager

Open Plugins › Manage plugins. The tabs are All, Installed, Not installed, Upgradeable, Install from ZIP and Settings. Untick a plugin to switch it off: it stays installed but its tools are hidden. Built-in plugins cannot be switched off or removed.

Under Settings, add a repository (https, file:// or a local path) and press Reload repositories. A repository is a JSON index:

{"plugins": [{"id": "my-plugin", "name": "My plugin", "version": "1.2.0",
  "download": "https://example.org/my-plugin-1.2.0.zip", "sha256": "optional hex digest"}]}

Where repositories overlap, the newest version wins. A package whose SHA-256 does not match is refused. QGIS plugins.xml repositories are not read.