# Writing an Orca plugin
From an empty folder to a plugin anyone can install from a catalog. The field-by-field rules
live in the generated references: [manifest](plugin-manifest.md), [catalog](plugin-catalog.md),
[host API](plugin-host-api.md), [themes](plugin-themes.md) and [theme tokens](plugin-theme-tokens.md),
[syntaxes](plugin-syntax.md). Listed plugins must follow the
[plugin listing policy](plugin-listing-policy.md).
## 1. Write
A plugin is a folder (in practice its own Git repository) with `orca-plugin.json` at the root.
Start from `examples/plugins/orca-gleam-syntax`, which carries the JSON Schema for editor
completion, or from one of:
| Example | Execution | Shows |
| ------------------------------------ | ---------------- | ------------------------------------ |
| `examples/plugins/orca-gleam-syntax` | `data` | A syntax, no code |
| `examples/plugins/hello-orca` | `trusted-worker` | A panel, a worker command and events |
Leave `publisher` out. Orca names the publisher after where the plugin comes from, the way a
[Go module path](https://go.dev/ref/mod#module-path) is its repository path: the owner of the
repository, lowercased (`acme` for `github.com/Acme/gleam-syntax`), so the plugin installs as
`acme.gleam-syntax`. Nobody can choose a publisher name that is not theirs.
| Installed from | Publisher |
| --------------------------------- | ------------------------------------------------------------ |
| The official or community catalog | the repository owner, set by the catalog in the entry's `id` |
| Another catalog | whatever that catalog's entry `id` says |
| A Git URL | the URL's owner (`.` and `_` become `-`) |
| A local or development folder | `local` |
`publisher` is deprecated: Orca ignores it for catalog and Git installs. Leaving it out needs
`engines.orca` of at least `>=1.4.246`, because older Orca requires the field and refuses a
value that differs from the catalog's. To keep supporting older Orca, keep `publisher` and set it
to the repository owner; `validate:plugin` warns about it either way. An install made before
1.4.246 keeps its key, approvals and data. A local or development folder that still declares
`publisher` keeps using it until you remove it.
When copying the Gleam example, also drop its `stablyai` publisher and `orca-` id prefix: both are
reserved for Orca's own plugins, as are owners named `official`, `anthropic`, `openai`, `github`
or starting with `orca`.
Pick the lowest `execution` profile that covers what you contribute; it decides what the user is
asked to approve:
- `data`: themes, terminal themes, icon themes, syntaxes, translations. Installs in one click.
- `declarative`: commands that run Orca actions, shortcuts, VM recipes. Reviewed before they
are enabled.
- `sandboxed-ui`: panels and custom editors, in a sandboxed frame that talks to Orca only
through `window.orca` (see [Frames](#frames-windoworca)) and can call only the host methods
whose surfaces list `panel` or `editor`.
Orca's colors reach frame CSS as `--orca-background`, `--orca-foreground`, `--orca-primary`
and the rest of its panel token set, so they never collide with a frame's own variables.
- `trusted-worker`: `main` runs in a separate Node process and can call every host method its
`capabilities` allow. There is no global switch for these full-access plugins: each one runs
only after the user approves it on install. An enterprise policy to block full-access plugins
may come later.
Set `engines.orca` to the newest `Since` of any field you use, and `pluginApi` (currently `1`)
only when a panel or worker calls the host API.
### Frames: `window.orca`
Before any plugin code runs, the frame shell defines a frozen `window.orca`. It is the only
supported way to talk to Orca; the messages underneath are internal and change without notice.
```js
orca.api // { major: 1, minor: 1 }: the host API this Orca implements
if (orca.host.has('workspace.readContext')) {
// Synchronous: is the method offered to this surface by this Orca?
const controller = new AbortController()
try {
const context = await orca.host.call('workspace.readContext', {}, { signal: controller.signal })
} catch (error) {
// error.code, error.message, error.retryable, error.retryAfterMs (rate_limited only)
}
}
const subscription = orca.on('theme', ({ colorScheme }) => {})
subscription.dispose()
```
Two calls need a click or key press in the frame (they reject with `not_permitted` otherwise)
and work only in the desktop app (in browser clients `orca.shell` and `orca.files` are undefined and
`orca.host.has` answers false):
```js
button.onclick = () => orca.shell.openExternal('https://example.com/docs') // http:, https:, mailto:
// Orca asks the person before a web host this plugin was not allowed; { opened: false } if they cancel.
const { status } = await orca.files.saveAs({
suggestedName: 'chart.png',
mimeType: 'image/png',
data: arrayBuffer
})
// status is 'saved' or 'cancelled' (the person closed the dialog). The plugin never learns the
// path, and the file is marked as downloaded so the OS warns before opening it. Any mimeType is
// accepted; the dialog filters by the suggested name's extension. Executable, script, installer,
// shortcut and disk-image types (.exe, .js, .sh, .app, .lnk, .iso…) are refused.
```
`orca.locale` is Orca's UI language as a BCP-47 tag (`navigator.language` follows the
operating system, not Orca). `` opens the system file picker.
Methods, their surfaces and their errors are in the [host API reference](plugin-host-api.md).
Aborting the `signal` rejects the call with `cancelled`. Treat an error `code` you do not know as
`action_failed`. Params are strict: a field the running Orca does not know is refused with
`invalid_params`, so check `orca.host.has` or `orca.api.minor` before using a newer one.
#### Files and assets
Preview: isolated frames are off by default for now (set `ORCA_PLUGIN_ISOLATED_FRAMES=1` to try
them); until they ship, desktop frames behave like browser clients below. With them on, in the desktop
app each plugin's frames run in their own isolated process, storage and origin,
`orca-plugin-frame:///`, served from your plugin folder. Load scripts, styles, images, fonts,
WASM and workers by relative URL (`