---
title: "Open Policy Agent"
description: "Use Open Policy Agent for MainSDK access and policy, with Rego bundles evaluated in process - main.yaml, testing, limits and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/open-policy-agent"
updated: "2026-10-28"
---

# Open Policy Agent

> Use Open Policy Agent for MainSDK access and policy, with Rego bundles evaluated in process - main.yaml, testing, limits and swapping.

Canonical: https://mainsdk.dev/docs/integrations/open-policy-agent · Updated: 2026-10-28

Open Policy Agent (OPA) is an open-source, general-purpose policy engine that uses the Rego language. In MainSDK it can drive [`access`](https://mainsdk.dev/docs/interfaces/access), for your app's permissions, and [`policy`](https://mainsdk.dev/docs/interfaces/policy), for everything MainSDK decides. Pick it when your company already writes Rego and keeps OPA bundles, so your rules carry over unchanged.

## Interfaces it serves

### Permissions (`access`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `opa` (Open Policy Agent)
- Docs: https://mainsdk.dev/docs/interfaces/access.md

```yaml
access:
  driver: opa
```

Swap to another provider: `cedar`, `openfga`, `spicedb`, `cerbos`, `permit`, `oso`, `avp`.

### Policy decisions (`policy`)

- Status: Building for 0.2 · ships as a stable API
- Driver: `opa` (Open Policy Agent)
- Docs: https://mainsdk.dev/docs/interfaces/policy.md

```yaml
policy:
  driver: opa
```

Swap to another provider: `builtin`.

## Before you start

1. **Write your policies in Rego** and keep them in git next to `main.yaml`.
2. **Build a bundle** of those policies with the OPA tooling. For `policy`, MainSDK evaluates the compiled bundle in process; there is no OPA server to run for it.

## Configure

Permissions:

```yaml title="main.yaml"
access:
  driver: opa
  models: [rbac, abac]
  unreachable: { deny: true, cache: 30s }
  # [TBD] connection keys for the access driver
```

Policy decisions:

```yaml title="main.yaml"
policy:
  engine: opa
  placement: server
  mode: observe                  # the default for new policies
  opa: { bundle: ./policies/rego }
```

## Use it

```ts title="app/api/orders/[id]/route.ts"
import { access } from "mainsdk";

const decision = await access.check({ subject: user, action: "orders.update", resource: order });
if (!decision.allow) return new Response("Forbidden", { status: 403 });
```

```ts title="app/api/reports/[id]/export/route.ts"
import { policy } from "mainsdk";

const d = policy.decide({
  subject: { type: "user", id: user.id },
  action: { name: "export.data" },
  resource: { type: "report", id: report.id },
});
if (d.decision === "deny") console.log(policy.explain(d.id));
```

## Local development and testing

The bundle runs in process, so local development needs nothing extra. `mainsdk test policy` and `mainsdk test access` run your tests; `policyTest` from `@mainsdk/testing` asserts a decision and its rule, and `createTestApp()` puts `access` on its fake. `chaos.on("access", { outage: true })` shows `access` denying when the engine is unreachable.

## What MainSDK adds on top

- Deny when no policy permits, with no bypass path.
- `policy` decisions in process, no network round trip per decision; the same shared decision suite passes on OPA and Cedar.
- Lower layers only tighten; a broken bundle keeps the last good policy set (`MAIN-POLICY-0009`).
- Every decision written to the audit log with the policy version.

See [Permissions](https://mainsdk.dev/docs/interfaces/access) and [Policy decisions](https://mainsdk.dev/docs/interfaces/policy).

## Limits and notes

- The OPA drivers are phase 1 tickets in the `access` spec (milestone 0.3) and the `policy` spec (milestone 0.2).
- OPA has no relations: calling `access.relate()` with `driver: opa` fails `mainsdk check` with `MAIN-ACCESS-0004`, which lists drivers that support them.
- `policy` never calls a remote OPA per decision.
- `access.raw()` and `policy.raw()` return the engine; `mainsdk check` lists every use.

## Swap to or from it

```yaml title="main.yaml"
policy:
  engine: cedar   # was opa
```

> **Warning:** Swapping engines changes your policy language between Rego and Cedar. Port the policies and run the shared decision suite on both engines before you switch.

## FAQ

**Do I need to run an OPA server?** Not for `policy`: the bundle is evaluated in process.

**Can I keep my existing OPA bundles?** Yes. They run in place (`byo-policy`).

## Next

- [Permissions](https://mainsdk.dev/docs/interfaces/access) and [Policy decisions](https://mainsdk.dev/docs/interfaces/policy)
- [Cerbos](https://mainsdk.dev/docs/integrations/cerbos) and [AWS Verified Permissions](https://mainsdk.dev/docs/integrations/aws-verified-permissions)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
