--- sidebar_position: 1 --- # Create a Plugin MetoYou plugins are browser-safe ES modules loaded by the Angular renderer. A plugin receives a frozen `TojuClientPluginApi`, declares every privileged capability in its manifest, and registers cleanup work through disposables. ## Folder Layout A local desktop plugin is discovered from an immediate child folder under the app data `plugins` directory. ```text my-plugin/ toju-plugin.json main.js README.md icon.svg ``` The manifest file can be named `toju-plugin.json` or `plugin.json`. Entrypoints and readmes must stay inside the plugin folder. ## Minimal Manifest ```json { "schemaVersion": 1, "id": "example.hello-world", "title": "Hello World", "description": "Adds a toolbar action that sends a message.", "version": "1.0.0", "kind": "client", "scope": "client", "apiVersion": "1.0.0", "compatibility": { "minimumTojuVersion": "1.0.0" }, "entrypoint": "./main.js", "capabilities": ["messages.send", "ui.pages"] } ``` ## Entrypoint ```js export function activate(context) { const { api } = context; api.logger.info('Hello World activated'); const disposable = api.ui.registerToolbarAction('hello', { label: 'Hello', run: () => api.messages.send('Hello from my plugin') }); context.subscriptions.push(disposable); } export function ready(context) { context.api.logger.info('All ready plugins have loaded'); } export function deactivate(context) { context.api.logger.info('Hello World deactivated'); } ``` ## Lifecycle Hooks | Hook | When it runs | Use it for | | --- | --- | --- | | `activate(context)` | During explicit plugin activation. | Register UI, subscribe to events, initialize state. | | `ready(context)` | After the load-order pass has activated ready plugins. | Cross-plugin coordination that needs other plugins loaded. | | `deactivate(context)` | During unload or reload. | Flush state and log shutdown. Disposables are also cleaned up by the host. | | `onPluginDataChanged(context, event)` | When plugin data changes are observed. | React to plugin-scoped persistence changes. | | `onServerRequirementsChanged(context, snapshot)` | When server plugin requirements change. | Adapt to required, optional, blocked, or incompatible server plugins. | ## Cleanup Every API registration returns a disposable. Push it into `context.subscriptions`. ```js const subscription = api.messageBus.subscribe({ topic: 'poll:votes', handler: (event) => api.logger.info('vote received', event.payload) }); context.subscriptions.push(subscription); ``` The plugin host disposes subscriptions in reverse order when the plugin unloads. ## Capability Grants A plugin can only call privileged APIs after the matching capability is declared in the manifest and granted by the user. Keep the manifest narrow. For example, a plugin that only adds a settings page does not need message or user management capabilities. ## Testing Locally 1. Create the plugin folder in the desktop plugins directory. 2. Open the Plugin Manager. 3. Register or refresh local plugins. 4. Grant required capabilities. 5. Activate the plugin. 6. Inspect plugin logs in the manager. For broad API examples, compare against the E2E fixture plugin under `toju-app/public/plugins/e2e-all-api/`.