The pieces
For how the host is put together internally, see Plugin host in the contributor docs.
Worlds, imports and exports
The plugin API is one WIT package. Its base world,plugin, lists what a component may import (host interfaces it calls) and what it must export (plugin interfaces the IDE calls):
- Imports:
log,ui,storage,fs,workspace,editor,decorations,invocation,vcs,commit,events,tasks,diagnostics,net,secrets,process,ipc,output,auth,command-runner,views,run,run-targets,database,mcp,settings. - Exports:
lifecycle(activate, deactivate),commands(run a command),event-handler(a batch of events).
plugin and add one export. A plugin exports one only when its manifest declares the matching contribution:
In Rust you list the flags:
register_plugin!(MyPlugin, providers, views). Each world also has an export-only twin (syntax, editor-providers, views-provider, …) that component tools merge with plugin.
Calls in both directions
- IDE → plugin (exports): the IDE calls
commands.runwhen the user runs a command,event-handler.handlewith batches of subscribed events, provider functions when the editor needs completion, hover and so on, andview-provider.renderto draw a view. - Plugin → IDE (imports): inside any of those calls the plugin calls host functions such as
editor.text-editor.document,workspace.project.read-fileornet.fetch. A host function the plugin lacks a capability for returnserror::denied("<capability>"); the plugin keeps running and can degrade.
The sandbox
Each plugin gets its own wasmtimeStore:
- no preopened directories, no environment variables, no network, no inherited stdin or stdout;
- from WASI, only clocks and randomness;
- stderr is captured:
eprintln!output goes to the IDE log, and a panic’s message is shown with the crash.
<iframe sandbox="allow-scripts"> loaded from the IDE’s own openidea-plugin URL scheme: an opaque origin with no network, no cookies or storage, no IDE API, and only the page’s own files. The page talks to the plugin’s code through postMessage.
Limits
Every call into a plugin is bounded:
Waiting for the user (a picker, an input box, a confirmation) does not count against the time limit. Individual host functions have their own caps (file sizes, number of handles, response sizes), listed in
plugins/README.md.
Threads and crashes
Each active plugin runs on its own thread (an actor that owns the plugin’s runtime). Callers queue jobs and wait on a reply, so calls into one instance never run concurrently. An editor caller can stop waiting at its soft deadline while the call goes on until the hard deadline; calls can be cancelled from any thread. Wall-clock limits use wasmtime’s epoch interruption. A panic, trap, permission violation or exceeded limit drops the instance. The user is told once, and the plugin starts afresh the next time it is needed, after a pause that doubles with each crash in a row (1 s, 2 s, …, at most 60 s). After three crashes in a row the plugin is marked Failed for the session, with the reason in Settings › Plugins; Restart, toggling it off and on, or Reload from disk starts it again. A call that returns normally resets the count. A crashing plugin never affects the IDE itself. Settings › Plugins shows each plugin’s activations, recent call latency (p95), crashes, late editor results and idle reclaims.Remote projects
Plugin code runs where the project is. When a window opens a remote project, the desktop sends its enabled code plugins and their grants toopenidea-server, which runs them next to the files: file reads, searches, processes, network requests and diagnostics all act on the remote machine, and process: programs are resolved there. Secrets stay in the desktop’s keychain; the server asks for one when a request needs it. Views, notifications, status items and terminals of remote plugins show in the window as usual.
Current limits: project plugins (.openidea/plugins) don’t run in remote projects yet, and a plugin’s storage on the server starts empty.