---
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 View plugins menu 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', {
    icon: 'HI',
    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');
}
```

`registerToolbarAction()` adds an action tile to the server side panel's View plugins menu. Use `icon` for the tile badge and keep the `label` short enough to scan in a grid.

## 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/`.
