--- title: Plugin reference description: Local plugin files, client and server runtimes, platform limits, contributions, RPCs, lifecycle, hosts, and CLI commands. nav: Reference order: 48 category: Plugins --- # Plugin reference Start with the [plugin quickstart](/docs/plugins) to create your first plugin. Migrating an existing plugin? Follow the standalone [runtime-entry migration guide](/docs/plugins/migration). Local plugins are directory sources installed into one Paseo daemon. A plugin can contribute: - React Native screens and sidebar header and footer items to Paseo clients; - workspace and agent panels opened as workspace tabs; - global, workspace, and agent actions in the Command Center; - slash commands in the message composer; - transformed and daemon-pushed agent timeline rows; - light and dark themes in Settings → Appearance; - schema-validated RPC handlers running beside the daemon; - normal Paseo operations through the TypeScript SDK; - searchable external resources in the message composer. Plugin code is trusted and unsandboxed. Client surfaces run in the Paseo app. Backend contributions run in a subprocess with access to the daemon machine, including its files, processes, credentials, and network. ## Project files `paseo plugin init /absolute/path/to/my-plugin` creates: ```text my-plugin/ paseo-plugin.json index.client.tsx index.server.ts client/greeting.tsx server/greeting.ts shared/greeting.ts package.json tsconfig.json ``` The required root manifest is `paseo-plugin.json`: ```json { "id": "my-plugin", "description": "Reviews changes before merge", "requirements": { "paseo": ">=0.8.0" } } ``` | Field | Required | Behavior | | -------------- | -------- | ----------------------------------------------------------------------------------------- | | `id` | Yes | Default installation ID. | | `name` | No | Non-empty display name for the registry and website; independent of the installation ID. | | `icon` | No | Relative path to a PNG inside the plugin package. | | `media` | No | Array of image or video paths inside the package, or HTTPS URLs. An empty array is valid. | | `description` | No | Non-empty summary shown below the plugin ID in **Settings → Plugins**. | | `translations` | No | Listing `name` and `description` in other languages, keyed by language tag. | | `requirements` | No | Supported Paseo versions, described below. | | `build` | No | Preparation commands, described in the CLI reference. | Paths are relative to `paseo-plugin.json`, use forward slashes, and cannot contain `..` segments. Registry listings for visual plugins require an interface image even though `media` is optional in the runtime manifest. See [icons and screenshots](/docs/plugins/publishing#icons-and-screenshots) for accepted formats and listing requirements. Paseo validates the references without opening local assets or fetching URLs. Unknown top-level fields are ignored. Known fields still validate, and unknown keys inside `requirements` are rejected so a misspelled constraint cannot silently skip a compatibility check. Manifests using `name`, `icon`, or `media` require Paseo 0.11.0 or later; older daemons reject these fields during installation. `translations` keys are canonical language tags other than `en`, such as `zh-CN`. Each value may set `name`, `description`, or both, as non-empty strings; other keys are rejected. See [other languages](/docs/plugins/publishing#other-languages) for how listings use them. ### Requirements `requirements` is an optional object. Its currently supported key, `paseo`, accepts an npm semver range. An omitted `requirements.paseo` means `<0.8.0`: the plugin predates the first breaking plugin release. Paseo 0.8 and later reject it with a link to the [migration guide](migration). Empty strings, invalid ranges, and unknown manifest requirement keys are rejected. | Range | Compatible releases | | ---------------- | ---------------------------------------------------------------------------- | | `>=0.8.0` | 0.8.0 and later releases, including prereleases and future breaking releases | | `^0.8.0` | 0.8.x releases, including prereleases | | `>=0.8.3 <0.9.0` | 0.8.3 through the last 0.8 patch, including prereleases | Prerelease Paseo versions also satisfy a range their stable core (`major.minor.patch`) satisfies, so `0.8.0-beta.1` satisfies `>=0.8.0` but not `<0.8.0`. A prerelease minimum such as `>=0.12.0-beta.4` therefore also admits `0.12.0-beta.3` through its stable core `0.12.0`. Use a different stable core when you need to exclude earlier builds of that core. `paseo plugin init` writes `>=` followed by the current CLI version and pins the matching SDK for typechecking. Raise the minimum when adopting a newer API. Add an upper bound when a later release is incompatible; a minimum alone does not promise protection from future breaking changes. The daemon checks its version before installing, running preparation commands, or loading a plugin, and checks again on startup, enable, and reload. A rejected update keeps the installed revision. Each connected app checks its own version before evaluating client code and shows incompatibility in Settings → Plugins. A compatible daemon does not make an older app compatible. A plugin with no client entry does not require the connected app to match. For example: `Plugin "review" requires Paseo >=0.8.0. Your daemon is 0.7.2.` Use a compatible plugin revision or update the named runtime. Releases before 0.8 do not understand this manifest field and cannot show this new diagnostic. ### Runtime entries | Entry | Runtime | Receives | Required | | ------------------ | --------------------- | --------------------- | ------------------------------------------------------------------------------- | | `index.client.tsx` | Paseo app, per client | `PluginClientContext` | When the plugin has any UI, callback, theme, or attachment source | | `index.server.ts` | Daemon subprocess | `PluginServerContext` | When the plugin contributes handlers, hooks, settings persistence, or providers | At least one entry is required; both accept `.ts` or `.tsx`. A directory that still has only the old `index.ts` fails to load and points at the [migration guide](/docs/plugins/migration). Plugin, screen, sidebar-item, workspace-panel, Command Center item, attachment-source, and slash-command IDs start with a lowercase letter and contain lowercase letters, numbers, or hyphens. The generated `package.json` installs `@getpaseo/plugin` and the other host modules as development dependencies for local typechecking and tests. Paseo supplies their runtime instances. Consumers do not install them when adding the plugin. Every other module lives in one of three directories. Nesting inside them is fine; a module at the plugin root is a compile error. | Directory | Compiled into | Use it for | | --------- | ------------------ | -------------------------------------------------------------------- | | `client/` | App bundle only | React, React Native, hooks, styles, surfaces, panels, and callbacks. | | `server/` | Daemon bundle only | Node APIs, local resources, credentials, and RPC handlers. | | `shared/` | Both | Zod RPC contracts and plain values imported by both runtimes. | ## Runtime translations {#runtime-translations} **Unreleased API.** Set `requirements.paseo` to the release that introduces these APIs before publishing your plugin. The [translation example](https://github.com/getpaseo/paseo/tree/main/plugin-examples/translations) uses `>=0.12.1` provisionally; the introducing release has not been assigned. Ship JSON resources with your client bundle. English (`en`) is required; other keys use canonical language tags such as `zh-CN`. Resources contain nested objects with string leaves. Other languages may omit keys, which fall back to English. Unknown keys, malformed resources, noncanonical locale tags, and mismatched interpolation placeholders are rejected when defining translations. ```ts // client/messages.ts import { defineTranslations } from "@getpaseo/plugin/client"; import en from "./locales/en.json"; import zhCN from "./locales/zh-CN.json"; export const messages = defineTranslations({ resources: { en, "zh-CN": zhCN } }); ``` Enable `resolveJsonModule` in your plugin's `tsconfig.json`. Paseo bundles these imports locally; no network loader runs. For example, the resource pair can contain: `client/locales/en.json`: ```json { "title": "Workspace notes", "add": "Add note", "notes_one": "{{count}} note", "notes_other": "{{count}} notes" } ``` `client/locales/zh-CN.json`: ```json { "title": "工作区笔记", "add": "添加笔记", "notes_other": "{{count}} 条笔记" } ``` Call the plugin hook in React UI: ```tsx import { useTranslation } from "@getpaseo/plugin/client"; import { Text } from "react-native"; import { messages } from "./messages"; function NoteCount({ count }: { count: number }) { const { t, locale } = useTranslation(messages); return {t("notes", { count })}; } ``` Use `messages.message(key, options?)` for persistent host-rendered labels: ```tsx client.addScreen({ id: "notes", title: messages.message("title"), Component: Notes, }); ``` `PluginText` is `string | PluginMessage`. Message references work in screen titles (including title functions), settings and sidebar titles, panel titles, Command Center titles, slash-command descriptions and argument hints, button titles and labels, nested menu titles, attachment-source titles/picker titles/search placeholders, and theme names. Existing literal strings remain valid. Keep IDs, slash-command names, icons, URLs, and command search keywords stable. Descriptor types keep their existing string defaults. When explicitly annotating translated descriptors, use the text parameter, for example `PluginScreenContribution`. Inline registrations infer this automatically. For transient text in callbacks, call `client.i18n.t(messages, "add")`; it reads the app's current language at invocation. `client.i18n.locale` and the hook's `locale` report the resolved app language even when a message falls back to English. Do not store the result of `t` as a persistent registration label. Keys come from English. Plural base keys such as `notes` require a numeric `count`; i18next selects the locale's `_zero`, `_one`, `_two`, `_few`, `_many`, or `_other` form. Use `{{name}}` for named interpolation and pass `{ name: value }`. JSON string values are widened by TypeScript, so interpolation names from imported JSON are checked at runtime; inline literal resources also infer required interpolation names. i18next formatting syntax such as `{{amount, number}}` uses the resolved language's `Intl` formatter. An explicit app language wins over the device language; System follows the app's supported-locale resolution. Separate connected apps have independent languages. Changing language updates mounted UI and host labels without reloading the plugin, resetting component state, or replacing its RPC/query cache. Modal and popover content receives the same installation context on web and native. Return language-neutral structured RPC data and translate its presentation in the client. Keep user drafts, agent output, raw daemon errors, logs, and terminal text unchanged. Manifest listing translations and `OVERVIEW..md` are separate publication metadata; see [your listing page](publishing#your-listing-page). ## Runtime modules Paseo builds each bundle from its matching entry. An import from `client/` into the daemon bundle, from `server/` into the app bundle, or of a Node module anywhere in the app bundle is a compile error. Server imports of React, React Native, or client SDK entries also fail. Shared code imports only shared code: no Node, React, runtime-specific SDK entries, or runtime-specific types. The SDK root (`@getpaseo/plugin`) contains shared data, schemas, and runtime-neutral helpers only. Import client contexts and hooks from `/client`, server contexts and lifecycle contracts from `/server`, and UI from `/client/react-native` or `/client/ui`. These rules include type imports and transitive dependencies. `/client/host` is private to the app host; plugins cannot import it. ### Client runtime Paseo provides these modules to client code: | Module | Use it for | | -------------------------------------- | ------------------------------------------------------------------------------------------------- | | `@getpaseo/plugin` | Shared data, `defineRpc`, `defineSettings`, `defineAttachmentSource`, `RpcInput`, and `RpcOutput` | | `@getpaseo/plugin/client/ui` | Named, composable settings components | | `@getpaseo/plugin/client/react-native` | Paseo UI components and UI hooks | | `@getpaseo/plugin/client` | Client contribution contexts, `usePaseo`, `useRpc`, `useSettings`, and data hooks | | `@tanstack/react-query` | Request state and caching | | `react` | Components and hooks | | `react/jsx-runtime` | Compiled JSX | | `react-native` | Cross-platform UI | | `zod` | Shared schemas | The host owns its paired React and renderer versions. The SDK's React peer range permits patch versions for tooling and Node consumers; it does not change the app's pinned React version or guarantee compatibility with another host's renderer. These exact module specifiers use the host's runtime instances. A client bundle that requests another host module fails with `Module "" is not available in plugin client code`. Do not import `lucide-react-native`, `react-native-svg`, or DOM libraries. Set contribution `icon` fields to a [Lucide icon name](https://lucide.dev/icons/); Paseo validates the name and renders the icon. ### Cross-platform rules Client code runs on iOS, Android, and in browsers through React Native Web. A component that works in your browser and crashes on a phone is the most common plugin bug. The rules: | Do | Do not | | -------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `View`, `Text`, `Pressable`, `ScrollView`, `TextInput` from `react-native` | `
`, ``, `