# Permit.io (/docs/guides/integrations/permit-io)

Location: Guides > Integrations > Permit.io

Introduction [#introduction]

[Permit.io](https://www.permit.io/) is an authorization-as-a-service platform that lets you implement fine-grained access control
rules based on real-world relationships.

This guide explains how to connect Permit.io to a new Express + Prisma app, define a
[Relationship-Based Access Control (ReBAC)](https://www.permit.io/blog/what-is-rebac) policy,
and automatically filter Prisma queries so users only see the data they're allowed to access.

You'll build a small project-task API to demonstrate access inheritance in action - no manual `WHERE` clauses required.

You can find a complete example of this guide [here](https://www.permit.io/blog/prisma-orm-data-filtering-with-rebac).

Prerequisites [#prerequisites]

* [Node.js v20+](https://nodejs.org/)
* [PostgreSQL](https://www.postgresql.org/) (local or hosted)
* [Prisma CLI](/orm/reference/prisma-cli-reference) (`npx prisma`)
* [TypeScript](https://www.typescriptlang.org/)
* [Permit CLI](https://github.com/permitio/permit-cli) (`npm install -g @permitio/cli`)

1. Set up your project [#1-set-up-your-project]

First of all, you'll create a new Express + Prisma project from scratch using TypeScript.
You'll also install the tools needed to support ReBAC filtering with Permit.io.

1.1 Create the project folder [#11-create-the-project-folder]

  

#### npm

```bash
mkdir prisma-rebac-filtering
cd prisma-rebac-filtering
npm init
```

#### pnpm

```bash
mkdir prisma-rebac-filtering
cd prisma-rebac-filtering
pnpm init
```

#### yarn

```bash
mkdir prisma-rebac-filtering
cd prisma-rebac-filtering
yarn init
```

#### bun

```bash
mkdir prisma-rebac-filtering
cd prisma-rebac-filtering
bun init
```

1.2 Install the required dependencies [#12-install-the-required-dependencies]

Install application and development dependencies:

  

#### npm

```bash
npm install express cors dotenv @prisma/client @prisma/adapter-pg pg
```

#### pnpm

```bash
pnpm add express cors dotenv @prisma/client @prisma/adapter-pg pg
```

#### yarn

```bash
yarn add express cors dotenv @prisma/client @prisma/adapter-pg pg
```

#### bun

```bash
bun add express cors dotenv @prisma/client @prisma/adapter-pg pg
```

  

#### npm

```bash
npm install -D prisma typescript tsx @types/pg
```

#### pnpm

```bash
pnpm add -D prisma typescript tsx @types/pg
```

#### yarn

```bash
yarn add --dev prisma typescript tsx @types/pg
```

#### bun

```bash
bun add --dev prisma typescript tsx @types/pg
```

> [!NOTE]
> If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/core-concepts/supported-databases/database-drivers).

Then, initialize your Prisma setup:

  

#### npm

```bash
npx prisma init
```

#### pnpm

```bash
pnpm dlx prisma init
```

#### yarn

```bash
yarn dlx prisma init
```

#### bun

```bash
bunx --bun prisma init
```

This creates:

* A `prisma/` directory with a default `schema.prisma` file
* A `prisma.config.ts` file for configuring Prisma
* A `.env` file at the root.

1.3 Set up your TypeScript config [#13-set-up-your-typescript-config]

Create a `tsconfig.json` file:

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src", "scripts"]
}
```

1.4 Create your folder structure [#14-create-your-folder-structure]

Set up your project folders:

```bash
mkdir -p src/controllers src/middleware src/config scripts
```

You're now ready to define your Prisma data model.

2. The authorization model [#2-the-authorization-model]

Before we continue with the setup, it's important to define how access control will work in your application.

This guide uses **Relationship-Based Access Control (ReBAC)** to automatically restrict database queries based on a user's relationship to the data.

Let's see what this looks like:

<img alt="ReBAC authorization model" src="/img/guides/permitio/rebac.png" width="875" height="503" />

Scenario overview [#scenario-overview]

You're building a **project management API** that supports team-level access controls. Each project belongs to a team (like Marketing or Engineering), and users should only be able to access the projects—and their associated tasks—that they're assigned to.

This is a perfect use case for ReBAC, because:

* Access depends on relationships between users and data (e.g., team membership)
* You want task access to inherit from project access
* You want to avoid manually checking permissions in every controller

Resources [#resources]

These are the main data entities you'll protect:

* `Project`: Represents a team-specific workspace that may contain business-critical data (timelines, budgets, client deliverables).
* `Task`: Represents an item of work that belongs to a project

Relationships [#relationships]

* Projects contain tasks (`Project → Task`)
* Users are members of projects (`User → Project`)

Instance-level roles [#instance-level-roles]

Instance-level roles describe what users can do with specific resources:

| Role             | Description                           |
| ---------------- | ------------------------------------- |
| `project#Member` | User can access a specific project    |
| `task#Member`    | User can access tasks in that project |

Role derivation [#role-derivation]

ReBAC lets you **automatically derive roles** based on relationships. In this case:

* If a user is a `project#Member`, they automatically become a `task#Member` for all tasks within that project.
* New tasks inherit project access—no need to update permissions manually.

Access policies [#access-policies]

Once relationships and roles are defined, access policies determine what users can do:

| Role             | Action | Resource |
| ---------------- | ------ | -------- |
| `project#Member` | `read` | Project  |
| `task#Member`    | `read` | Task     |

This model ensures that:

* Users can only access the projects and tasks they're assigned to
* No cross-team visibility
* Access automatically stays in sync with the business structure

<img alt="Access control planning" src="/img/guides/permitio/planning.png" width="908" height="571" />

3. Define your data model [#3-define-your-data-model]

To support permission-aware data filtering, you need to structure your database so that relationships are clearly defined. In this case, every `Task` belongs to a `Project`, and users gain access to tasks by being members of the parent project.

3.1 Update your Prisma schema [#31-update-your-prisma-schema]

Open `prisma/schema.prisma` and replace the contents with the following:

```prisma title="prisma/schema.prisma"
generator client {
  provider = "prisma-client"
}

datasource db {
  provider = "postgresql"
}

model Project {
  id        String   @id @default(uuid())
  name      String
  tasks     Task[]   // One-to-many relationship for permission inheritance
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Task {
  id          String   @id @default(uuid())
  name        String
  description String?
  projectId   String
  project     Project  @relation(fields: [projectId], references: [id])
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
```

3.2 Add dotenv to prisma.config.ts [#32-add-dotenv-to-prismaconfigts]

To get access to the variables in the `.env` file, they can either be loaded by your runtime, or by using `dotenv`.
Include an import for `dotenv` at the top of the `prisma.config.ts`

```ts
import "dotenv/config"; // [!code ++]
import { defineConfig, env } from "prisma/config";
export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
});
```

3.3 Run your first migration [#33-run-your-first-migration]

To create the database schema:

  

#### npm

```bash
npx prisma migrate dev --name init
```

#### pnpm

```bash
pnpm dlx prisma migrate dev --name init
```

#### yarn

```bash
yarn dlx prisma migrate dev --name init
```

#### bun

```bash
bunx --bun prisma migrate dev --name init
```

  

#### npm

```bash
npx prisma generate
```

#### pnpm

```bash
pnpm dlx prisma generate
```

#### yarn

```bash
yarn dlx prisma generate
```

#### bun

```bash
bunx --bun prisma generate
```

This will:

* Apply your schema to the connected PostgreSQL database
* Generate your Prisma Client
* Create tables for `Project` and `Task` with a one-to-many relationship

3.3 Confirm the structure [#33-confirm-the-structure]

You can open Prisma Studio to inspect your database:

  

#### npm

```bash
npx prisma studio
```

#### pnpm

```bash
pnpm dlx prisma studio
```

#### yarn

```bash
yarn dlx prisma studio
```

#### bun

```bash
bunx --bun prisma studio
```

This structure allows the `@permitio/permit-prisma` extension to filter records by user relationships at query time. Next, you'll seed test data to simulate distinct team ownership over projects and tasks.

4. Seed test data with project boundaries [#4-seed-test-data-with-project-boundaries]

To test your data filtering logic, you'll create two projects, each with its own set of tasks. This separation simulates team ownership and will allow you to validate that users only see the data of their assigned project.

4.1 Create the seed script [#41-create-the-seed-script]

Create a new file at `scripts/seed.ts` and add the following:

```ts
import { PrismaClient } from "../src/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL!,
});

const prisma = new PrismaClient({
  adapter,
});

async function main() {
  console.log("🌱 Seeding test data...");

  // Clear existing records
  await prisma.task.deleteMany();
  await prisma.project.deleteMany();

  // Create Project Alpha for the Marketing team
  const projectAlpha = await prisma.project.create({
    data: {
      id: "project_alpha",
      name: "Marketing Campaign Q2",
    },
  });

  // Create Project Beta for the Engineering team
  const projectBeta = await prisma.project.create({
    data: {
      id: "project_beta",
      name: "API Development Sprint",
    },
  });

  // Add tasks to Project Alpha
  await prisma.task.createMany({
    data: [
      {
        id: "task-alpha-1",
        name: "Strategy Planning",
        description: "Define campaign goals and KPIs",
        projectId: projectAlpha.id,
      },
      {
        id: "task-alpha-2",
        name: "Budget Review",
        description: "Review marketing budget with finance",
        projectId: projectAlpha.id,
      },
    ],
  });

  // Add tasks to Project Beta
  await prisma.task.createMany({
    data: [
      {
        id: "task-beta-1",
        name: "Implement Auth API",
        description: "Create endpoints for user login/signup",
        projectId: projectBeta.id,
      },
      {
        id: "task-beta-2",
        name: "Schema Migration",
        description: "Update tables for new user roles",
        projectId: projectBeta.id,
      },
    ],
  });

  console.log("✅ Seeded 2 projects and 4 tasks with distinct ownership");
}

main()
  .catch((e) => {
    console.error("❌ Error seeding data:", e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
```

4.2 Run the seed script [#42-run-the-seed-script]

  

#### npm

```bash
npx tsx scripts/seed.ts
```

#### pnpm

```bash
pnpm dlx tsx scripts/seed.ts
```

#### yarn

```bash
yarn dlx tsx scripts/seed.ts
```

#### bun

```bash
bunx --bun tsx scripts/seed.ts
```

If successful, you'll see:

✅ Seeded 2 projects and 4 tasks with distinct ownership

At this point, if you run a query like `prisma.task.findMany()`, it will return all tasks. In the next steps, you'll connect Permit.io to filter these results automatically based on the user's access rights.

5. Install and configure ReBAC filtering [#5-install-and-configure-rebac-filtering]

In this section, you'll install the `@permitio/permit-prisma` extension and configure it to automatically filter Prisma queries based on your access control policies.

5.1 Install the Permit extension [#51-install-the-permit-extension]

Install the `permit-prisma` package:

  

#### npm

```bash
npm install @permitio/permit-prisma
```

#### pnpm

```bash
pnpm add @permitio/permit-prisma
```

#### yarn

```bash
yarn add @permitio/permit-prisma
```

#### bun

```bash
bun add @permitio/permit-prisma
```

5.2 Configure the Permit client [#52-configure-the-permit-client]

Create a new file at `src/config/permit-config.ts`:

```ts
import dotenv from "dotenv";
dotenv.config();

export const clientExtensionConfig = {
  permitConfig: {
    token: process.env.PERMIT_API_KEY!, // Your Permit.io API key
    pdp: process.env.PERMIT_PDP_URL || "http://localhost:7766", // Local or cloud PDP
    debug: true,
  },
  enableAutomaticChecks: true,
  enableDataFiltering: true, // Enables automatic query filtering
  enableResourceSync: true, // (Optional) Keeps Permit in sync with resource changes
};
```

> [!NOTE]
> API Key
> 
> You can find your API key and PDP URL in your [Permit.io dashboard](https://app.permit.io/).

5.3 What this configuration does [#53-what-this-configuration-does]

When you later extend the Prisma Client with this config:

* All Prisma queries will automatically check access rules
* `findMany()` and similar methods will **only return data the user is allowed to access**
* You no longer need to manually add `WHERE` clauses to enforce permissions

You're now ready to define your ReBAC policy using the Permit CLI.

6. Define your access control policy in Permit.io [#6-define-your-access-control-policy-in-permitio]

Next, you'll use the **Permit CLI** to apply a ready-made ReBAC template that matches your project-task structure.

6.1 Install the Permit CLI [#61-install-the-permit-cli]

  

#### npm

```bash
npm install -g @permitio/cli
```

#### pnpm

```bash
pnpm add -g @permitio/cli
```

#### yarn

```bash
yarn global add @permitio/cli
```

#### bun

```bash
bun add --global @permitio/cli
```

6.2 Log in to your Permit account [#62-log-in-to-your-permit-account]

Use the CLI to authenticate:

```bash
permit login
```

This opens a browser window where you can log in to your Permit.io account and link your CLI session to an environment.

6.3 Apply the ReBAC policy template [#63-apply-the-rebac-policy-template]

Permit provides a prebuilt policy structure for hierarchical data filtering.

Apply it using:

```bash
permit env template apply --template orm-data-filtering
```

This will create:

* **Resources**: `project`, `task`
* **Relationships**: `project` is the parent of `task`
* **Roles**:
  * `project#Member`: User can access a specific project
  * `task#Member`: Derived from project membership
* **Access policies**: Users with the appropriate roles can `read` each resource

6.4 View the policy in the Permit UI [#64-view-the-policy-in-the-permit-ui]

Go to the [Permit.io dashboard](https://app.permit.io/) and navigate to your environment to explore:

* Your resource graph
* Role derivations
* Relationship mappings
* Policy rules for `read` access

> [!NOTE]
> These rules are used by the @permitio/permit-prisma extension to determine which records to return for each user—automatically.

With your policy in place, you're now ready to wire up user context and filtering logic in your Express middleware.

7. Add middleware to set user context [#7-add-middleware-to-set-user-context]

To filter Prisma queries per user, you need to:

1. Identify the current user (simulated via an email header)
2. Attach the filtered Prisma Client instance to the request
3. Set the user in the Permit context (`prisma.$permit.setUser()`)

7.1 Create the middleware file [#71-create-the-middleware-file]

Create a new file: `src/middleware/auth.middleware.ts`

```ts
import { Request, Response, NextFunction } from "express";
import { PrismaClient } from "../generated/prisma/client.js";
import { PrismaPg } from "@prisma/adapter-pg";
import createPermitClientExtension from "@permitio/permit-prisma";
import { clientExtensionConfig } from "../config/permit-config";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL!,
});

// Extend PrismaClient with Permit
const prisma = new PrismaClient({
  adapter,
}).$extends(createPermitClientExtension(clientExtensionConfig));

// Extend Request type with Prisma and user context
export interface AuthRequest extends Request {
  user?: { email: string };
  prisma?: typeof prisma;
}

export const authenticate = (req: AuthRequest, res: Response, next: NextFunction): void => {
  const userEmail = req.headers["x-user-email"] as string;

  if (!userEmail) {
    res.status(401).json({ error: "Missing user email" });
    return;
  }

  // Register the user in Permit context
  prisma.$permit.setUser(userEmail);

  // Add user + Prisma client to request
  req.user = { email: userEmail };
  req.prisma = prisma;

  next();
};
```

> [!WARNING]
> Going to prod.
> 
> In a production app, you'd replace the x-user-email header with proper authentication logic (e.g. JWT or session validation).

7.2 What this middleware does [#72-what-this-middleware-does]

* Reads the user's email from the request header
* Sets the user identity in the Permit context (used for query filtering)
* Adds the filtered Prisma client to the request object (`req.prisma`)
* Makes the user and database client available to all downstream route handlers

You're now ready to build your API endpoints without writing a single line of access control logic.

8. Build your API endpoints [#8-build-your-api-endpoints]

You'll now create two endpoints:

* `GET /api/projects`: returns all projects the user has access to
* `GET /api/tasks`: returns all tasks the user has access to (inherited from project membership)

Thanks to the Permit-Prisma integration, **you won't need to add any manual filtering logic**—it's handled automatically.

8.1 Get user-visible projects [#81-get-user-visible-projects]

Create a controller file: `src/controllers/project.controller.ts`

```ts
import { Response } from "express";
import { AuthRequest } from "../middleware/auth.middleware";

export const getProjects = async (req: AuthRequest, res: Response) => {
  try {
    const prisma = req.prisma!;

    const projects = await prisma.project.findMany(); // Auto-filtered

    res.json({
      user: req.user?.email,
      count: projects.length,
      projects,
    });
  } catch (error: any) {
    console.error("Error fetching projects:", error);
    res.status(500).json({ error: error.message });
  }
};
```

> Even though this is a raw findMany() query, only authorized records will be returned for the current user.

8.2 Get user-visible tasks [#82-get-user-visible-tasks]

Create another controller: `src/controllers/task.controller.ts`

```ts
import { Response } from "express";
import { AuthRequest } from "../middleware/auth.middleware";

export const getTasks = async (req: AuthRequest, res: Response) => {
  try {
    const prisma = req.prisma!;
    const projectId = req.query.projectId as string;

    const where = projectId ? { projectId } : undefined;

    const tasks = await prisma.task.findMany({ where }); // Still filtered

    res.json({
      user: req.user?.email,
      count: tasks.length,
      tasks,
    });
  } catch (error: any) {
    console.error("Error fetching tasks:", error);
    res.status(500).json({ error: error.message });
  }
};
```

> [!NOTE]
> projectId
> 
> Even if you provide a projectId manually, the query results are still filtered by permissions.

8.3 What this demonstrates [#83-what-this-demonstrates]

* You can write **normal Prisma queries**
* Users will only get records they're allowed to see
* You don't need custom role-checking logic in every handler
* Task access is **automatically derived** from project membership

You're now ready to wire it all together and launch the app.

9.1 Create your Express app [#91-create-your-express-app]

Create `src/app.ts`:

```ts
import express from "express";
import cors from "cors";
import { authenticate } from "./middleware/auth.middleware";
import { getProjects } from "./controllers/project.controller";
import { getTasks } from "./controllers/task.controller";

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

// Auth middleware applies ReBAC filtering per request
app.get("/api/projects", authenticate, getProjects);
app.get("/api/tasks", authenticate, getTasks);

app.listen(PORT, () => {
  console.log(`🚀 Server running at http://localhost:${PORT}`);
  console.log(`🔐 ReBAC filtering is now active`);
});
```

9.2 Run the server [#92-run-the-server]

Start the development server with:

  

#### npm

```bash
npx tsx src/app.ts
```

#### pnpm

```bash
pnpm dlx tsx src/app.ts
```

#### yarn

```bash
yarn dlx tsx src/app.ts
```

#### bun

```bash
bunx --bun tsx src/app.ts
```

If everything is set up correctly, the console will display:

```
🚀 Server running at http://localhost:3000
🔐 ReBAC filtering is now active
```

9.3 Test your API [#93-test-your-api]

You can simulate requests as different users by setting the `x-user-email` header. This mimics logged-in users with access to specific projects.

Example: John (Marketing team member) [#example-john-marketing-team-member]

```bash
curl -H "x-user-email: john@company.com" http://localhost:3000/api/projects
```

This should only return Project Alpha (and its tasks).

Example: Mary (Engineering team member) [#example-mary-engineering-team-member]

```bash
curl -H "x-user-email: mary@company.com" http://localhost:3000/api/tasks
```

This should only return tasks from Project Beta.

> [!NOTE]
> If you haven't yet assigned users to project memberships in the Permit.io UI, visit the Policy Editor and assign users to roles (project#Member).

Once you've confirmed these results, your Prisma API is now enforcing secure, relationship-based access control, all **without adding manual filtering logic anywhere in your code.**

You've now built a secure API that:

* Filters query results based on user relationships
* Uses ReBAC to avoid role explosion and brittle permission logic
* Keeps Prisma queries clean, safe, and scalable

10. Next steps [#10-next-steps]

Now that you've successfully implemented data filtering with Prisma and ReBAC, you can extend this foundation to support more complex authorization use cases and developer tooling.

Extend your model [#extend-your-model]

* Add a `User` model and create a many-to-many `Membership` relationship between users and projects.
* Introduce instance-level roles like `Editor` or `Owner` with different permissions.
* Support additional actions like `create`, `update`, and `delete`, using Permit.io's role policies.

Add authentication [#add-authentication]

Integrate your API with an auth provider (e.g., [Clerk](/guides/authentication/clerk/nextjs), Auth0) and replace the `x-user-email` header with a secure identity mechanism (like a JWT token).

Use Permit Elements [#use-permit-elements]

Permit.io provides UI components for:

* Managing user access visually
* Reviewing access logs
* Approving access requests (MCP)

Explore [Permit Elements](https://docs.permit.io/embeddable-uis/overview/) to make access management easier for your end users or admins.

More resources [#more-resources]

* [Permit.io ReBAC Policy Setup Guide](https://docs.permit.io/overview/create-a-rebac-policy)
* [Read the full guide: Data Filtering with Prisma and ReBAC](https://docs.permit.io/how-to/enforce-permissions/data-filtering)
* [ReBAC vs RBAC - Learn when to use which](https://www.permit.io/blog/rbac-vs-rebac)

## Related pages

- [`AI SDK (with Next.js)`](https://www.prisma.io/docs/guides/integrations/ai-sdk): Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages
- [`Datadog`](https://www.prisma.io/docs/guides/integrations/datadog): Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog
- [`Embedded Prisma Studio (with Next.js)`](https://www.prisma.io/docs/guides/integrations/embed-studio): Learn how to embed Prisma Studio directly in your Next.js application for database management
- [`GitHub Actions`](https://www.prisma.io/docs/guides/integrations/github-actions): Provision and manage Prisma Postgres databases per pull request using GitHub Actions and Prisma Management API
- [`Neon with Accelerate`](https://www.prisma.io/docs/guides/integrations/neon-accelerate): Learn how to set up PostgreSQL on Neon with Prisma Accelerate's Connection Pool