# Adding a Plugin

Adding a plugin is smaller than it sounds.

Most of the time, it is one ordinary Dart service plus a thin class that gives
the runtime a name for the feature.

Here is the whole move up front:

```dart
extractRegion(pluginLifecycleSource, 'session-plugin-basic')
```

That already buys you three things:

- the feature has a runtime identity
- the service lives inside an isolated session
- the runtime can later enable, disable, replace, or configure it

If your app has anything that looks like "open the thing", "close the thing",
"have more than one open at once", or "let the user choose which features
apply", that shell starts paying rent quickly.

## Unpack the example

The notification feature is a good first shape because it is boring on purpose.
No clever architecture yet. Just one service, one plugin, one session.

Start with the boring Dart class first:

```dart
extractRegion(pluginServicesSource, 'adding-plugin-notification-service')
```

That is intentional. Plugin Kit does not require every useful class to inherit
from something. A service can stay ordinary until it needs settings,
identity, or lifecycle.

## Wrap the feature in a plugin

Now give the feature an identity and register the service.

```dart
extractRegion(pluginServicesSource, 'adding-plugin-notification-plugin')
```

That is already a real plugin.

The important pieces are:

- `pluginId` is the runtime identity for the feature
- `register(...)` contributes services to the session registry
- `serviceId` is the slot other code resolves later
- `ScopedServiceRegistry` already knows which plugin is registering

## Pick the scope deliberately

Default to `SessionPlugin`.

A session is one isolated unit of work: one document, one chat, one workspace,
one tenant, one sandbox. If your app opens more than one of the thing, the
plugin belongs in the session scope.

Use `GlobalPlugin` when there should be exactly one of something for the whole
runtime:

- telemetry
- app-wide settings
- a shared auth client
- a coordinator that watches all sessions
**Default to session scope when unsure:** Session scope prevents accidental state sharing. Promote to a global plugin
once shared behavior is obvious.

## Run it

Add the plugin to a runtime, create a session, and resolve the service.

```dart
extractRegion(pluginServicesSource, 'adding-plugin-run-notification')
```

You now have:

- a feature with a runtime identity
- a service registered in a named slot
- a session that owns an isolated registry
- a runtime that controls lifecycle and cleanup

That is the core move. Everything else in this folder is a refinement of that
shape.

## Reacting when the session is alive

`register(...)` says what the plugin provides. Once a session opens, your
code may need to:

- subscribe to events
- answer typed requests
- start background work
- resolve services registered by other plugins

The idiomatic home for that logic is a `StatefulPluginService`. Its
`attach()` runs with the session context already bound, and inside it
you can reach `on`, `onRequest`, `onRequestSync`, `bind`, and `emit`
on the service. The four subscription helpers auto-track their handles
and the framework cancels them after `detach()` returns, so you never
have to remember the cleanup step.

Here is the notification feature extended to react when a long-running
task completes:

```dart
extractRegion(pluginServicesSource, 'adding-plugin-notification-reactive')
```

The plugin class itself did not override `attach(...)`. When the session
starts, the framework walks every *winning* `StatefulPluginService` the plugin
registered and attaches each one before invoking the plugin's own
`attach(...)`. The watcher subscribes to `TaskCompleted` from inside its
own `attach()`, and the framework cancels that subscription when the
service detaches.

You can still override the plugin's own `attach(...)` when you actually
need it: resolving a service from a different plugin at startup,
coordinating across plugins, or starting a shared resource. `Plugin.attach`
and `Plugin.detach` are pure user hooks; the framework runs orchestration
(stateful service attach/detach, subscription cleanup) around them.
**Resolve peers in attach, not register:** `register(...)` is for contributing your own services. By the time
`attach(...)` runs, the runtime has collected the enabled plugins for that
scope, built the context, and applied settings. That is the right moment to
resolve other plugins' services.

## Keep the plugin class thin

A plugin class should mostly be wiring:

- `pluginId`
- `register(...)`
- `attach(...)`
- `detach(...)`

When the class starts holding real behavior, move that behavior out.

| If the feature needs... | Put it in... |
|---|---|
| plain reusable behavior | an ordinary Dart class |
| typed settings and registry identity | `PluginService` |
| session lifecycle, subscriptions, timers, or mutable session state | `StatefulPluginService` |
| shared domain data used by many plugins | a custom context |

This is the difference between a plugin that stays readable and a plugin that
slowly turns into a second application hidden inside one class.

## Add settings when someone needs control

