Plugin development
How a WiTwin Studio Plugin is built: its manifest, Python and web halves, slots, tests and release, and the order to read these pages in.
This folder is the one entry point for writing a WiTwin Plugin. Other plugin
documents in dev_docs/ describe history or internals; when they disagree
with this folder, this folder and the code win.
Every Studio panel is a Plugin (decision D16, dev_docs/DECISIONS.md). The
Console, the Git panel and a third-party solver Plugin use the same host API.
The only differences are policy: system Plugins ship inside Studio, are
trusted in advance, may use proposed APIs and cannot be uninstalled (they can
be turned off per Project, or replaced; see Disabling, replacing and profiles). Design: dev_docs/everything-is-a-plugin-plan.md.
A Plugin in one picture#
<plugin>/
witwin.toml manifest: id, dependencies, what it contributes (read without running code)
pyproject.toml pip distribution wt-<domain>, entry point witwin.plugins
__init__.py Python half: activate(ctx) / deactivate(ctx), runs in the ProjectServer
api.py optional: what other Plugins may import from this one
web/viewport.js optional web half: export default definePlugin({...}), runs in the Studio window
tests/ pytest, including the boundary checkThree processes can run Plugin code. The Python half runs in the
ProjectServer. The web half runs in the Studio window (desktop or browser).
Heavy work runs in solver children on the Project interpreter. The halves
talk only through ctx.rpc / host.services.get('rpc') and files.
Plugin, slot entry or facet?#
Pick the smallest thing that does the job.
| You want to | Make | Example |
|---|---|---|
| Add a panel, a domain, a solver, Tools, a file format | a Plugin | wt_maxwell, studio.git |
| Add something inside another Plugin's UI (a row, a viewer, an inspector, a Study kind editor) | a slot entry from your Plugin, with an optional dependency on the owner | a Console channel in console.channels |
| Add fields to a component another Plugin owns, stored in the same component | a component facet (ctx.components.add_facet) | studio.em adds the "em" fields to Material |
Add a kind of Study, an optimizer, an objective or a witwin CLI command that runs without Studio | an SDK entry point in witwin.studio (layer A) | witwin.study_kinds |
A slot entry and a facet always come from some Plugin; they are not separate packages. If the owner Plugin is off, the entry waits and the facet's values are kept untouched in the file.
Read in this order#
- How the Plugin system works: how the system works as a whole: the words, where code runs, the API layers and versions, the load pipeline from folder to active Plugin, unload, the security model and where state lives.
- The manifest:
witwin.toml, dependencies (optional),sdkVersion,enabledApiProposals,contributes.*. - Python API: the three Python layers,
ctx.*, components and facets, entry points, the import rule. - Web API:
@witwin/host2.0,definePlugin,inject, stable and proposed services, registration functions, slots. - Linking panels: how panels work together (
selection,openerwithwitwin://URIs,events). - Files and the network: Project paths, resource references, signed URLs, local files, desktop versus web.
- Testing a Plugin:
witwin_server.api.testing, the boundary check, the web-half test, the dual-mode e2e. - Publishing, installing and trust: install sources, trust, versions.
- Disabling, replacing and profiles: turning system Plugins off,
replaces, profiles andhosts. - Plugin standards: every rule a Plugin follows (names, versions, code boundaries, behaviour, interface, security, tests), what enforces each one, and the checklist before you publish.
Generated reference (do not edit by hand; each page says which script writes it):
- Python stable API: every
witwin_server.apiname. - PluginContext (ctx): every
ctxmember and its methods. - witwin.toml keys: every
witwin.tomlkey the server reads. - @witwin/host services: every service and
hostmember of@witwin/host. - Slots, events and URIs: every slot, event and URI helper.
A complete small example#
server/tests/fixtures/plugins/witwin_example_notes/ is a Plugin written from
these pages alone: a Python half with an RPC method that reads a Project file
through ctx.fs, and a web half that adds a Console channel through the
console.channels slot with an optional dependency on studio.console. Its
tests are server/tests/test_example_plugin.py (Python half, boundary
check) and studio/src/app/pluginHost/examplePlugin.test.ts (web half).
The skill#
The in-app Agent and Claude Code use the same skill:
server/witwin_server/skills/creating-plugins/SKILL.md (Agent) and its
generated copy .claude/skills/witwin-plugin-dev/SKILL.md (Claude Code).
Edit only the first, then run node studio/scripts/gen-plugin-docs.mjs.
Keeping these pages true#
node studio/scripts/gen-plugin-docs.mjswrites the web reference pages and the skill copy;npm run check:plugin-docs(instudio/) fails when they are stale.python server/scripts/gen_plugin_api_docs.pywrites the Python reference pages;server/tests/test_plugin_docs.pyfails when they are stale, when a manifest key orctxmember has no explanation in the hand-written pages, or when the skill copy differs from its source.