Plugin Lifecycle
Recall owns plugin installation, loading, and activation. A plugin participates in this lifecycle through the entry declared in its manifest. JavaScript plugins export lifecycle functions, while theme plugins provide validated JSON.
Declare the entry
Section titled “Declare the entry”Set recall.entry in the plugin’s package.json:
{ "recall": { "manifestVersion": "0.1", "permissions": ["registry.command", "api.toast"], "entry": { "runtime": "js", "file": "src/index.ts" } }}entry.file is relative to the plugin root. The Plugin Creator builds this
source entry and writes the resulting path into the packaged manifest.json.
Recall then loads the declared built file when the plugin is activated.
See Plugin Schema for all manifest fields and entry-path rules, and Plugin Creator for the build command.
JavaScript plugins
Section titled “JavaScript plugins”A JavaScript entry exports init(ctx) and can optionally export unload(ctx).
Recall runs every enabled JavaScript plugin in a separate headless runtime, so
context methods are asynchronous RPC calls and lifecycle functions should be
async.
import type { RuntimeCtx } from "@jrtilak-recall/runtime";
const cleanups: Array<() => Promise<void>> = [];
export async function init(ctx: RuntimeCtx) { if (!ctx.commands || !ctx.toast) return;
const command = await ctx.commands.register({ id: "focus-tools.start-session", name: "Start focus session", async run() { await ctx.toast?.success({ title: "Focus session started" }); }, });
cleanups.push(() => command.dispose());}
export async function unload(_ctx: RuntimeCtx) { for (const cleanup of cleanups.splice(0).reverse()) { await cleanup(); }}The example requires registry.command and api.toast in
recall.permissions.
Recall calls init after an enabled plugin and its entry are ready to run. The
host currently prevents a successfully initialized plugin id from being
initialized again until it has been unloaded. Plugins should still keep their
own setup and cleanup straightforward instead of relying on repeated init
calls.
Recall calls unload only for a JavaScript plugin that completed init and is
currently loaded. The function is optional, but any plugin that owns resources
with a lifetime beyond init should provide it. The host also force-disposes
tracked registrations when a plugin unloads or its sandbox fails.
Plugins do not render React Native components. Menus, toasts, dialogs, and other supported contributions are data or actions rendered by the host app.
Cleanup
Section titled “Cleanup”Release everything created by the plugin when unload runs. Common examples
include:
- Registry handles returned by commands, menus, sorting methods, or highlight colors.
- Runtime subscriptions and external event listeners.
- Timers, background tasks, and connections started by the plugin.
- Module-level references that should not survive the plugin lifecycle.
If init throws, Recall does not mark the plugin as loaded. The host releases
tracked resources and destroys the failed sandbox. Plugins may still use
try/finally when setup owns resources outside the runtime context.
Disabled plugins
Section titled “Disabled plugins”Disabled plugins are not initialized. When Recall processes a disabled plugin that was previously loaded, it unloads that plugin instead of running it. Activation changes may be applied by the host during its next plugin initialization cycle, so do not depend on plugin code running while the plugin is disabled.
Theme plugins
Section titled “Theme plugins”Theme entries use runtime: "theme", point to a theme JSON file, and request
the registry.theme permission:
{ "recall": { "manifestVersion": "0.1", "permissions": ["registry.theme"], "entry": { "runtime": "theme", "file": "src/theme.json" } }}Theme plugins do not export init or unload. Recall reads the entry, validates
it against the theme schema, and contributes its themes to the host. See the
theme plugin contract for the
file format and schema URL.
Validation and failures
Section titled “Validation and failures”Recall rejects plugin metadata and entry files that fail the applicable validation. A malformed manifest or theme, a missing or unsafe entry path, or manifest name and version values that do not match the installed plugin can prevent activation.
Errors thrown while loading the JavaScript module or running init also fail
that plugin’s initialization. Other plugins can continue initializing, so each
plugin should throw actionable errors rather than hiding a failed setup.