---
title: "OpenFGA"
description: "Use OpenFGA for relationship-based MainSDK access on your existing store and model - main.yaml, adopt, testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/openfga"
updated: "2026-10-28"
---

# OpenFGA

> Use OpenFGA for relationship-based MainSDK access on your existing store and model - main.yaml, adopt, testing and swapping.

Canonical: https://mainsdk.dev/docs/integrations/openfga · Updated: 2026-10-28

OpenFGA is an open-source, Zanzibar-style engine for relationship-based access: "she can edit this document because her team owns the folder". Behind [`access`](https://mainsdk.dev/docs/interfaces/access), your OpenFGA model and tuples stay where they are, and MainSDK adds typed permissions, checks and audit in front. Pick it when permissions follow relationships between users, teams and records, or when your company already runs OpenFGA.

## Interfaces it serves

### Permissions (`access`)

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

```yaml
access:
  driver: openfga
```

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

## Before you start

1. **Run an OpenFGA server**, or use a managed OpenFGA service, reachable from your app.
2. **Create a store and an authorization model**, and note both IDs.
3. **Set up credentials** for your app if your server requires them, and keep them in your secrets manager.

## Configure

```yaml title="main.yaml"
access:
  driver: openfga
  openfga: { api: https://fga.pinecart.internal, store: 01J…, model: 01J… }   # shape from the requirements; not in the spec yet
  models: [rebac, abac]
  fresh-after-write: true
  unreachable: { deny: true, cache: 30s }
```

The `access` spec does not define connection keys for remote engines yet, so the `openfga:` line may change. Credentials take `secret:` references.

## Use it

```ts title="server/folders.ts"
import { access } from "mainsdk";

export async function shareFolder(user: { id: string }, folderId: string, teamId: string) {
  const folder = { type: "folder", id: folderId };
  await access.relate({ type: "team", id: teamId }, "editor", folder);
  return access.can(user, "folders.edit", folder);   // higher consistency right after the write
}
```

## Local development and testing

`mainsdk dev` runs Cedar in process, so you can build without a server; run OpenFGA locally to exercise your real model. `mainsdk test access` runs your model tests, `createTestApp()` from `@mainsdk/testing` puts `access` on its fake, and `chaos.on("access", { outage: true })` shows checks denying with `MAIN-ACCESS-0007`.

## What MainSDK adds on top

- Typed permissions from your model through `mainsdk adopt access --from openfga`.
- Fresh after write: a check right after `access.relate()` in the same request uses higher consistency.
- Denies when OpenFGA is unreachable, with only a short cache you set.
- Every decision written to the audit log with the model ID.

See [Permissions](https://mainsdk.dev/docs/interfaces/access).

## Limits and notes

- The OpenFGA driver is a phase 1 ticket in the `access` spec (milestone 0.3).
- `mainsdk check` flags OpenFGA 1.8.0 to 1.13.1 (CVE-2026-34972) with `MAIN-ACCESS-0011`; upgrade OpenFGA.
- New tuples are written through the job outbox, so the database row and the tuple never drift apart.
- `access.raw()` returns the OpenFGA client; `mainsdk check` lists every use.

## Swap to or from it

```yaml title="main.yaml"
access:
  driver: spicedb   # was openfga
```

> **Warning:** Your tuples live in the OpenFGA store. Adopting OpenFGA needs no migration, but leaving it means moving relationships and porting the model to the new engine. Check the new driver's capability profile first; `mainsdk check` names any gap.

## FAQ

**Do I have to move my tuples?** No. They stay in your store; MainSDK sits in front.

**Does this work with Auth0 FGA?** Auth0 FGA is managed OpenFGA, listed in the requirements as a close relative on the same path. How the driver connects to it is not specified yet.

## Next

- [Permissions](https://mainsdk.dev/docs/interfaces/access)
- [SpiceDB](https://mainsdk.dev/docs/integrations/spicedb), [Permit.io](https://mainsdk.dev/docs/integrations/permit-io) and [Auth0](https://mainsdk.dev/docs/integrations/auth0)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