If your service needs runtime configuration, promote it to `PluginService`
and read from `config`.

```dart
extractRegion(pluginServicesSource, 'plugin-service-settings-inject')
```

Settings target the plugin and service slot:

```dart
extractRegion(runtimeSettingsSource, 'runtime-settings-construct')
```

`PluginId.service(...)` returns a `Pin` for the slot owned by that plugin.
For runtime construction without the chain, use `Pin('my_notifier',
['notification_service'])` (or `Pin.wildcard(['notification_service'])` for
the wildcard form).

You do not need to hand-parse a map inside your app code. The registry
injects the effective settings when the service is resolved.

### Who is "someone"

Settings are user-facing by default. That is the headline use: let the
person using your app decide how a feature behaves, through a settings
screen or a config file they edit.

But nothing in the contract says settings have to come from an end user.
The host app can inject them too, and that is completely fine. Plugin Kit
treats both the same way.

### User-facing example: API keys

The canonical user-driven case is a service that needs a secret only the
user can supply. Suppose your app has a chat plugin that talks to an
external API, and that API wants a key:

```dart
extractRegion(pluginServicesSource, 'adding-plugin-chat-backend-service')
```

A settings UI (more on capabilities in a minute) can read the expected
shape, render a text field, and push the new value back into
`RuntimeSettings.services`. The plugin never has to know whether that
field came from a keychain, a dotfile, or a dialog the user just closed.

### Developer-facing example: one plugin, many apps

Since settings are just a `Map`, the host app developer can inject them
directly. If you maintain a shelf of plugins shared across several apps,
this is how you flip the same plugin into different modes without
forking plugin code.

```dart
extractRegion(namingSource, 'naming-settings-keys')
```

Same plugin code, two runtimes, two behaviors, zero `if (appName == 'A')`
branches buried inside the plugin. The user-facing and developer-facing
cases can also mix: some config keys come from the app developer, others
from the end user, and they all land in the same `config` node on the
service.

## Make it discoverable without instantiating it

Sometimes the host app needs to know things about your service before
deciding whether to touch it. What file formats does it support? Is it slow?
Does it belong in the "experimental" pane of a settings screen? Does the
CEO insist it appear first?

Capabilities are the answer. A `Capability` is an empty abstract base
class. You subclass it with whatever fields you want, attach instances
during registration, and the host reads them back through the registry
without instantiating the service.

```dart
extractRegion(capabilitiesSource, 'capability-register-multiple')
```

And on the reading side:

```dart
extractRegion(capabilitiesSource, 'capability-resolve-raw-wrapper')
```

`resolveCapability` returns the capability instance, or `null` when the
slot is unregistered, every registration is disabled, or the current
winner does not carry that capability type. The same call serves as a
presence check and as access to capability fields like
`SupportsFileFormats.extensions`.

The runtime does not know what `SupportsFileFormats` or `IsSlowCapability`
mean. It just holds them. Defining a schema for your settings UI, tagging
slots for routing, marking which plugins the CEO loves: that is all up to
you. See [Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/) for the full reader-side
story.

## A useful authoring checklist

1. **Name the feature.**

   Pick a short, stable `pluginId`. Lowercase and underscores age well:
   `notification`, `sql_language`, `markdown_preview`.

2. **Pick the scope.**

   Use `SessionPlugin` for document/chat/workspace behavior. Use
   `GlobalPlugin` for truly shared runtime behavior.

3. **Write the service first.**

   Keep it as a normal Dart class unless it needs settings or lifecycle.

4. **Register one clear slot.**

   Prefer descriptive service ids like `notification_service` over vague
   names like `service`.

5. **Put reactions in a service, not the plugin class.**

   Event subscriptions, typed request handlers, timers, and background
   tasks belong in a `StatefulPluginService`'s `attach(...)`, where
   subscriptions are tracked for you. Keep the plugin's own `attach(...)`
   for plugin-wide wiring like peer resolution.

6. **Promote only when needed.**

   Reach for `PluginService`, `StatefulPluginService`, capabilities, and
   custom contexts when the feature earns them.

## Where to go next

[Plugin Services](https://plugin-kit.saad-ardati.dev/concepts/plugin-services/)
  [Configuration](https://plugin-kit.saad-ardati.dev/concepts/configuration/)
  [Settings & Overrides](https://plugin-kit.saad-ardati.dev/guides/settings/)
  [Custom Contexts](https://plugin-kit.saad-ardati.dev/concepts/custom-context/)