Plugin SDK (qgis-sdk)
Plugin SDK — qgis-sdk
qgis-sdk is a Python framework for building QGIS plugins — now with a Rust-native CLI (qgis-plugin) and optional Rust acceleration, all installable from pip or conda-forge at native speed.
- pip:
pip install qgis-sdk→import qgis_sdk+qgis-pluginbinary on PATH - conda:
conda install -c conda-forge qgis-sdk→ same, with QGIS backend
qgis-rs (rendering/tiling) can stay alongside — both packages share the same Rust workspace and both ship Rust binaries.
Installation
pip install qgis-sdk# orconda install -c conda-forge qgis-sdkpixi add qgis-sdk
# Testpython -c "import qgis_sdk; print(qgis_sdk.__version__, qgis_sdk.HAS_RUST)"qgis-plugin --helpqgis-plugin versionFrom source:
git clone https://github.com/Archont561/qgis-rscd qgis-rspip install maturin(cd py-packages/qgis-sdk && maturin develop)python -m pytest py-packages/qgis-sdk/tests -vPython API
from qgis_sdk import Plugin, action, toolbar, menu
class MyPlugin(Plugin): name = "My Plugin" version = "0.1.0" description = "Does useful things" author = "Your Name" email = "you@example.com" qgis_min_version = "3.28" category = "Vector"
@toolbar("My Toolbar") @action(tooltip="Run my tool", icon="icons/tool.svg") def run_tool(self, iface): layer = iface.activeLayer() print(layer.name(), layer.featureCount())
@menu("Plugins", "My Plugin", "Settings") def open_settings(self, iface): ...
# metadata.txt generation (Rust-accelerated when _core present)print(MyPlugin.metadata_txt())MyPlugin.write_metadata("./my_plugin")Processing algorithm:
from qgis_sdk import Algorithm, parameter, output
class BufferAdvanced(Algorithm): id = "my_plugin:buffer_advanced" name = "Advanced Buffer" group = "Vector geometry"
input_layer = parameter.source("Input layer") distance = parameter.distance("Buffer distance", default=10.0) dissolve = parameter.boolean("Dissolve results", default=False) output_layer = output.sink("Buffered")
def process(self, context): distance = context.get(self.distance) # ... return {self.output_layer.name: distance}CLI — Rust-native
# Scaffoldqgis-plugin new my_plugin --type processing --rustqgis-plugin new my_plugin --author "Your Name" --email "you@example.com"
# Validateqgis-plugin validateqgis-plugin validate ./my_pluginqgis-plugin info ./my_plugin --json
# Build, test, installqgis-plugin buildqgis-plugin testqgis-plugin installqgis-plugin dev --rust --launch
# Package for QGIS Plugin Repositoryqgis-plugin package -o dist/qgis-plugin publish --zip dist/my_plugin-0.1.0.zip --dry-run
# Rust accelerationqgis-plugin rust initqgis-plugin rust build --release
# Via Python module (same speed — uses Rust extension directly)python -m qgis_sdk.cli new my_pluginpython -m qgis_sdk.cli validateqgis-sdk version # aliasCommands
| Command | Pure Rust? | Description |
|---|---|---|
new | ✅ | Scaffold new plugin |
build | ✅ | Build package |
test | ✅ | Run tests |
install | ✅ | Install into QGIS profile |
dev | ✅ | Watch mode |
package | ✅ | Create .zip |
publish | ✅ | Upload to repo |
validate | ✅ | Check structure |
info | ✅ | Show metadata |
rust init | ✅ | Add Rust module |
rust build | ✅ | Build Rust module |
version | ✅ | Print version |
Architecture
qgis-sdk wheel├── qgis_sdk/│ ├── __init__.py → Python API (Plugin, Algorithm, metadata)│ ├── _core.so → Rust cdylib (PyO3) — metadata, validation, scaffolding at native speed│ ├── _fallback_cli.py → pure-Python fallback when _core not built│ ├── scaffold.py → pure-Python scaffolding fallback│ ├── cli.py → Python CLI wrapper (uses _core directly)│ ├── plugin.py → Plugin base class│ ├── algorithm.py → Algorithm base class│ └── runtime.py → lazy PyQGIS access└── bin/ ├── qgis-plugin → Rust binary (native speed) └── qgis-sdk → alias binary- Rust crate:
crates/qgis-sdk/Cargo.toml— lib_core+ binsqgis-plugin,qgis-sdk, reached frompy-packages/qgis-sdk/pyproject.tomlvia[tool.maturin].manifest-path - Python:
src/qgis_sdk/withpyproject.tomlusing maturin - Conda:
conda-recipe/meta.yamlbuilds Rust binaries + Python wheel
UI, Typed Bridge, and Testing
UI Dialogs + WebEngine
qgis-plugin new my_plugin --web --framework react# Generates ui/main_dialog.ui + dialogs/main_dialog.py + web/map.html + web/bridge.d.ts + frontend/See Plugin Development Guide and Web Frameworks Guide.
Typed Bridge — @qgis-sdk/bridge
npm install @qgis-sdk/bridgeqgis-plugin bridge generate --bridge my_plugin.dialogs.web_dialog:Bridge --output web/bridge.d.tsimport { createBridge } from '@qgis-sdk/bridge';import type { Bridge } from './web/bridge.d.ts';const bridge = await createBridge<Bridge>(); // auto-injects qrc:///qtwebchannel/qwebchannel.jsReact: import { useQgisBridge } from '@qgis-sdk/bridge/react'
Vue: import { useQgisBridge } from '@qgis-sdk/bridge/vue'
Web Components: <qgis-bridge> + import '@qgis-sdk/bridge/webcomponents'
See Typed Bridge Guide.
Testing without QGIS
qgis_sdk.testing provides fakes + pytest fixtures:
pytest_plugins = ["qgis_sdk.testing"]
def test_toolbar(fake_iface, fake_action_factory): from my_plugin import MyPlugin MyPlugin.action_factory = staticmethod(fake_action_factory) plugin = MyPlugin(fake_iface) plugin.init_gui() assert len(fake_iface.toolbar_icons) == 1
def test_bridge(fake_bridge): assert fake_bridge.get_layer()["name"] == "test_layer"Fakes: FakeIface, FakeAction, FakeDialog, FakeWebView, FakeBridge, mock_features(), mock_source().
python -m pytest py-packages/qgis-sdk/tests -q # 112 tests, no QGISConda-forge
See py-packages/qgis-sdk/conda-recipe/meta.yaml. It builds Rust binaries and Python extension and installs both.
To publish:
- Fork https://github.com/conda-forge/staged-recipes
- Copy
conda-recipe/torecipes/qgis-sdk/ - Open PR