Skip to content

Design decisions

The SDK’s architecture is recorded as decision records: short documents that state a problem, the choice made and what it rules out. They are public because the constraints they impose show up in the API - why a plugin never sees iface, why a job is a plain function, why the two packages version independently - and a user who hits one of those constraints deserves the reasoning, not just the error.

Each row links the full record on GitHub from its number. The status is the record’s own: an accepted decision is in force, a proposed one is still under discussion and not yet what a release does, and a superseded one has been replaced by the later record its summary names. The date is when the record was written.

Number Title Decision Status Date
decision-1 Python declarative API is the plugin authoring source Plugins are written as Python declarations - decorators and typed functions - that the SDK inspects; authors never maintain a separate JSON description. Accepted 2026-10-15
decision-2 The plugin description is the shared schema contract One plugin description derived from the declarations is the contract every tool and renderer shares, with a versioned JSON form for the TypeScript side. Accepted 2026-10-15
decision-3 Development and distribution artifacts are separate qgis-sdk is developer tooling and qgis-sdk build produces a self-contained plugin ZIP, so end users install nothing but the plugin. Accepted 2026-10-15
decision-4 QGIS-aware networking uses an async-first transport Networking goes through one injectable, QGIS-aware transport that is asynchronous by default; a blocking call is always explicit. Accepted 2026-10-15
decision-5 Rust algorithm implementations are post-v1 and backend-neutral Algorithms stay in Python for 1.0; the SDK imposes no Rust toolchain on authors or users. Accepted 2026-10-15
decision-6 Separate models services core and runtime adapters The Python package is split into QGIS-free models and service contracts, a core, and native adapters that alone import QGIS. Accepted 2026-10-16
decision-7 Use layered pytest fixtures properties and behavioural contracts Testing is layered pytest fixtures over fakes with a deterministic scheduler, plus behavioural contract tests that fakes and native adapters share. Accepted 2026-10-16
decision-8 Separate check test dev and build workflows check, test, dev and build are separate commands with separate responsibilities; check never installs, builds or rewrites. Accepted 2026-10-16
decision-9 Declare allowlisted typed bridge endpoints with runtime owned routers Bridge endpoints are declared explicitly with typed request and result models and stable ids, and the runtime owns the router. Accepted 2026-10-16
decision-10 Use dataclass authoring and schema driven TypeScript generation DTOs are standard-library dataclasses in a documented typing subset; the TypeScript types and clients are generated from the derived schema. Accepted 2026-10-16
decision-11 Use one context injected class per Processing algorithm Processing algorithms were one context-injected class each; decision-42 reshaped them into plain functions on a provider. Proposed 2026-10-16
decision-12 Use one context injected controller per UI surface Superseded by decision-55: UI surfaces are plain bound classes composed from curated components, not controller classes with a build() factory. Superseded 2026-10-16
decision-13 Use runtime bound Celery inspired local jobs Local jobs are runtime-bound and owned by the scope that submitted them, with typed handles for cancellation and completion. Accepted 2026-10-16
decision-14 Enforce QGIS free import boundaries during check check enforces the import boundaries: a QGIS import outside a native module, or a pure module depending on a native one, is a check error. Accepted 2026-10-16
decision-15 Refresh development through owned runtime generations qgis-sdk dev reloads through owned runtime generations over a development-only control channel that never ships in a ZIP. Accepted 2026-10-16
decision-16 Separate reusable layer styles from UI themes Layer styles are reusable resources applied through the layer service, separate from UI themes; the renderer API is not mirrored. Accepted 2026-10-16
decision-17 Deliver a qualified Python first vertical slice for v1 1.0 is one qualified Python-first vertical slice - a reference plugin through new, check, test, dev and build - not a broad wrapper set. Accepted 2026-10-16
decision-18 Ship a framework neutral bridge and explicit web scaffolding choices The TypeScript bridge is framework-neutral with optional adapters; the root import never loads a framework, test utilities or a live connection. Accepted 2026-10-16
decision-19 Prove a non destructive local GIS reference workflow The reference workflow is a non-destructive local GIS task on a fixture dataset; it never waits on an external service and never edits the source layer. Accepted 2026-10-16
decision-20 Enforce scoped host authority and stale reference failures Web views receive opaque session-scoped handles; a stale, foreign or removed handle fails explicitly instead of resolving by name. Accepted 2026-10-16
decision-21 Bound job ownership and refuse unsafe runtime replacement Jobs submitted from a view belong to the view, disposing the view cancels them, and a runtime replacement with live work is refused. Accepted 2026-10-16
decision-22 Use strict inputs extensible results and experimental contracts Inputs are strict - unknown fields and implicit coercion are rejected - results are extensible, and experimental contracts are marked as such. Accepted 2026-10-16
decision-23 Adapt Tauri invocation resource and testing patterns to QWebChannel The bridge follows Tauri’s shape over QWebChannel: a small invocation kernel plus generated typed clients that validate responses. Accepted 2026-10-16
decision-24 Release the Python SDK and TypeScript bridge at one shared version Superseded by decision-25 before any release shipped under it: the Python SDK and the TypeScript bridge were to share one version number. Superseded 2026-10-24
decision-25 Version each package independently behind a shared contract version Each package versions independently from its own conventional commits behind a shared contract version; a release stamps only the package it publishes. Accepted 2026-10-24
decision-26 Derive the bridge allowlist and the generated client from one declared manifest One declared manifest drives the bridge allowlist, the handshake and the generated client; nothing is registered twice. Accepted 2026-10-24
decision-27 Expose curated host services to web views as generated namespaces Curated host services - layers, notifications, styles, settings - reach web views as generated namespaces on the host surface. Accepted 2026-10-24
decision-28 Declare streams as operations and generate their events not the frame envelope A stream is an operation in the same id namespace as actions; its events are generated types, the frame envelope is not. Accepted 2026-10-24
decision-29 Keep READMEs minimal and hold reference prose in backlog guides until the docs site READMEs are landing pages; the reference prose lives in the guides the documentation site renders. Accepted 2026-10-24
decision-30 Derive declaration ids and titles from names deterministically, never randomly A missing declaration id or title is derived deterministically from the decorated name, never generated randomly. Accepted 2026-10-25
decision-31 Bind the plugin context as a class attribute, not a constructor argument The runtime binds the plugin context as a class attribute, so self.ctx is readable everywhere including __init__ and the class stays plain outside a runtime. Accepted 2026-10-25
decision-32 Offer the curated services on PluginContext and build the web-view surface from it PluginContext carries the curated services, and the web-view host surface is built from that same context. Accepted 2026-10-25
decision-33 Rename the npm package to @archont561/qgis-sdk and suffix the package directories The npm package is @archont561/qgis-sdk, the counterpart of qgis-sdk on PyPI, with capabilities as subpath entries rather than package names. Accepted 2026-10-25
decision-34 Hand CLI commands a runtime through ctx.obj and library functions the same runtime as a parameter CLI commands receive one CliRuntime through ctx.obj and library functions take the same runtime as a parameter; there are no globals. Accepted 2026-10-25
decision-35 Split the Python SDK into a runtime, a tools package and a meta kit behind one contract Python ships as a stdlib-only runtime (qgis_sdk), a tools package (qgis_sdk_tools) and a meta kit (qgis-sdk), around one contract. Accepted 2026-10-25
decision-36 Cut the three Python distributions before the contract freeze The three Python distributions were cut in the 0.5.0 line; the contract freeze is a separate decision and means the runtime’s 1.0.0. Accepted 2026-10-25
decision-37 State support as a matrix of demonstrated targets Support is a matrix of demonstrated targets held once as data, each row qualified with evidence or unqualified with a named blocker. Accepted 2026-10-25
decision-38 Declare jobs as plain functions with the JobContext injected A job is a plain function whose first parameter is the injected JobContext and whose other parameters are typed keyword inputs. Accepted 2026-10-25
decision-39 Expose optional services as capability attributes that refuse by name Optional services are capability attributes that return the host’s instance or refuse by name; author code never checks for None. Accepted 2026-10-25
decision-40 Make the root entry the page import and keep generated plugin clients import-free The root of @archont561/qgis-sdk is the page import - a barrel over errors, declarations and the bridge - and generated clients import nothing. Accepted 2026-10-26
decision-41 Pin the scaffolded page to the released bridge and switch it to the workspace bridge only by explicit flag The scaffolded page pins the released bridge exactly; the release pipeline moves the pin and only an explicit flag switches to the workspace bridge. Accepted 2026-10-26
decision-42 Declare algorithms as plain functions on providers the plugin owns Algorithms are plain functions decorated with @provider.algorithm on a Provider the plugin lists, with the AlgorithmContext injected. Accepted 2026-10-26
decision-43 Generate the QGIS host artifacts in build and dev instead of scaffolding them build and dev generate the QGIS host artifacts - metadata.txt, the classFactory - so the source tree holds only the plugin. Accepted 2026-10-26
decision-44 Generate a typed bridge schema module and build the client from it at runtime qgis-sdk bridge generate writes a typed schema module, and QgisSdk builds the client from it at runtime. Accepted 2026-10-26
decision-45 Bind actions to the Desktop with stackable toolbar and menu decorators and plain surface classes Desktop bindings are stackable @toolbar, @menu and @shortcut decorators over @action, and surfaces are plain classes. Accepted 2026-10-26
decision-46 Ship UI helpers as declaration kinds, standard surfaces, headless web bindings and themed defaults UI helpers ship as parameter kinds complete across hosts, standard surfaces, headless web bindings and themed defaults. Accepted 2026-10-26
decision-47 Build web UI on a framework-free store core with per-framework adapters and ship components as qgis-sdk-ui Web UI is a framework-free store core in the bridge root with per-framework adapters, and the components ship as qgis-sdk-ui. Accepted 2026-10-26
decision-48 Give the QGIS Python console the curated services and plugin handles, never iface The QGIS Python console gets the curated services and plugin handles through qgis_sdk.console.connect, never iface. Accepted 2026-10-26
decision-49 Expose the runtime to agents over a loopback transport with the MCP server in the tools for developers and users alike Agents reach the runtime over a loopback transport, with the MCP server in the tools and separate developer and user modes. Superseded 2026-10-26
decision-50 Declare third-party Python packages as externals with a startup probe and vendor only pure-Python ones Third-party Python packages are declared as externals with a startup probe; only pure-Python ones are vendored into the ZIP. Accepted 2026-10-26
decision-51 Snapshot the plugin’s own surfaces as a structured tree plus screenshot for agents and tests A UI snapshot of the plugin’s own surfaces is a structured tree plus a screenshot, for agents and tests alike. Accepted 2026-10-26
decision-52 Run one plugin class on three hosts - QGIS Desktop, QGIS Server and standalone PyQGIS apps One plugin class runs on QGIS Desktop, QGIS Server and standalone PyQGIS apps from a declared host set; a capability a host lacks refuses by name. Accepted 2026-10-26
decision-53 Run offloaded job steps as QgsTasks under QgsTaskManager and keep the step scheduler on the driving thread Offloaded job steps run as QgsTasks under QgsTaskManager, and the step scheduler stays on the driving thread. Accepted 2026-10-26
decision-54 Cover every QGIS service and layer kind through the contract-fake-native-host-grant pattern under one grant vocabulary A QGIS service exists only when its contract, models, fake, native adapter, host operations and grants land together. Accepted 2026-10-26
decision-55 Compose plugin UI from curated components and bound surface classes, and generate nothing from an action Plugin UI is composed from curated components and bound surface classes; nothing is generated from an action. Accepted 2026-10-26
decision-56 Define 1.0 API stability as tiers with a generated public API inventory and a one-minor deprecation window 1.0 stability is three named tiers, a generated public API inventory and a one-minor deprecation window. Accepted 2026-10-26
decision-57 Support QGIS 4 LTR and latest on Linux and Windows with a native CI leg per row, and keep QGIS 3 out of scope The SDK supports QGIS 4 LTR and latest on Linux and Windows with a native CI leg per row; QGIS 3 is out of scope. Accepted 2026-10-26
decision-58 Publish plugins to the official QGIS repository from the tools, and ship standalone apps as conda packages qgis-sdk publish uploads plugins to the official QGIS repository, and standalone apps ship as conda packages. Accepted 2026-10-26
decision-59 Carry large and binary payloads across the bridge and agent boundaries by reference, with bounded pages and backpressure Large and binary payloads cross the bridge and agent boundaries by reference, with bounded pages and backpressure. Accepted 2026-10-26
decision-60 Translate declared strings through Qt catalogues shared with the page, and make accessible names a check rule Declared strings are translated through Qt catalogues shared with the page, and accessible names are a check rule. Accepted 2026-10-26
decision-61 Version plugin settings with migrations, add first-run and reset hooks, and keep runtimes isolated per plugin Plugin settings carry a schema version with migrations, first-run and reset hooks, and runtimes stay isolated per plugin. Accepted 2026-10-26
decision-62 Write one authority and threat model document and make every check and grant refer to it One authority and threat model document names principals, assets and boundaries, and every check and grant refers to it. Accepted 2026-10-26
decision-63 Add a structured plugin log service, crash capture into it, and a diagnostics bundle, with no telemetry A structured plugin log service with crash capture and a diagnostics bundle, and no telemetry. Accepted 2026-10-26
decision-64 Generate the Processing catalogue from the host registry and run any registered algorithm through one typed surface The Processing catalogue is generated from the host registry, and any registered algorithm runs through one typed surface. Accepted 2026-10-27
decision-65 Run registered jobs on workers with owner-scoped named submission In the 0.9.0 design, whole registered jobs run on workers and are submitted by name through owner-scoped handles. Accepted 2026-10-28
decision-66 Unify declaration introspection with describe and declaration methods A single describe helper and declaration methods expose kind-specific metadata, including local UI bindings, without changing the bridge contract. Accepted 2026-10-28
decision-67 Retire the optional Pydantic controlled deployment adapter The optional Pydantic controlled-deployment adapter and extras are retired in a future Python minor release; third-party integrations remain plugin-owned. Accepted 2026-10-28
decision-68 Explore additive plugin applications with explicit action groups Proposed: explore additive plugin application objects and explicit action groups while preserving class-based plugins and the bridge contract. Proposed 2026-10-28
decision-69 Order host hooks, bound the app-host shutdown and guard GUI-bound capabilities at runtime Order host hooks, bound the app-host shutdown and guard GUI-bound capabilities at runtime. Accepted 2026-10-30
decision-70 Support QGIS 3.44 LTS on both hosts while QGIS 4 x is not on apt, with Qt imported through qgis PyQt Support QGIS 3.44 LTS on both hosts while QGIS 4 x is not on apt, with Qt imported through qgis PyQt. Accepted 2026-10-30