---
title: "Plugins"
description: "Install and share bundles of skills from a repo, git URL, or local folder."
icon: "box"
hidden: true
---

<Note>
  Plugins are in **beta**. Behavior and configuration may change in future releases.
</Note>

A **plugin** is a bundle of [skills](/cli/extensibility/skills/overview) you can
install from a GitHub repo, a git URL, or a local folder and reuse across
projects. Installing a plugin makes its skills available as
`/<plugin>:<skill>` slash commands, and it can pull in other plugins it depends
on automatically.

A plugin is just a source that contains:

```
my-plugin/
├── .devin-plugin/
│   └── plugin.json     # The plugin manifest
└── skills/
    └── review/
        └── SKILL.md    # An ordinary skill
```

The `skills/` directory holds ordinary skills — plugins introduce no new skill
format. See [Creating Skills](/cli/extensibility/skills/creating-skills) for the
`SKILL.md` format.

---

## Installing a plugin

A plugin source can be a GitHub `owner/repo`, a git URL, or a local path:

```bash
# From GitHub
devin plugins install acme/review-tools

# From any git host
devin plugins install https://gitlab.com/acme/review-tools.git

# From a local folder (great for authoring)
devin plugins install ./my-plugin
```

Before installing, Devin shows what the plugin adds — the skills it provides,
any required plugins that will be auto-installed, and any policy it introduces
(for example, if it forbids other plugins). Pass `-y` / `--yes` to skip the
prompt.

Plugins are installed at the **user** level and are available across all your
projects.

---

## Managing plugins

```bash
# List installed plugins, their versions, and whether any are blocked by policy
devin plugins list

# Show a plugin's skills and its required/optional/forbidden lists
devin plugins info review-tools

# Re-fetch a plugin (or all plugins) at the latest version
devin plugins update review-tools
devin plugins update

# Remove a plugin (auto-installed required plugins are left in place)
devin plugins remove review-tools
```

Local plugins are linked directly to their source folder, so edits are live:
`devin plugins install ./my-plugin` → edit `skills/<name>/SKILL.md` → changes
apply on the next session, no `update` needed.

---

## The manifest

`.devin-plugin/plugin.json` describes the plugin. Only `name` is required, and
it must be unique among installed plugins (it is the `/<name>:…` namespace).

```jsonc
{
  "name": "review-tools",
  "version": "1.0.0",
  "description": "Code-review skills for our team",
  "requiredPlugins": [
    "acme/secure-base",
    { "source": "github", "repo": "acme/audit-logging" }
  ],
  "optionalPlugins": [
    "acme/deploy-tools",
    { "source": "url", "url": "https://gitlab.com/acme/extra.git" }
  ],
  "forbiddenPlugins": ["sketchy-org/bad-plugin", "acme/*", "*"]
}
```

Supported metadata fields: `name`, `version`, `description`, `author`
(`{ name, email }`), `homepage`, `repository`, `license`, and `keywords`.

A dependency entry is a **source** — either a string shorthand or an object:

- `"owner/repo"` → GitHub
- `"https://…"`, `"git@…"`, `"ssh://…"` → git URL
- `{ "source": "github", "repo": "owner/repo" }`
- `{ "source": "url", "url": "https://gitlab.com/team/plugin.git" }`

All GitHub forms for the same repo (`owner/repo`, the HTTPS URL, the `.git`
URL, the SSH form) refer to the same plugin identity.

---

## Dependencies and governance

A plugin can declare three lists, which let a single plugin act as a curated,
governed collection of other plugins.

### `requiredPlugins`

Auto-installed (recursively) when the plugin is installed. If a required plugin
is blocked by policy, the whole install fails — there is no partial install.

### `optionalPlugins`

An **allow-list** of plugins this plugin endorses. They are **not**
auto-installed; the list only matters as a carve-out against a forbidden entry
(see below).

### `forbiddenPlugins`

A **deny-list**. Each entry is one of:

- An exact plugin identity, written as `owner/repo`, a git URL, or a local path.
- A **glob pattern** — any entry containing `*`. The `*` matches any sequence of
  characters, including `/`. Patterns are normalized into canonical-identity
  space first, so `acme/*` becomes `https://github.com/acme/*` (all of `acme`'s
  GitHub repos), `*/secrets` matches a repo named `secrets` under any owner, and
  `https://gitlab.com/acme/*` matches any repo under that path.
- The lone `"*"`, which matches every other plugin (an un-defeatable lockdown).

The policy rules are:

- **Deny wins.** A plugin is blocked if any installed plugin forbids it (via its
  exact identity, a matching glob, or `"*"`).
- **Self-override.** A plugin's own `requiredPlugins`, `optionalPlugins`, and
  itself are exempt from its **own** forbidden list. So
  `forbiddenPlugins: ["*"]` together with `optionalPlugins: [B, C]` means "allow
  myself, B, and C; forbid everything else."
- **No cross-plugin re-permitting.** One plugin's allow-lists cannot re-permit
  what **another** plugin forbids. An installed plugin with
  `forbiddenPlugins: ["*"]` is an un-defeatable lockdown.
- **Default open.** If no installed plugin forbids anything, nothing is blocked.

Policy is enforced at two points:

- **Install time** — installing a blocked plugin (or one whose required plugins
  can't be satisfied, or whose name collides with an installed plugin) is
  refused.
- **Load time** — if a plugin becomes blocked after install (for example, a
  forbidding plugin is installed later), it stays on disk but its skills are
  skipped at session start, with a warning naming the plugin that forbids it.

{/*

---

## Inheritance

Plugins are not loaded from a single place. Devin gathers plugin configuration
from several sources and processes them in a fixed **hierarchical order**, from
highest authority to lowest:

1. **Enterprise-level** configuration (cloud)
2. **Org-level** configuration (cloud)
3. **System-level** configuration (distributed by MDM)
4. **User-level** configuration (a local configuration directory)
5. **Repo-level** configuration (in the app repo / local directory)
6. **Session-level** configuration (ephemeral)

The governing rule across levels is **higher authority wins**: a level lower in
the hierarchy can never *forbid* a plugin that a higher level *requires*, and it
can never *allow* a plugin that a higher level *forbids*. So an org can mandate a
plugin no user, repo, or session can opt out of, and forbid a plugin no lower
level can re-permit.

If a lower level conflicts with a higher one — for example, a user-level plugin
tries to forbid a plugin a higher level requires — Devin refuses to operate
rather than silently resolving the conflict.

### How levels combine

Within each level, the full plugin dependency graph is explored (a plugin's
`requiredPlugins` are loaded recursively, exactly as described above). Deny
lists accumulate as the graph is walked:

- When a **`requiredPlugin`** is loaded, its `forbiddenPlugins` are merged into
  the parent's deny list — a required plugin's restrictions propagate upward and
  apply to everything alongside it.
- An **`optionalPlugin`** only contributes to the **current plugin's** deny
  list. It does **not** change the parent's deny list, so an optional dependency
  cannot impose restrictions on its parent or its siblings.

The same higher-authority-wins rule then applies between levels: if a plugin
lower in the hierarchy (for example, a user-level plugin) attempts to forbid a
plugin required by a higher level (for example, an org-level plugin), Devin
refuses to operate.

*/}
