# How to Create and Configure Plugins in Jido: The Complete Developer Guide

> Learn to create and configure Jido plugins. Define modules with metadata and callbacks, then declare them in agents for composable runtime behavior and state key derivation.

- Repository: [agentjido/jido](https://github.com/agentjido/jido)
- Tags: how-to-guide
- Published: 2026-03-09

---

**You create and configure plugins in Jido by defining a module that `use Jido.Plugin` with compile-time metadata and callbacks, then declaring them in an agent's `plugins:` list where per-instance configuration overrides and automatic state key derivation enable composable runtime behavior.**

Jido is an open-source Elixir framework for building agent-based systems that treat plugins as pure, composable capabilities. To create and configure plugins in Jido effectively, you must understand the separation between compile-time plugin definitions and their runtime instantiation within agents. This guide explains the complete plugin lifecycle—from code generation to signal routing—based on the actual source implementation in the `agentjido/jido` repository.

## Understanding Jido Plugin Architecture

The Jido plugin system operates through three distinct runtime stages, each managed by specific modules in `lib/jido/plugin/`.

### The Plugin Definition Stage

At compile time, a plugin module invokes `use Jido.Plugin` which expands the `__using__/1` macro defined in [`lib/jido/plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin.ex). This macro validates options against `@plugin_config_schema` and generates accessor functions (e.g., `name/0`, `state_key/0`, `actions/0`). 

The macro produces two key data structures:

- **Spec** (`Jido.Plugin.Spec`): A per-agent runtime view containing the resolved configuration map, defined in [`lib/jido/plugin/spec.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/spec.ex).
- **Manifest** (`Jido.Plugin.Manifest`): Compile-time metadata including capabilities, requirements, and signal routes used by discovery tools, defined in [`lib/jido/plugin/manifest.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/manifest.ex).

The macro also supplies default implementations for callbacks like `mount/2`, `handle_signal/2`, and `child_spec/1`, which developers can override for custom behavior.

### Runtime Instance Resolution

When an agent starts, each plugin declaration transforms into a `Jido.Plugin.Instance` struct via the logic in [`lib/jido/plugin/instance.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/instance.ex). This module normalizes various declaration formats—whether `MyModule`, `{MyModule, %{config: true}}`, or `{MyModule, as: :alias}`—into a standardized struct.

The Instance module derives unique identifiers for each plugin attachment:
- **`state_key`**: Determines where the plugin's state slice lives within the agent's global state map.
- **`route_prefix`**: Prepends to signal types for routing (e.g., `"support.chat.send"`).

If a plugin declares `singleton?: true`, the Instance module enforces that it cannot be aliased multiple times within the same agent.

### Configuration Merging and Validation

Configuration resolution occurs in [`lib/jido/plugin/config.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/config.ex), which merges three sources in order of precedence:

1. **Application environment** values retrieved via `Application.get_env/3` using the plugin's `otp_app`.
2. **Per-agent overrides** supplied directly in the agent's `plugins:` declaration.
3. **Default values** defined in the plugin's optional `config_schema`.

The merged configuration validates through Zoi schemas, returning `{:ok, config}` or `{:error, errors}` depending on validation results.

## Creating a Jido Plugin

### Generating the Plugin Scaffold

While manual creation is possible, use the Mix generator to scaffold boilerplate:

```bash
mix jido.gen.plugin MyApp.ChatPlugin --signals="chat.*,message.*"

```

This creates [`lib/my_app/chat_plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/my_app/chat_plugin.ex) and corresponding test files via [`lib/mix/tasks/jido.gen.plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/mix/tasks/jido.gen.plugin.ex), pre-populating the module structure with required callbacks.

### Defining Plugin Metadata and Schema

Every plugin requires compile-time metadata that declares its capabilities. In [`lib/jido/plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin.ex), the macro consumes these options to generate manifests and specs:

```elixir
defmodule MyApp.ChatPlugin do
  use Jido.Plugin,
    name: "chat",
    state_key: :chat,
    actions: [MyApp.Actions.SendMessage, MyApp.Actions.ListHistory],
    schema: Zoi.object(%{
      messages: Zoi.list(Zoi.any()) |> Zoi.default([]),
      model: Zoi.string() |> Zoi.default("gpt-4")
    }),
    signal_patterns: ["chat.*"],
    signal_routes: [
      {"chat.send", MyApp.Actions.SendMessage},
      {"chat.history", MyApp.Actions.ListHistory}
    ],
    config_schema: Zoi.object(%{
      api_key: Zoi.string(),
      model: Zoi.string() |> Zoi.default("gpt-4")
    })

```

The `config_schema` option is optional but recommended—it enables automatic validation of configuration maps passed during agent initialization.

### Implementing the Mount Callback

The `mount/2` callback in [`lib/jido/plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin.ex) initializes plugin-specific state. This callback runs purely without side effects during agent initialization:

```elixir
  @impl Jido.Plugin
  def mount(_agent, config) do
    # Return initial state merged with defaults from the schema option

    {:ok, %{
      initialized_at: DateTime.utc_now(),
      api_key: config[:api_key],
      model: config[:model]
    }}
  end
end

```

Defaults defined in the `schema` option automatically merge with the returned map, creating the final state slice stored under the plugin's `state_key`.

## Configuring Plugins in Agents

Agents declare plugins through the `use Jido.Agent` macro, which processes the `plugins:` option during compilation and passes declarations to `Jido.Agent.new/1` at runtime.

### Basic Plugin Declaration

Include a plugin using its module name to accept default configuration from the application environment:

```elixir
defmodule MyApp.ChatAgent do
  use Jido.Agent,
    name: "chat_agent",
    plugins: [
      MyApp.ChatPlugin
    ]
end

```

### Per-Agent Configuration Overrides

Override configuration values for a specific agent by passing a configuration map:

```elixir
plugins: [
  {MyApp.ChatPlugin, %{model: "claude-3", api_key: "sk-override"}}
]

```

The `Jido.Plugin.Config` module merges these overrides on top of application environment values, validating the final map against `config_schema` if defined.

### Working with Aliased Instances

Create multiple isolated instances of the same plugin using the `as:` option, which generates derived state keys and route prefixes:

```elixir
plugins: [
  {MyApp.ChatPlugin, as: :support, api_key: "support-token"},
  {MyApp.ChatPlugin, as: :sales, api_key: "sales-token"}
]

```

This produces the following mappings in `Jido.Plugin.Instance`:

| Declaration | `state_key` | `route_prefix` |
|-------------|-------------|----------------|
| `MyApp.ChatPlugin` | `:chat` | `"chat"` |
| `{MyApp.ChatPlugin, as: :support}` | `:chat_support` | `"support.chat"` |
| `{MyApp.ChatPlugin, as: :sales}` | `:chat_sales` | `"sales.chat"` |

Signals prefixed with `"support.chat"` route to the support instance's actions, while `"sales.chat"` routes to the sales instance, enabling complete runtime isolation within a single agent.

## Runtime Execution and Signal Routing

When `Jido.AgentServer` starts (defined in [`lib/jido/agent_server.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent_server.ex)), it processes each plugin instance:

