# Policies

Policies decide whether OpenCode may perform an action on a named resource. They are
authored under `policies` and can also be delivered by a connected
[OpenCode Console](/console) workspace.

Policies are separate from [permissions](/permissions). Permissions are per-agent tool
rules that can `allow`, `deny`, or `ask`. Policies are binary, never prompt, and only
ever tighten what permissions and providers would otherwise allow.

## Configure

Deny one provider everywhere by adding a statement to `opencode.jsonc`:

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "policies": [{ "action": "provider.use", "resource": "openai", "effect": "deny" }],
}
```

A denied provider disappears from the catalog and model selection even when it has
valid credentials.

Earlier releases read policies from `experimental.policies`. That key still works and
merges into `policies`:

```jsonc
{
  "experimental": {
    "policies": [{ "action": "provider.use", "resource": "openai", "effect": "deny" }],
  },
}
```

## Statements

Each statement has three fields:

| Field      | Values                                                                                                                      | Meaning                                              |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `action`   | `provider.use`, `tool.use`, `integration.connect`, `integration.use`, `mcp.use`, `skill.use`, `plugin.use`, `websearch.use` | The operation being controlled                       |
| `resource` | string or wildcard pattern                                                                                                  | What the statement applies to; depends on the action |
| `effect`   | `allow`, `deny`                                                                                                             | The decision when this statement matches             |

Statements that fail validation are dropped with a warning in the server log and
the rest of the list still applies. Check the log after editing a `deny`.

## Matching

Resources use the same whole-value wildcards as permissions: `*` matches zero or
more characters and `?` matches one.

```jsonc
{
  "policies": [{ "action": "provider.use", "resource": "company-*", "effect": "deny" }],
}
```

This denies `company-us` and `company-eu`. A pattern ending in ` *` also matches the
value without arguments, so `shell:git push *` covers `git push` on its own.

## Order

When several statements match, the last one wins. There is no specificity rule, so
put broad statements first and exceptions after them. If nothing matches, the
action is allowed.

```jsonc
{
  "policies": [
    { "action": "provider.use", "resource": "*", "effect": "deny" },
    { "action": "provider.use", "resource": "anthropic", "effect": "allow" },
  ],
}
```

Only Anthropic remains available.

## Providers

`provider.use` controls whether a provider is usable at all. The resource is the
provider ID: `anthropic`, `openai`, `opencode`, or a custom ID from
[`providers`](/providers).

```jsonc
{
  "providers": {
    "company-ai": {
      "package": "@opencode/ai/providers/openai-compatible",
      "settings": { "baseURL": "https://ai.company.example/v1" },
    },
  },
  "policies": [
    { "action": "provider.use", "resource": "*", "effect": "deny" },
    { "action": "provider.use", "resource": "company-ai", "effect": "allow" },
  ],
}
```

The `providers` entry configures the endpoint; the statements make it the only
provider OpenCode will use. Policy applies however a provider became known: catalog
data, environment variables, saved accounts, built-in plugins, or configuration.

Use `provider.use` instead of the V1 `enabled_providers` and `disabled_providers`
lists. V1 files still load; see
[Migrate from V1](/migrate-v1#supported-fields-without-direct-native-equivalents).

## Tools

`tool.use` statements hard-deny a [permission](/permissions) check. The resource is
`<action>:<value>`, matched against every resource the tool checks.

```jsonc
{
  "policies": [
    { "action": "tool.use", "resource": "shell:git push *", "effect": "deny" },
    { "action": "tool.use", "resource": "edit:*.env", "effect": "deny" },
    { "action": "tool.use", "resource": "webfetch:*", "effect": "deny" },
  ],
}
```

A denied check fails with `Blocked by configuration policy` instead of prompting. It
applies after agent rules and saved approvals, so it overrides an `ask` and an
"Allow always" approval alike.

| Pattern                      | Blocks                                      |
| ---------------------------- | ------------------------------------------- |
| `shell:*`                    | Every shell command                         |
| `read:*/.ssh/*`              | Reading SSH material                        |
| `external_directory:*`       | Any access outside the Location or worktree |
| `subagent:general`           | Launching one subagent                      |
| `github_delete_repository:*` | One MCP tool                                |
| `*`                          | Everything not already denied               |

A `tool.use` statement with `allow` never grants access. It only lifts an earlier
broader `deny`, after which the agent's own rules decide.

```jsonc
{
  "policies": [
    { "action": "tool.use", "resource": "shell:*", "effect": "deny" },
    { "action": "tool.use", "resource": "shell:git status *", "effect": "allow" },
  ],
}
```

`git status` falls back to the agent's `shell` rule; every other command is blocked.

## Integrations

An integration is anything you connect in `/connect`: the OpenCode Console
(`opencode`), a provider such as `anthropic`, a web search service such as `exa`,
or a well-known config origin such as `https://ai.acme.com`. Signing in to an MCP
server is not affected by integration policies; use `mcp.use` to control MCP
servers.

`integration.connect` controls which integrations `/connect` lists and which can be
connected. The resource is the integration ID.

`integration.use` controls everything an integration supplies: its providers and
models, its web search, and the MCP servers and config a Console workspace or
well-known origin delivers. The resource is `<integration>#<account>`, where the
account is the connected organization or account ID, such as
`opencode#org_01M3CAP8H43Z49CER6NT0Y3ZF1`. Without a connected account it is the
bare integration ID: the signed-out free tier is `opencode`, an `ANTHROPIC_API_KEY`
environment variable is `anthropic`, and a provider from `opencode.json` is its
provider ID.

This MDM policy requires one Console workspace and nothing else:

```jsonc
{
  "policies": [
    { "action": "integration.connect", "resource": "*", "effect": "deny" },
    { "action": "integration.connect", "resource": "opencode", "effect": "allow" },
    { "action": "integration.use", "resource": "*", "effect": "deny" },
    { "action": "integration.use", "resource": "opencode#org_01M3CAP8H43Z49CER6NT0Y3ZF1", "effect": "allow" },
  ],
}
```

`/connect` offers only the Console so members can sign in. The free tier, personal
Console accounts, other organizations, environment API keys, and providers from
`opencode.json` are all unavailable. The workspace's providers, MCP servers, and
web search work, and changing them in the Console needs no new MDM profile.

Only managed policies can withhold the MCP servers and config an integration
delivers; its own policies keep applying either way.

## MCP servers, skills, and plugins

| Action       | Resource                                                          |
| ------------ | ----------------------------------------------------------------- |
| `mcp.use`    | An [MCP server](/mcp-servers) by its configured name              |
| `skill.use`  | A [skill](/skills) by its ID                                      |
| `plugin.use` | A [plugin](/plugins) by unversioned package name or absolute path |

```jsonc
{
  "policies": [
    { "action": "mcp.use", "resource": "*", "effect": "deny" },
    { "action": "mcp.use", "resource": "github", "effect": "allow" },
    { "action": "plugin.use", "resource": "opencode-acme-cli", "effect": "deny" },
  ],
}
```

A denied MCP server or skill is removed, and a denied plugin is not loaded, however
it was configured.

### Upgrading from `integration.use`

Before OpenCode 2.2.3, `integration.use` covered MCP servers, skills, and plugins
through an `mcp:`, `skill:`, or `plugin:` prefix. Prefixed statements still load and
are converted to `mcp.use`, `skill.use`, or `plugin.use`.

A statement without one of those prefixes, including a bare `*`, now applies to
integrations instead. A broad deny such as

```jsonc
{ "action": "integration.use", "resource": "*", "effect": "deny" }
```

no longer blocks MCP servers, skills, or plugins. Instead it blocks every provider,
including the free tier, unless a later statement allows an integration. To keep
the old meaning, replace it with:

```jsonc
{
  "policies": [
    { "action": "mcp.use", "resource": "*", "effect": "deny" },
    { "action": "skill.use", "resource": "*", "effect": "deny" },
    { "action": "plugin.use", "resource": "*", "effect": "deny" },
  ],
}
```

Exceptions move the same way: `integration.use` `mcp:github` becomes `mcp.use`
`github`, and `skill:team:review` becomes `skill.use` `team:review`.

Clients older than 2.2.3 ignore `integration.connect`, `integration.use` (in its new
meaning), `mcp.use`, `skill.use`, and `plugin.use`, so require 2.2.3 or newer
wherever you rely on them.

## Web search

`websearch.use` controls the web search providers the `websearch` tool can use. The
resource is the provider ID: `opencode` for OpenCode Web Search from the Console, a
built-in provider such as `exa`, `firecrawl`, `parallel`, `tavily`, or `tinyfish`, or
the ID of a provider a plugin adds.

```jsonc
{
  "policies": [
    { "action": "websearch.use", "resource": "*", "effect": "deny" },
    { "action": "websearch.use", "resource": "opencode", "effect": "allow" },
  ],
}
```

Denied providers are removed even when they work without an API key, such as Exa and
TinyFish. When every provider is denied, the `websearch` tool is no longer offered.
Clients older than 2.2.6 ignore `websearch.use`.

## Precedence

Ordinary settings let the nearest configuration win. Policies reverse that: statements
from broader configuration are evaluated later, so they override narrower ones. Managed
configuration and the connected Console always outrank user and project files.

| Authority   | Source                                                                                                         |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| 1 (highest) | macOS managed preferences (`/Library/Managed Preferences/ai.opencode.managed.plist`)                           |
| 2           | System `opencode.json(c)` (`/Library/Application Support/opencode`, `/etc/opencode`, `%ProgramData%\opencode`) |
| 3           | Connected OpenCode Console workspace                                                                           |
| 4           | Global `~/.config/opencode/opencode.json(c)`                                                                   |
| 5           | Direct `opencode.json(c)`; outer directories beat inner ones                                                   |
| 6 (lowest)  | `.opencode/opencode.json(c)`; outer directories beat inner ones                                                |

A repository cannot re-enable a provider you deny globally:

```jsonc title="~/.config/opencode/opencode.jsonc"
{
  "policies": [{ "action": "provider.use", "resource": "openai", "effect": "deny" }],
}
```

```jsonc title="project/opencode.jsonc"
{
  "policies": [{ "action": "provider.use", "resource": "openai", "effect": "allow" }],
}
```

`openai` stays denied. Within one file, statements keep their written order.

## Console

A workspace on the [OpenCode Console](/console) compiles its Providers and Tools
policies into regular `policies` statements in the workspace configuration. OpenCode
merges that configuration like a configuration file that ranks right below managed
preferences and the system file, so an organization `deny` cannot be lifted by a
repository or user `allow`, and an organization `allow` lifts a lower-authority `deny`.
Managed preferences and the system file still override the workspace.

Denials from these statements name the workspace:

```text
Blocked by Acme's policy
```

Statements follow the connected account.

| Event                       | Result                                             |
| --------------------------- | -------------------------------------------------- |
| Console policy changes      | Applied on the next fetch, about once a minute     |
| Switch to another workspace | Statements replaced with that workspace's          |
| Disconnect                  | Statements cleared                                 |
| Console unreachable         | Last statements for that connection stay in effect |

The Console plugin and the policy plugin cannot be disabled from `plugins`; see
[plugin control](/plugins#control).

<Callout type="note">
  Policies do not configure endpoints or credentials, and they cannot make an unusable provider usable. They also do not
  sandbox plugin code: a denied provider is blocked on the normal provider and model path.
</Callout>