Permissions
Ghosty plugins run in a sandboxed environment with no default access to
platform features. To use storage, make network requests, show notifications,
or navigate the host application, a plugin must declare the required permissions
in its plugin.yaml manifest.
How permissions work
- Declaration -- the developer lists required permissions in
plugin.yaml. - Install prompt -- when a user installs the plugin, Ghosty displays a consent dialog listing every requested permission with a plain-language explanation.
- Runtime enforcement -- the GhostySDK checks permissions on every API
call. If a plugin tries to call an API it has not been granted access to,
the call throws a
PermissionDeniedError. - Revocation -- users can revoke individual permissions at any time from
the plugin settings screen. Your plugin should handle
PermissionDeniedErrorgracefully and degrade functionality rather than crashing.
Only request the permissions your plugin genuinely needs. Every additional permission increases the user's trust barrier and the scrutiny applied during review.
Storage permissions
Storage permissions control access to the plugin's private key-value store. Each plugin has an isolated storage namespace; plugins cannot read or write another plugin's data.
Storage permissions
| Permission | Description | Risk |
|---|---|---|
storage.read | Read values from the plugin's private key-value store. | low |
storage.write | Write, update, and delete values in the plugin's private key-value store. | low |
storage.watch | Subscribe to real-time change events on storage keys. | low |
Storage limits
| Metric | Limit |
|---|---|
| Max keys per plugin | 1,000 |
| Max value size | 64 KB |
| Total storage per plugin | 10 MB |
| Watch subscriptions | 50 concurrent |
Notification permissions
Notification permissions control a plugin's ability to display system notifications and in-app toasts.
Notification permissions
| Permission | Description | Risk |
|---|---|---|
notifications.show | Display in-app toast notifications to the user. | medium |
notifications.system | Send OS-level system notifications (may appear even when Ghosty is in the background). | high |
notifications.badge | Update the plugin's badge count shown on its icon in the dashboard. | low |
Plugins that send excessive or spammy system notifications will be flagged during review and may be delisted from the plugin store. Use system notifications only for time-sensitive, user-initiated alerts.
Network permissions
Network permissions control outbound HTTP requests and WebSocket connections.
Network permissions
| Permission | Description | Risk |
|---|---|---|
network.fetch | Make outbound HTTP/HTTPS requests to external APIs. The target domain must be declared in the manifest's allowed_domains list. | high |
network.websocket | Open persistent WebSocket connections to declared domains. | high |
Domain allowlist
When requesting network.fetch or network.websocket, you must also declare
the specific domains your plugin will communicate with:
permissions:
- network.fetch
allowed_domains:
- api.openweathermap.org
- api.example.comThe runtime blocks any fetch or WebSocket connection to a domain not in this list. Wildcard patterns are not supported.
You cannot use * or glob patterns in allowed_domains. Every domain must
be explicitly listed. This is a deliberate security measure to prevent data
exfiltration.
Navigation permissions
Navigation permissions control the plugin's ability to interact with the Ghosty host application's navigation stack.
Navigation permissions
| Permission | Description | Risk |
|---|---|---|
navigation.open_url | Open URLs in the user's default browser. | medium |
navigation.deep_link | Navigate the Ghosty host to another plugin or built-in screen using deep links. | medium |
navigation.fullscreen | Transition the plugin from widget mode to fullscreen mode. | low |
User context permissions
User context permissions control access to information about the currently logged-in Ghosty user.
User context permissions
| Permission | Description | Risk |
|---|---|---|
user.profile | Read the user's display name and avatar URL (no email or private data). | low |
user.preferences | Read the user's locale, timezone, and theme preference. | low |
user.id | Read the user's unique anonymous identifier. Cannot be correlated to personal data. | medium |
Handling permission denial
When a user revokes a permission or the plugin attempts an unpermitted action,
the SDK throws a PermissionDeniedError. Always wrap SDK calls in try/catch
blocks and provide meaningful fallback behaviour:
import { ghosty, PermissionDeniedError } from "@ghosty/sdk";
async function loadSavedCity(): Promise<string> {
try {
const city = await ghosty.storage.get("selected_city");
return city ?? "London";
} catch (error) {
if (error instanceof PermissionDeniedError) {
// Storage permission was revoked -- fall back to default
console.warn("Storage permission denied, using default city.");
return "London";
}
throw error; // Re-throw unexpected errors
}
}Permission changes between versions
When you release a new version that requires additional permissions, Ghosty prompts the user to approve the new permissions before updating. If the user declines, they stay on the previous version.
To minimise friction:
- Add new permissions only when genuinely needed.
- Use the
descriptionfield in your store listing update notes to explain why new permissions are required. - Design your plugin so that new features behind new permissions are optional, not blocking.
Consider releasing permission-heavy features as opt-in. Request the new
permission only when the user attempts to use the feature, rather than
upfront. This pattern is supported via ghosty.permissions.request().