1. Calls `mount/2` to initialize the state slice under the derived `state_key`.
2. Starts any child processes defined by the plugin's `child_spec/1` callback.
3. Registers signal bus subscriptions based on `signal_patterns` or `signal_routes`.
4. Routes incoming signals to the appropriate action module according to the manifest.

Dispatch signals to specific plugin instances using the route prefix:

```elixir

# Routes to the default instance

Jido.AgentServer.cast(agent_pid, %Jido.Signal{
  type: "chat.send",
  payload: %{content: "Hello"}
})

# Routes to the aliased support instance

Jido.AgentServer.cast(agent_pid, %Jido.Signal{
  type: "support.chat.send", 
  payload: %{content: "Help needed"}
})

```

The `handle_signal/2` callback (defaulting to `{:ok, nil}` in [`lib/jido/plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin.ex)) can be overridden to intercept signals for authentication, logging, or action substitution before routing occurs.

## Summary

- **Plugin definition** occurs through `use Jido.Plugin` in [`lib/jido/plugin.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin.ex), which generates a **Spec** for runtime data and a **Manifest** for compile-time metadata.
- **Configuration** merges application environment, per-agent overrides, and defaults through `Jido.Plugin.Config` in [`lib/jido/plugin/config.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/config.ex), with optional Zoi schema validation.
- **Runtime instantiation** creates `Jido.Plugin.Instance` structs in [`lib/jido/plugin/instance.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/instance.ex), deriving unique `state_key` and `route_prefix` values that enable multiple plugin instances within one agent.
- **Signal routing** uses route prefixes to direct messages to specific plugin instances, while the `mount/2` callback initializes pure state slices merged into the agent's global state.

## Frequently Asked Questions

### What is the difference between a Plugin Spec and a Manifest?

According to the source code in [`lib/jido/plugin/spec.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/spec.ex) and [`lib/jido/plugin/manifest.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/manifest.ex), the **Manifest** contains static, compile-time metadata about capabilities, requirements, schedules, and signal routes used for discovery and tooling. The **Spec** represents the runtime view of an attached plugin within a specific agent, holding the resolved configuration, actual state key, and current signal patterns. While the manifest exists once per plugin module, each agent creates a unique spec during initialization.

### How do I override plugin configuration for a specific agent?

Pass a configuration map as the second element of a tuple when declaring the plugin in your agent's `use Jido.Agent` block: `{MyPlugin, %{key: "value"}}`. The `Jido.Plugin.Config` module merges this override on top of the application environment configuration and schema defaults, validating the final result before the agent starts. Invalid configurations return errors during `Jido.Agent.new/1` initialization.

### Can I use the same plugin module multiple times in one agent?

Yes, by using the `as:` option to create aliased instances: `{MyPlugin, as: :alias_name}`. The `Jido.Plugin.Instance` module in [`lib/jido/plugin/instance.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/instance.ex) automatically derives unique `state_key` values (e.g., `:myplugin_alias_name`) and route prefixes (e.g., `"alias_name.myplugin"`) for each alias. However, if the plugin declares `singleton?: true`, the Instance module enforces that only one instance—without aliasing—is permitted per agent.

### What happens if I don't provide a config_schema?

If you omit the `config_schema` option in `use Jido.Plugin`, the configuration system in [`lib/jido/plugin/config.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/plugin/config.ex) skips schema validation while still merging application environment values with per-agent overrides. Without a schema, no defaults are automatically applied, and the plugin receives whatever configuration map results from the merge process. While functional, lacking a config_schema removes compile-time guarantees and runtime validation for configuration parameters.