Plugins add commands, geoprocessing tools and assistant skills to RustGIS. A plugin has a
plugin.json manifest and is packaged as a .rgplugin file. WebAssembly plugins run sandboxed
in the desktop app, on the command line and in the web app. Process plugins
run a local program instead and need the desktop app or command line.
Browse the plugin catalog.
opengeos/rustgis-plugins holds the registry,
blocklist and rustgis-plugin SDK,
not plugin source. Plugin sources live in their own repositories; start one from the
Rust or
Python template.
Open Plugins › Plugin Manager. The Plugins tab also has Install From File and Browse Plugins buttons.
- Browse lists the plugins in the registry, with search and a category filter. Install downloads the plugin, checks its SHA-256 and id against the registry entry, and shows what the plugin may do before it loads.
- Installed lists your plugins: turn them on or off, update them, or uninstall them.
- Settings has Install From File… for a
.rgpluginfile; you can also drop the file on the window. It also sets the registry address (empty means the RustGIS registry). On the desktop, Add Plugin Folder… loads an unpacked plugin while you develop it.
A plugin's commands and tools appear on the Plugins tab, in a group named after the plugin, or on the plugin's own tab if it has one. Its commands are also in command search, and its tools are in the Tools pane under Plugins ›.
The desktop app keeps installed packages in plugins/ in its data folder (overridden by
$RUSTGIS_PLUGINS_DIR) and loads them at startup. The web app remembers the
plugins you installed from the registry and downloads them again on your next visit. Packages
installed from files are remembered when they are at most 1.5 MiB; larger ones last for the
current session only.
On the command line:
rustgis-cli plugins # installed plugins
rustgis-cli plugins browse # the registry
rustgis-cli plugins get vector-toolkit # install from the registry
rustgis-cli plugins install my.rgplugin # install a file (or a plugin folder)
rustgis-cli plugins disable vector-toolkit # enable / disable / uninstall <id>
rustgis-cli run --sample \
--cmd 'gp.run={"tool":"plugin:vector-toolkit/hexagon-grid","params":{"input":"Countries","size":300}}' \
--cmd 'share.exportMap={"path":"hex.png"}'
rustgis-cli run --plugin ./dev-plugin/ --sample --cmd 'plugin:my-plugin/hello' # load for one runUse --no-plugins to skip loading installed plugins. plugins browse and plugins get accept
--registry https://…/registry.json for another registry.
Plugins run in a sandbox: an interpreter (wasmi) with no access to files, the network, the
clock or anything else outside the module. A plugin reaches RustGIS only through the requests
described below, and RustGIS checks each one:
- Every plugin can read the map: its layers, their features and the view.
- With the
editpermission, it can change the project. It can run commands that edit it and add layers. A plugin command that changes the project is one undo step, and it is rolled back if the command fails. - With the
filespermission, it can run commands that read or write files the user names, and its tools can take a file the user picks (afileparameter). SQL, database and Zarr commands require bothfilesandnetwork, since they can reach local files or remote servers. - With the
networkpermission, it can reach the web: read a small document from a web address (host.fetch) and run the commands that fetch (adding data or services by their address). Withedittoo, it can add web tile layers (map.addTiles). The addresses are the plugin's choice and can carry what it read from the project, so this is a permission of its own. A command that would bring in a tile layer some other way (importing a map file) has it taken out again withoutnetwork. - No plugin may run the user's scripts, tasks or models, create or save models, install or locate other software, manage plugins or connections and logins, replace the project, or step through undo history. It cannot call another plugin's command directly.
A process plugin is the exception: it is a program on your computer, not sandboxed, and the install prompt says so.
The install prompt lists these permissions. Each call has a fuel budget, so a plugin stuck in a loop is stopped. Memory is capped at 512 MB, and requests and replies are size-limited. A plugin that traps, panics or returns garbage produces an error message, never a crash.
The registry is curated: plugins are reviewed before they are listed, and packages are
hash-pinned. A blocklist.json beside a registry withdraws a plugin (every version, or one
package). RustGIS checks it when you browse that registry. Desktop and command-line startup
apply the cached blocklist; the web app fetches it when restoring plugins.
Start from the plugin template (or the
Python template for a
process plugin), and read the
official plugins for examples, such as
Hello RustGIS.
A plugin uses the rustgis-plugin SDK crate:
use rustgis_plugin::{Kind, Request, ToolOutput, Value, geojson, host, json};
fn handle(req: Request) -> Result<Value, String> {
match (req.kind, req.id.as_str()) {
(Kind::Command, "layer-report") => {
let n = host::layers()?.as_array().map_or(0, Vec::len);
host::notify(&format!("{n} layers"));
Ok(json!({"layers": n}))
}
(Kind::Tool, "bounding-boxes") => {
let boxes: Vec<Value> = req.features("input")?.iter().filter_map(|f| {
let b = geojson::bbox(&geojson::positions(&f["geometry"]))?;
Some(geojson::feature(geojson::polygon(&[[b[0], b[1]], [b[2], b[1]], [b[2], b[3]], [b[0], b[3]]]), json!({})))
}).collect();
Ok(ToolOutput::new().layer("Boxes", geojson::collection(boxes)).into())
}
(_, other) => Err(format!("unknown '{other}'")),
}
}
rustgis_plugin::export!(handle);rustup target add wasm32-unknown-unknown
cargo build --release --target wasm32-unknown-unknown{
"id": "hello-rustgis",
"name": "Hello RustGIS",
"version": "0.1.0",
"description": "What it does, in a sentence or two.",
"author": "You",
"homepage": "https://github.com/you/your-plugin",
"license": "MIT",
"entry": "plugin.wasm",
"abi": 1,
"minRustgisVersion": "0.1.0",
"permissions": ["edit"],
"commands": [
{ "id": "layer-report", "label": "Layer\nReport", "help": "Shown on hover.", "icon": "stats", "params": "{}" }
],
"tools": [
{
"id": "bounding-boxes", "name": "Bounding Boxes", "summary": "One line.", "toolbox": "Hello",
"params": [
{ "name": "input", "label": "Input features", "kind": "vectorLayer", "required": true },
{ "name": "whole", "label": "One box for the whole layer", "kind": "bool", "default": false }
]
}
],
"i18n": { "de": { "Layer\nReport": "Ebenen-\nbericht" } }
}id: 1 to 64 lower-case letters, digits and hyphens, starting with a letter. Command and tool ids follow the same rules. Their full ids areplugin:<id>/<command-or-tool>; run commands directly and tools throughgp.run.runtime:wasm(the default),processorskills. For WebAssembly plugins,entrydefaults toplugin.wasmand must name a.wasmfile relative to the manifest, using forward slashes with no.or..path segments.abidefaults to1and must equal1.versionand optionalminRustgisVersionare numeric versions such as1.2.3or1.2. RustGIS compares their numeric components, ignoring prerelease and build suffixes, and refuses a plugin that needs a newer RustGIS.permissions:edit,filesandnetwork(see Safety); empty by default. Ask only for what you use.commands[].params: a string describing the command's parameters for scripts and agents, not a JSON object or a form definition; it defaults to"{}".commands[].icon: the name of one of RustGIS's own icons (stats,zoom_full,bookmark,buffer…), or a puzzle piece when it isn't one.- Tool parameter
kinds:layer,vectorLayer,polygonLayer,pointLayer,field,numericField,number,text,choice(with 1 to 64choices),boolandfile(see File parameters). Each hasname,label, optionalhelp,required(defaultfalse) and an optional JSONdefault. Parameter names start with a lower-case letter and may also contain upper-case letters and underscores after it. panel(optional) gives the plugin a pane of its own, with widgets bound to its commands. See A panel.i18ntranslates the plugin's own text into the interface language, by language code (de,zh-CN…). See Translations.skills(optional) lists skills for the AI Assistant that the package carries. See Skills.
A manifest can declare at most 256 commands, 64 tools and 32 parameters per tool.
A package is a zip archive, conventionally named <id>-<version>.rgplugin. It holds
plugin.json at its root or inside one top-level folder; entry is relative to that manifest.
Packages are at most 64 MiB with at most 4,096 entries, modules at most 48 MiB, and the manifest
at most 512 KiB.
A tool can read a file the user picks, such as a NetCDF model run, with a file parameter.
The plugin needs the files permission:
{ "name": "model", "label": "Model output", "kind": "file", "required": true, "extensions": ["nc", "nc4"] }- The Tools pane shows a path box with a Browse button.
extensionsfilters the picker: at most 16 extensions, each 1 to 16 letters or digits without the dot. Leave it out for any file. - On the desktop and the command line the value is a path, and RustGIS reads the file. In the
web app, which has no paths, the user picks the file and its contents go with the run. A
script can do the same with
{"name": "run.nc", "data": base64}. - The tool gets the file in the request's
files, by parameter name, as{"name": "run.nc", "data": base64}. The parameter itself holds just the file name. - Files are at most 64 MB. The Geoprocessing history keeps the file's name, not its contents.
- RustGIS versions before file parameters refuse a plugin that has one, so set
minRustgisVersionto the first version with them.
A plugin speaks the interface language through its i18n table: for each language code, its
English strings and their translations, written exactly as they appear in plugin.json
(\n included):
"i18n": {
"de": { "Bounding Boxes": "Begrenzungsrahmen", "Input features": "Eingabe-Features" },
"zh-CN": { "Bounding Boxes": "边界框", "Input features": "输入要素" }
}- It covers the plugin's name and description, command labels and help, tool names, summaries
and toolboxes, parameter labels and
choicevalues, the tab, group and menu labels, and the panel's title, help and widget labels. A string with no translation shows in English. - Alongside English (
en), RustGIS's interface languages arede,es,fr,it,nl,pt,ru,tr,az,id,vi,fa,zh,ja,koandka. A code with a region matches its language:zh-CNserveszh. - The registry copies the name and description translations into
registry.json, so the Plugin Manager lists plugins in the interface language before they are installed. - Messages a plugin builds while it runs can follow the interface language too: every request
carries its code as
lang. In the SDK that isreq.lang, andreq.trlooks a message up ini18n. - Up to 64 languages and 20,000 translated strings in all.
By default a plugin's commands and tools get a group on the Plugins tab. A plugin can have a
ribbon tab of its own instead. The tab sits at the end of the tab strip, and Settings › Customize › Ribbon
can hide it like any other. Declare it in plugin.json:
"tab": {
"label": "Explorer",
"groups": [
{
"label": "Go To",
"items": [
{ "menu": "Continent", "icon": "zoom_world", "help": "Go to a continent.", "items": ["go-africa", "go-europe"] },
"random-place"
]
},
{ "label": "Inspect", "items": ["layer-report", "tool:field-summary"] }
]
}itemslists the buttons. An item is a command id,tool:<id>(which opens that tool in the Tools pane),panel(which opens the plugin's panel), or a dropdownmenuwhoseitemsare any of those.- A tab has 1 to 8 groups, a group has 1 to 16 items, and a menu has 1 to 32 entries. Every entry must name one of the plugin's own commands or tools, or its panel.
- The label is one line of 1 to 32 characters and can't be the name of one of RustGIS's tabs
(Home, Data, Style, Analyze, Plugins…), including their older names. The
i18ntranslations apply to the tab, group and menu labels too.
Map Explorer is an example.
A plugin can have a panel: a pane on the right of the map with texts, checkboxes, sliders and
buttons bound to its commands. Declare it in plugin.json:
"panel": {
"title": "Historical Imagery",
"icon": "history",
"help": "Step through the archived releases of World Imagery.",
"state": "panel-state",
"items": [
{ "kind": "text", "id": "selected", "label": "Selected release" },
{ "kind": "checkbox", "id": "local", "label": "Only versions with local changes", "command": "panel-state" },
{ "kind": "slider", "id": "release", "label": "Release timeline", "steps": true, "command": "go-to-release" },
{ "kind": "button", "command": "older" },
{ "kind": "button", "command": "newer" },
{ "kind": "text", "id": "metadata", "label": "Location metadata" }
]
}titleis the pane's tab and the label of the button that opens it: one line of 1 to 48 characters. RustGIS puts the button first in the plugin's group on the Plugins tab. On a tab of its own, putpanelamong a group's items. Command search lists the panel too.iconandhelpare for that button.itemslists the widgets, top to bottom (1 to 32). Buttons that follow each other share a row. Each has anid(lower-case letters, digits and hyphens; a button's is its command's unless given), andlabelandhelp(on hover) where they apply:text: a caption (label) over text the plugin sets (textis what it shows first).checkbox: a box with alabel,defaultfalseunless given.slider: whole numbers frommintomax, both within ±1,000,000,000 (both default to0;defaultisminunless given), with a caption, the value's label, and its two ends' labels.steps: trueadds ‹ and › buttons that move it by one.button: runs itscommand; itslabelis the command's unless given.
- A checkbox runs its
commandwhen it changes, and a slider when it is let go, or stepped: never while it is dragged, while the pane shows the label of the value it would take. Without acommand, a widget's value just goes with the plugin's next request. state(optional) is a command RustGIS runs each time the panel is opened, to answer its state.- Every
commandreference, includingstate, must name one of the plugin's own commands. Widget ids are independent of command ids. A plugin with a panel can't have a command calledpanel.
A plugin keeps nothing between calls, so RustGIS keeps the panel's state. Every request of a plugin with a panel carries the widgets' values, whatever ran the command (the panel, a ribbon button, a script or the command line):
"panel": { "values": { "local": false, "release": 196 }, "changed": ["release"] }values has each checkbox (true or false) and slider (a whole number), and changed the
ones this call set: the widget the user just moved. A widget's command is run with
{"panel": {"release": 196}} as its parameters, which sets the values before the call and is
taken out of the request's params, so a script or rustgis-cli can do the same:
rustgis-cli run --sample --cmd 'plugin:historical-imagery/go-to-release={"panel": {"release": 40}}'Any command can answer the panel's new state, beside the rest of its answer:
{
"panel": {
"values": { "release": 196 },
"items": {
"selected": { "text": "2025-12-18\nRelease 13192" },
"release": { "min": 0, "max": 196, "labels": ["2014-02-20", "…", "2025-12-18"] },
"newer": { "enabled": false },
"metadata": { "text": "Source: Maxar (Vivid)\nResolution: 0.3 m" }
}
}
}- An item can set
label(nullbrings back the declared one),enabledandvisible; a text itstext; a slider itsmin,maxandlabels, one per value frommin(the numbers when there are none). What an answer leaves out stays as it was. - RustGIS checks the answer like any other: unknown ids and fields and values of the wrong type are ignored, a slider's value stays within its ends, texts are clipped to 4,000 characters, labels to 200, and a slider has at most 2,000 labels of 80 characters.
- A plugin words what it sets in the request's
lang; RustGIS translates only what the manifest declares. - A command that fails leaves the values as they were. The panel's state command is no undo step when it changes nothing in the project (it only reads the map); other commands keep theirs.
- RustGIS asks for the state only when the panel opens, and takes in what the plugin's own commands answer. It doesn't run the plugin when the map changes in other ways: undo, another plugin, a project opened. A state command that reads what it shows from the map (which release is on it) puts the panel right when it is opened again.
- The values are kept while RustGIS runs, not saved with the project.
plugin.panel {"id": "<plugin>"}answers a panel's state (valuesanditems), and setsvaluesor, with"refresh": true, runs the state command first. On the command line, which has no panes, the commands still run with the values given.- RustGIS versions before panels ignore
panel(and refuse a tab that names it): keep the commands usable from the ribbon too, or setminRustgisVersion.
Inside a request, host::call(op, params) sends one of these to RustGIS:
| Request | Answer |
|---|---|
host.info |
{rustgis, abi, plugin, permissions}: version, ABI, plugin id and permissions |
host.layers |
[{id, name, raster, visible, geometry, features, selected, active, fields: [{name, type}]}] |
host.features {layer, selected?, limit?} |
the layer's features as GeoJSON; each feature has its row |
host.view |
{center: [lon, lat], scale, projection} |
host.notify {text, error?} |
shows a message in the status bar |
host.addLayer {name, geojson} |
adds a layer (edit) |
host.fetch {url} |
{url, text, json?}: the document at a web address (network; see Fetching a document) |
map.addTiles {url, kind?, name?, attribution?, maxZoom?, overlay?, replace?, blend?, …} |
adds a tile layer (edit and network); accepts the built-in command's parameters |
any command id (layer.zoomTo, select.byAttributes, layer.symbology…) |
what the command returns, within the plugin's permissions |
In the web app a command can't wait for a download, so where layer.addUrl has to download a
file whole it answers {"pending": true, "url": …} there: RustGIS downloads the data off the
frame and adds the layer when it lands, as Home › Add Data › From URL does. A PMTiles
archive, a cloud-optimised GeoTIFF or a FlatGeobuf or GeoParquet read in place is added at once,
as elsewhere.
rustgis-cli commands lists every command id and its parameters.
host.fetch {"url": "https://…"} reads a small text document, such as a web API's JSON
answer, with a GET request. It needs the network permission. The answer is
{"url", "text", "json"?}: url is the address fetched, text the document, and json the
document parsed when it is a JSON object or array.
- Only
http://andhttps://addresses on the internet: not this computer, the local network or cloud metadata addresses, and no redirect may lead there or fromhttpstohttp. Names are checked as written and again where they resolve, on every redirect too, so a name that resolves to this computer or the local network is refused on the desktop and the command line (in the web app, the browser decides what it may reach). Requests carry no credentials (an S3 connection's included) and no headers of the plugin's. On the desktop and the command line they never go through a proxy (HTTPS_PROXYand the like are ignored), since a proxy would reach addresses RustGIS can't check. - At most 2 MB, in UTF-8 (a byte-order mark is dropped). An answer that isn't a success (404,
500…) is an error ending in
HTTP <status>. - Answers are kept for 10 minutes (up to 256 documents and 16 MB in all) while RustGIS runs, so asking for the same address again costs nothing. Each plugin has answers of its own: one never reads what another fetched. Errors are kept for 30 seconds, so every command waiting for an address sees its error; after that it is fetched again.
- On the desktop and the command line the request waits for the answer: at most 20 seconds for one, and 60 seconds for all the fetches of one plugin call. A call makes at most 64 fetches.
- In the web app nothing may wait. A fetch that isn't kept yet answers
{"pending": true, "url"}and starts in the background. The command should then return straight away, before doing anything outside the project (writing a file): RustGIS undoes what the call did to the project (its notices stay, and so does anything done outside it), and runs the command again with the same parameters once every pending answer has landed (up to 8 per call), whenhost.fetchanswers from what was kept. A command that walks a chain of documents, each address taken from the one before, runs once per step, up to 64 times. The browser follows redirects itself, and the server must allow requests from web pages (CORS).
Use host::call("host.fetch", json!({"url": url})) in the Rust SDK.
Tools work differently. A tool receives its layer parameters' features as GeoJSON (only the
selected features when a layer has a selection), and its file parameters' files. It returns
new layers and standalone tables, or a selection, plus messages for the Geoprocessing history.
RustGIS adds the results as it does for built-in tools, with undo; returning these results
does not require edit. Tools can log, but they make no host requests.
In the desktop app a plugin tool runs in the background, so the map and the rest of the window
stay usable: the Tools pane shows the line the tool last logged, or a process plugin's last
progress message, and a Cancel button. Cancel stops a process plugin's program; a WebAssembly tool runs on to its
end and its result is dropped. One plugin tool runs at a time. gp.run, scripts and
rustgis-cli still wait for the result.
The SDK implements this; it is described here for other languages.
The module is wasm32 with no imports except these three, in module rustgis:
call(ptr: i32, len: i32) -> i32runs the host request atptr({"op": "...", "params": {...}}). It returns the length of the reply, which is{"ok": value}or{"error": "text"}, or-1when there is none. A new call replaces any reply not yet copied.result(ptr: i32)copies that reply toptrand consumes it. Allocate the lengthcallreturned first.log(ptr: i32, len: i32)logs a UTF-8 line.
The module exports:
memory.rustgis_alloc(len: i32) -> i32, which returns room forlenbytes. RustGIS writes the request there.rustgis_call(ptr: i32, len: i32) -> i64, which handles the request and returnsreply_ptr << 32 | reply_len. The reply is{"ok": value}or{"error": "text"}.
Command requests are {"abi": 1, "kind": "command", "id": "...", "params": {...}, "lang": "de"}.
id is the local command id from the manifest, not its plugin:…/… id. lang is the interface
language's code (en for English). Commands of a plugin with a panel also get
panel, and may answer panel beside the rest of their ok value.
Tools get "kind": "tool" and their local tool id, plus layers, which maps layer names
(not parameter names) to FeatureCollections. When they have file inputs, files maps each
parameter name to {"name": "...", "data": "..."}, with the contents in standard padded base64.
A tool's ok value can contain:
layers:[{"name": "...", "geojson": {...}}].tables: the same shape, using FeatureCollections whose features have no geometry.selection:{"layer": "<layer name>", "rows": [0, 2, ...]}. If present, it takes precedence overlayersandtables.messages: an array of strings.
RustGIS takes at most 64 layers, 64 tables and 200 messages, with at most 5,000,000 features in each output dataset.
Each call gets a fresh instance, so a module keeps no state between calls and need not free memory. Host requests and module replies are limited to 128 MiB.
A process plugin is a program on your computer instead of a WebAssembly module: a Python script, or an executable in any language. It can use the file system, threads and any library, which lets existing analysis code (numpy, xarray, GDAL…) become a plugin without a rewrite. It is not sandboxed: it can do anything you can. The install prompt says so, and the Plugin Manager marks it Not sandboxed, in Browse too, before it is installed (with the Python version its environment downloads, if it has one). Process plugins run on the desktop and the command line, not in the web app: its Browse list shows them as Desktop and command line only, with no Install button.
{
"id": "hello-python",
"name": "Hello Python",
"version": "0.1.0",
"runtime": "process",
"run": { "windows": ["python", "main.py"], "default": ["python3", "main.py"] },
"permissions": [],
"commands": [{ "id": "layer-count", "label": "Count\nLayers" }],
"tools": [{
"id": "feature-count", "name": "Count Features", "summary": "Counts a layer's features.",
"params": [{ "name": "input", "label": "Input layer", "kind": "vectorLayer", "required": true }]
}]
}"runtime": "process"andrun: the program and its arguments, started in the plugin's folder. A bare name (python3) is looked up onPATH; a relative path (bin/tool) is in the plugin's folder.runcan name a program per platform (windows,macos,linux, anddefaultwhen the platform has no entry). Omitentry; process plugins useruninstead. Each argument list contains the program and at most 63 arguments.- Commands, tools, tabs, translations and permissions work as for WebAssembly plugins. Permissions still decide which host requests RustGIS answers, but they don't limit what the program itself can do.
- A
.rgpluginholds the plugin's files (the manifest, scripts, executables). Installing it unpacks them tounpacked/<id>/in the plugins folder, keeping Unix execute bits and rejecting unsafe paths or more than 1 GiB of unpacked files. A plugin folder installed for development (Plugin Manager › Settings, orrustgis-cli plugins install ./folder) runs where it is.
Each command or tool call starts the program once. It speaks the ABI's requests and replies over its standard input and output, one JSON object per line:
- RustGIS writes the request as one JSON line, including
panelfor a command of a plugin with a panel, andlayersand optionalfilesfor a tool. - The program writes lines:
{"call": {"op": "...", "params": {...}}}: a host request. RustGIS answers with one line,{"ok": value}or{"error": "text"}.{"log": "text"}: a line for the log (and a tool's run messages).{"progress": "text"}: what a tool is doing now, shown while it runs and not kept in its messages: a line per step of a long run, say.- Last,
{"ok": value}or{"error": "text"}: the result.
- RustGIS then closes the program's input; a program still running a few seconds later is stopped.
Output lines that aren't protocol messages are logged and skipped, since libraries print (even JSON), and so is the program's error output. A command is stopped after 2 minutes; a tool runs until it finishes or is cancelled (the desktop app runs it in the background: see What a plugin can ask for).
The plugin above, in Python with no SDK:
import json
import sys
def call(op, params):
"""Sends a request to RustGIS and returns its answer."""
print(json.dumps({"call": {"op": op, "params": params}}), flush=True)
reply = json.loads(sys.stdin.readline())
if "error" in reply:
raise RuntimeError(reply["error"])
return reply["ok"]
req = json.loads(sys.stdin.readline())
try:
if req["id"] == "layer-count":
n = len(call("host.layers", {}))
call("host.notify", {"text": f"The map has {n} layers."})
result = {"ok": {"layers": n}}
elif req["id"] == "feature-count":
layer = req["params"]["input"]
n = len(req["layers"][layer]["features"])
result = {"ok": {"layers": [], "messages": [f"{layer} has {n} features."]}}
else:
result = {"error": f"unknown command '{req['id']}'"}
except Exception as e:
result = {"error": str(e)}
print(json.dumps(result), flush=True)A Python plugin can have RustGIS make its environment, so its users don't install Python or any package themselves:
"runtime": "process",
"python": { "version": "3.12", "requirements": "requirements.txt" },
"run": ["python", "main.py"]python,python3orpython.exeas the program inrunbecomes that environment's interpreter, on every platform. The Python version defaults to3.12; omitrequirementsfor an environment with no extra packages.- RustGIS prepares the environment when the plugin is installed: in the background in the desktop
app (the Plugin Manager shows how it's going, with a Prepare button if it is not ready), and
before the command returns in
rustgis-cli plugins installandget.rustgis-cli plugins prepare <id>and theplugin.preparecommand make it on demand. - It uses uv, which RustGIS downloads once (a pinned version,
checked against its pinned SHA-256;
RUSTGIS_UVnames one to use instead, which is then trusted as it is, unchecked). uv installs a standalone Python of the version asked for and the packages in the requirements file, from PyPI. Pythons and uv's cache are shared intools/in the plugins folder; each plugin's environment is inenvs/<id>/. One environment is made at a time per plugin, even across the app andrustgis-cli. - The environment is made again when the Python version or the requirements change (a plugin update), and removed with the plugin. Until it's ready, the plugin's calls fail with a message saying how to prepare it.
- The first install downloads Python and the packages: tens to hundreds of MB, depending on them. The install prompt says so.
A plugin can carry skills for the AI Assistant: instructions
for a kind of task, which the assistant reads when a request needs them. Put each in
skills/<name>/SKILL.md in the package and list the names in plugin.json:
"skills": ["flood-map"]A SKILL.md starts with front matter giving the same name and a description that says when
to use it, then the instructions in Markdown (the
Agent Skills format). The assistant knows it as <plugin id>:<name>
(hello-rustgis:flood-map) while the plugin is enabled. A SKILL.md is at most 64 KB, and a
plugin has at most 32 skills.
A package of skills alone, with no code, says "runtime": "skills". It has no module, commands,
tools, tab, panel or permissions, and must list at least one skill. Nothing in it runs, and it
installs from a file in the desktop app, on the command line and in the web app:
{
"id": "flood-skills",
"name": "Flood mapping skills",
"version": "1.0.0",
"runtime": "skills",
"skills": ["flood-map", "gauge-report"]
}-
Write the plugin in your own repository, starting from the Rust or Python template. See Write a plugin.
-
Release it: the templates' Release workflow attaches the
.rgpluginand its registry entry to a GitHub release. -
Open a pull request on opengeos/rustgis-plugins adding that entry as
registry/<id>.json:{"id": "...", "source": {"url": "https://…/<id>-<version>.rgplugin", "sha256": "…"}, "categories": ["Analysis"]}Every plugin is listed by
source; the registry build refusespathentries. -
CI downloads the package, checks its hash, validates its manifest and module (or a process plugin's files), and runs it in RustGIS. A maintainer reviews it before merging.
-
Once merged, the registry lists the plugin and serves its package at
https://packages.rustgis.app/<id>/<id>-<version>.rgplugin. Published packages never change, and every version stays available. To update, release a new version and submit a pull request updating the entry'ssource.urlandsource.sha256.
Pull requests get a preview with an Open in RustGIS link; process plugins must be tried in the desktop app instead. The nightly workflow runs every published plugin in the latest RustGIS. See the registry's publishing instructions for details.
The file you submit is only a pointer to the release. The registry build turns it into the
published registry.json that RustGIS reads, whose entries carry package (the package's address) and
sha256 in place of source. A registry entry contains id, name, version, description, author, homepage,
license, permissions, minRustgisVersion, i18n (the name and description translated),
categories, package, lower-case hexadecimal sha256, and size in bytes.
package is an HTTPS address or a path relative to registry.json;
plain http://localhost and http://127.0.0.1 addresses are accepted for local test
registries. Entries without a valid id, name, version or SHA-256 are skipped. A process
plugin's entry also has "runtime": "process" and, when it has a managed Python environment,
"python": "3.12" (the version); a WebAssembly plugin can omit both. Registries can list
skills packages with "runtime": "skills". The Plugin Manager checks the package's hash
and id before installing; rustgis-cli plugins get also checks its runtime.
rustgis-cli plugins browse prints runtime for every entry (wasm when the registry leaves
it out).