Network & Permissions
How plugins make HTTP requests, declare permissions, and use the CORS proxy.
Plugins can use fetch() to make HTTP requests. Since plugins run client-side in a Web Worker, requests go through the browser's native fetch API — subject to standard CORS rules.
Declaring permissions
Declare which hosts your plugin accesses using @permissions for documentation and future permission gating:
// @permissions network:api.github.comPermission syntax
| Pattern | Example |
|---|---|
| Single host | network:api.github.com |
| Wildcard subdomain | network:*.slack.com |
| Any host | network:* |
Multiple hosts are comma-separated:
// @permissions network:api.linear.app,network:hooks.slack.com,network:api.github.comMaking requests
Standard fetch() works for CORS-friendly APIs:
// @stoa-plugin
// @name GitHub Status
// @type widget
// @icon Globe
// @permissions network:api.github.com
// @refresh 5m
module.exports = {
async render(context) {
const resp = await fetch('https://api.github.com/repos/myorg/myrepo/actions/runs?per_page=5')
const data = await resp.json()
return {
type: 'list',
items: data.workflow_runs.map(run => ({
label: run.name,
subtitle: run.conclusion || run.status,
iconColor: run.conclusion === 'success' ? 'green' : run.conclusion === 'failure' ? 'red' : 'orange',
}))
}
}
}CORS proxy
For websites and APIs that don't support CORS, use the built-in stoaProxy() helper:
// Direct fetch (works for CORS-friendly APIs):
const resp = await fetch('https://api.github.com/repos/myorg/myrepo')
// Proxy fetch (for non-CORS websites):
const resp = await fetch(stoaProxy('https://example.com/page'))stoaProxy(url) is a global function available in all plugins. It returns the full proxy URL so fetch() works from the Web Worker. The proxy fetches the URL server-side and returns the response with CORS headers.
Proxy limits
| Limit | Value |
|---|---|
| SSRF protection | Blocks private IP ranges and metadata endpoints |
| Response size | 1MB maximum |
| Timeout | 10 seconds |
| Authentication | Required (Clerk middleware) |
Server runtime
You can set @runtime server to run a plugin in a sandboxed V8 isolate. This is only needed for future features like server-side secrets and persistent state.
Server runtime provides:
- Deny-by-default — no filesystem, no network, no env vars, no child processes unless explicitly granted
- SSRF protection — blocks requests to private IP ranges
- 64MB memory limit
- 5 second CPU timeout
- npm module access via
require()
Warning
Server runtime requires self-hosted infrastructure. It is not available on Vercel serverless.