GhostySDK Reference
The GhostySDK is a JavaScript API automatically injected into every plugin's runtime by the Ghosty host application. It provides structured access to storage, user context, notifications, navigation, and permission management.
You access the SDK through the global ghosty object, which is available as
soon as the onInit lifecycle hook fires.
import { ghosty } from "@ghosty/sdk";
// All SDK methods are available after onInit
ghosty.storage.get("key");
ghosty.user.displayName;
ghosty.notifications.toast("Hello!");Install @ghosty/sdk as a dev dependency to get full TypeScript types
and autocomplete. The package contains type declarations only; the actual
runtime is injected by the host.
ghosty.storage
The storage API provides a persistent key-value store scoped to your plugin. Data persists across sessions and device syncs if the user has sync enabled.
Required permissions: storage.read, storage.write, storage.watch
(depending on the method).
ghosty.storage.get(key)Retrieve a value by key. Returns null if the key does not exist.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The storage key to retrieve. |
ghosty.storage.set(key, value)Store a value under the given key. Overwrites any existing value.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The storage key. |
value | string | number | boolean | object | Yes | The value to store. Objects are serialised as JSON. Max 64 KB. |
ghosty.storage.delete(key)Remove a key and its value from storage.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The storage key to delete. |
ghosty.storage.keys()List all keys currently in storage. Returns an array of strings.
ghosty.storage.watch(key, callback)Subscribe to changes on a specific key. The callback fires whenever the value is updated or deleted, including from other devices.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key to watch. |
callback | (value: unknown) => void | Yes | Called with the new value on change. Receives null on deletion. |
Storage example
import { ghosty } from "@ghosty/sdk";
// Save user preference
await ghosty.storage.set("theme", "dark");
// Read it back
const theme = await ghosty.storage.get("theme");
console.log(theme); // "dark"
// Watch for changes from other devices
ghosty.storage.watch("theme", (newValue) => {
applyTheme(newValue as string);
});
// List all keys
const allKeys = await ghosty.storage.keys();
console.log(allKeys); // ["theme"]
// Clean up
await ghosty.storage.delete("theme");ghosty.user
The user API provides read-only access to the currently signed-in user's
profile and preferences. All fields are populated after onInit completes.
Required permissions: user.profile, user.preferences, user.id
(depending on the property).
| Property | Type | Permission | Description |
|---|---|---|---|
displayName | string | null | user.profile | The user's chosen display name. |
avatarUrl | string | null | user.profile | URL of the user's avatar image. |
locale | string | user.preferences | BCP 47 locale string (e.g. en-US). |
timezone | string | user.preferences | IANA timezone (e.g. America/New_York). |
theme | "light" | "dark" | "system" | user.preferences | Current theme preference. |
id | string | user.id | Anonymous unique identifier. |
User example
import { ghosty } from "@ghosty/sdk";
function renderGreeting(container: HTMLElement): void {
const name = ghosty.user.displayName ?? "there";
const hour = new Date().getHours();
let greeting = "Good evening";
if (hour < 12) greeting = "Good morning";
else if (hour < 18) greeting = "Good afternoon";
container.innerHTML = `<h2>${greeting}, ${name}!</h2>`;
}ghosty.notifications
The notifications API lets your plugin display toast messages inside the Ghosty app and, with additional permissions, send OS-level system notifications.
Required permissions: notifications.show, notifications.system,
notifications.badge.
ghosty.notifications.toast(message, options?)Display an in-app toast notification. Toasts auto-dismiss after the configured duration.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The notification text. Max 200 characters. |
options.duration | number | No | Duration in milliseconds before auto-dismiss. Default: 4000. |
options.variant | 'info' | 'success' | 'warning' | 'error' | No | Visual style. Default: 'info'. |
options.action | { label: string; onClick: () => void } | No | Optional action button. |
ghosty.notifications.system(title, body, options?)Send an OS-level system notification. Requires the notifications.system permission.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Notification title. Max 100 characters. |
body | string | Yes | Notification body. Max 300 characters. |
options.icon | string | No | URL to a custom notification icon. |
ghosty.notifications.setBadge(count)Set the badge count displayed on the plugin's icon. Set to 0 to clear.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
count | number | Yes | Badge count. Must be a non-negative integer. |
Notifications example
import { ghosty } from "@ghosty/sdk";
// Simple toast
ghosty.notifications.toast("Settings saved successfully!", {
variant: "success",
duration: 3000,
});
// Toast with action button
ghosty.notifications.toast("New update available", {
variant: "info",
action: {
label: "View",
onClick: () => ghosty.navigate.fullscreen(),
},
});
// System notification (requires notifications.system permission)
await ghosty.notifications.system(
"Task Due",
"Your task 'Review PR #42' is due in 30 minutes.",
);
// Badge count
ghosty.notifications.setBadge(3);ghosty.navigate
The navigation API controls transitions between views and opens external URLs.
Required permissions: navigation.open_url, navigation.deep_link,
navigation.fullscreen.
ghosty.navigate.openUrl(url)Open a URL in the user's default browser. Must be an HTTPS URL.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to open. |
ghosty.navigate.fullscreen()Transition from widget view to fullscreen view. Only available for plugins that declare a fullscreen view.
ghosty.navigate.widget()Return from fullscreen view to the dashboard (widget view).
ghosty.navigate.deepLink(target)Navigate to another plugin or built-in Ghosty screen.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Deep link URI (e.g. 'ghosty://plugin/weather-widget' or 'ghosty://settings'). |
ghosty.permissions
The permissions API allows runtime permission checks and dynamic permission requests.
ghosty.permissions.has(permission)Check whether a specific permission has been granted. Returns a boolean.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
permission | string | Yes | The permission key (e.g. 'storage.read'). |
ghosty.permissions.request(permissions)Prompt the user to grant additional permissions. Returns true if all were granted.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
permissions | string[] | Yes | Array of permission keys to request. |
Use ghosty.permissions.request() for features that need extra permissions.
This way, users only see the permission prompt when they actively engage
with the feature, resulting in higher grant rates.
Error handling
All async SDK methods can throw the following error types:
| Error | Description |
|---|---|
PermissionDeniedError | The required permission has not been granted or was revoked. |
StorageQuotaExceededError | The plugin has exceeded its storage limits. |
NetworkError | An outbound request failed (timeout, DNS, or domain not in allowlist). |
SandboxError | A sandbox policy violation occurred (e.g. CSP blocked an inline script). |
TimeoutError | A lifecycle hook or SDK call exceeded the configured timeout. |
Always use try/catch and provide meaningful fallback behaviour:
import { ghosty, PermissionDeniedError, StorageQuotaExceededError } from "@ghosty/sdk";
try {
await ghosty.storage.set("large-data", payload);
} catch (error) {
if (error instanceof StorageQuotaExceededError) {
ghosty.notifications.toast("Storage full. Please delete unused data.", {
variant: "warning",
});
} else if (error instanceof PermissionDeniedError) {
ghosty.notifications.toast("Storage access denied.", { variant: "error" });
} else {
throw error;
}
}