Projelli plugins run in a sandboxed Web Worker. By default, a plugin can register commands, add toolbar buttons, add sidebar panels, add settings pages, persist its own key/value storage, and show notifications. Anything that touches the user's files, the editor, an AI provider, or the network requires an explicit permission, declared in manifest.json, that the user approves on install.
There are exactly 6 permissions. Pick the smallest set that lets your plugin work. Users see your permission list before they install, and a plugin asking for everything will get fewer installs than one asking for the minimum.
| Permission | Grants | Risk |
|---|---|---|
workspace:read | List files and read file contents in the user's workspace. | Reads private notes. |
workspace:write | Create, overwrite, or modify files in the workspace. | Can corrupt or replace user data. |
editor:selection | Read the selected text and the active document's content. | Reads what the user is currently editing. |
editor:write | Replace the selection or insert text at the cursor. | Can change the active document. |
ai:invoke | Call the user's configured AI provider with a prompt. | Spends the user's API credits. |
network | Make HTTP requests from the worker to any URL. | Can exfiltrate data, contact arbitrary servers. |
These never require a permission. Use them freely:
api.commands.register and api.commands.invokeapi.toolbar.addButton and api.toolbar.removeButtonapi.sidebar.addPanel and api.sidebar.removePanelapi.settings.addPage, api.settings.get, api.settings.setapi.storage.get, api.storage.set, api.storage.removeapi.notify.info, api.notify.warn, api.notify.errorIf your plugin only uses these, declare an empty permissions array ("permissions": []). The Pomodoro example does exactly that.
Grants: api.workspace.listFiles(path?) and api.workspace.readFile(path).
Declare it when: your plugin needs to scan the workspace for files (e.g. a backlinks indexer, a research aggregator) or read files other than the active document.
network, the plugin can ship those files anywhere. Don't ask for this unless you actually need it."permissions": ["workspace:read"]
Grants: api.workspace.writeFile(path, content).
Declare it when: your plugin needs to create or overwrite files in the workspace (e.g. an export-to-PDF plugin that writes the result, a template plugin that scaffolds a folder).
"permissions": ["workspace:write"]
Grants: api.editor.getSelection() and api.editor.getContent().
Declare it when: your plugin needs to read what the user is currently editing. This is the most common permission for editor-enhancing plugins (word counters, grammar checkers, summarizers).
network or ai:invoke, that text leaves the machine."permissions": ["editor:selection"]
Grants: api.editor.replaceSelection(text) and api.editor.insertAtCursor(text).
Declare it when: your plugin modifies the active document (e.g. translation, tone shift, snippet expansion).
"permissions": ["editor:selection", "editor:write"]
Grants: api.ai.invoke({ prompt, system }). The call goes through the user's configured AI provider (Claude, OpenAI, or Gemini) using their stored API key. The plugin never sees the key.
Declare it when: your plugin needs to call an LLM. This is BYOK; the user pays their provider, not Projelli.
"permissions": ["ai:invoke"]
Grants: api.network.fetch(url, opts?). The worker can make arbitrary HTTP requests.
Declare it when: your plugin needs to talk to a third-party API directly (not through the user's AI provider). Examples: pulling research from a knowledge base, posting to a project tracker, syncing to a remote service.
"permissions": ["network"]
workspace:read, don't declare it. Each unused permission costs you installs.workspace:read + network, users will assume you're sending their files somewhere. Be explicit about what your plugin does and doesn't do.The API call rejects with a permission error. The plugin can catch it and degrade gracefully. The host doesn't crash and other plugins are unaffected.
Next: API reference →