App State
App state is a small shared store available through ctx.appState. Use it for
serializable settings and runtime preferences that need to be shared with the
host or other runtime features.
App state is not a database or private plugin storage. Do not store secrets, large content, or values that other permitted plugins must not read. Use a stable plugin-owned path prefix to avoid collisions.
const ENABLED_PATH = "plugins.focus-tools.enabled";Permission
Section titled “Permission”Plugins must request api.app-state before using ctx.appState:
{ "recall": { "permissions": ["api.app-state"] }}The host makes ctx.appState available only when that permission is granted.
Read a value
Section titled “Read a value”Use get(path) to read the current value at a dot-notation path:
const enabled = await ctx.appState.get("plugins.focus-tools.enabled");Dot paths address nested state. For example,
plugins.focus-tools.preferences.compactMode refers to compactMode inside
the preferences object owned by the focus-tools plugin.
Write a value
Section titled “Write a value”Use set(path, value) to write a value:
await ctx.appState.set("plugins.focus-tools.enabled", true);set also accepts an updater that receives the current value:
await ctx.appState.set("plugins.focus-tools.sessionCount", (count) => typeof count === "number" ? count + 1 : 1,);Keep stored values small and serializable, such as strings, numbers, booleans, arrays, and plain objects.
Subscribe to changes
Section titled “Subscribe to changes”Pass a path and listener to observe changes affecting that path:
const unsubscribe = await ctx.appState.subscribe( "plugins.focus-tools.enabled", (enabled) => { console.log("Focus tools enabled:", enabled); },);
await unsubscribe();Always call the returned function when the listener is no longer needed, usually from the plugin’s unload lifecycle.
You can also subscribe to every app-state change:
const unsubscribe = await ctx.appState.subscribe((state) => { console.log("App state changed", state);});Prefer a path subscription when possible so the plugin only processes relevant changes.
Read the complete state
Section titled “Read the complete state”list() returns a cloned snapshot of the complete state:
const snapshot = await ctx.appState.list();replace(nextState) is reserved for internal app operations such as restoring
a validated snapshot. It is not part of the plugin API because replacing the
complete state could overwrite data owned by the host or other plugins.
All plugin app-state calls are asynchronous because they cross the sandbox
boundary. An updater passed to set() reads the latest value and writes its
result in two steps; it is not an atomic transaction.