PageSourceSearch

https://permitio-docs.netlify.app/assets/js/16a4c697.2023c259.js

js permitio-docs.netlify.app collected 2026-10-03 11:19:45 UTC 25,711 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkmy_website||=[]).push([[5118],{44759(e,n,i){i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>d,default:()=>h,frontMatter:()=>c,metadata:()=>s,toc:()=>l});const s=JSON.parse('{"id":"sdk/permit-prisma-extension","title":"Enforce Permit.io permissions in Prisma queries","description":"Add the @permitio/permit-prisma Client extension to check permissions, filter query results, and sync resource instances on Prisma operations.","source":"@site/docs/sdk/permit-prisma-extension.mdx","sourceDirName":"sdk","slug":"/sdk/permit-prisma-extension","permalink":"/sdk/permit-prisma-extension","draft":false,"unlisted":false,"editUrl":"https://github.com/permitio/docs/edit/master/docs/sdk/permit-prisma-extension.mdx","tags":[],"version":"current","frontMatter":{"title":"Enforce Permit.io permissions in Prisma queries","sidebar_label":"Prisma ORM","description":"Add the @permitio/permit-prisma Client extension to check permissions, filter query results, and sync resource instances on Prisma operations."},"sidebar":"sdks","previous":{"title":"SDK Feature Parity","permalink":"/sdk/sdks-overview"},"next":{"title":"Calling the API","permalink":"/api/api-with-cli"}}');var r=i(74848),t=i(28453);const c={title:"Enforce Permit.io permissions in Prisma queries",sidebar_label:"Prisma ORM",description:"Add the @permitio/permit-prisma Client extension to check permissions, filter query results, and sync resource instances on Prisma operations."},d=void 0,o={},l=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Install the Permit Prisma extension",id:"installation",level:2},{value:"Add the extension to your Prisma client",id:"basic-setup",level:2},{value:"Configuration options",id:"configuration-options",level:3},{value:"How Prisma operations map to Permit actions",id:"how-prisma-operations-map-to-permit-actions",level:3},{value:"Enforce permissions on Prisma queries",id:"permission-enforcement",level:2},{value:"Permission check flow",id:"permission-check-flow",level:3},{value:"Configure the extension for your policy model",id:"configuration-examples-for-different-models",level:2},{value:"RBAC configuration",id:"rbac-example",level:3},{value:"ABAC configuration",id:"abac-example",level:3},{value:"ReBAC configuration",id:"rebac-example",level:3},{value:"Combined model configuration",id:"combined-model-example",level:3},{value:"Check permissions manually",id:"manual-permission-checks",level:2},{value:"Verify that the extension enforces permissions",id:"verify-that-the-extension-enforces-permissions",level:2},{value:"Next steps",id:"next-steps",level:2}];function a(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"@permitio/permit-prisma"})," package is a Prisma Client extension that checks each Prisma operation against your Permit.io policy before the operation reaches the database. This page is for Node.js developers who use Prisma ORM and want role-based access control (RBAC), attribute-based access control (ABAC), or relationship-based access control (ReBAC) enforced at the data layer."]}
1),"\n",(0,r.jsx)(n.p,{children:"The extension does three things, each turned on by its own option:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Capability"}),(0,r.jsx)(n.th,{children:"Option"}),(0,r.jsx)(n.th,{children:"What the extension does"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Automatic permission checks"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableAutomaticChecks"})}),(0,r.jsxs)(n.td,{children:["Maps each Prisma operation to a Permit action and calls ",(0,r.jsx)(n.code,{children:"permit.check()"})," for the active user. Throws a ",(0,r.jsx)(n.code,{children:"PermitError"})," when the policy decision point (PDP) denies the operation."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Data filtering"}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableDataFiltering"})}),(0,r.jsxs)(n.td,{children:["Adds an ",(0,r.jsx)(n.code,{children:"id"})," filter to ",(0,r.jsx)(n.code,{children:"findMany"})," queries, so the query returns only the records the active user can read."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Resource instance sync"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"enableResourceSync"}),", ",(0,r.jsx)(n.code,{children:"enableAttributeSync"})]}),(0,r.jsxs)(n.td,{children:["After a ",(0,r.jsx)(n.code,{children:"create"}),", ",(0,r.jsx)(n.code,{children:"update"}),", or ",(0,r.jsx)(n.code,{children:"delete"})," operation, creates, updates, or deletes the matching resource instance in Permit."]})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["A Permit.io account and an API key for the environment. See ",(0,r.jsx)(n.a,{href:"/overview/get-api-key",children:"Get your API key"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["A PDP. The Cloud PDP at ",(0,r.jsx)(n.code,{children:"https://cloudpdp.api.permit.io"})," supports RBAC and ReBAC. ABAC policies need an Edge PDP, such as a PDP container at ",(0,r.jsx)(n.code,{children:"http://localhost:7766"}),". See ",(0,r.jsx)(n.a,{href:"/overview/run-pdp",children:"Run the PDP"})," and ",(0,r.jsx)(n.a,{href:"/concepts/pdp/cloud-pdp-capabilities",children:"Cloud PDP capabilities"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Resources, actions, roles, and policies in Permit that match your Prisma models. See ",(0,r.jsx)(n.a,{href:"/how-to/build-policies/rbac/building-rbac-policy",children:"Build an RBAC policy"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"A Node.js project that uses Prisma Client."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"installation",children:"Install the Permit Prisma extension"}),"\n",(0,r.jsx)(n.p,{children:"Install the extension and the Prisma Client package from npm:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm install @permitio/permit-prisma @prisma/client\n"})}),"\n",(0,r.jsx)(n.h2,{id:"basic-setup",children:"Add the extension to your Prisma client"}),"\n",(0,r.jsxs)(n.p,{children:["Extend your ",(0,r.jsx)(n.code,{children:"PrismaClient"})," with ",(0,r.jsx)(n.code,{children:"createPermitClientExtension()"}),". Replace ",(0,r.jsx)(n.code,{children:"<YOUR_PERMIT_API_KEY>"})," with your API key and set ",(0,r.jsx)(n.code,{children:"pdp"})," to the address of your PDP."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'import { PrismaClient } from "@prisma/client";\nimport { createPermitClientExtension } from "@permitio/permit-prisma";\n\nconst prisma = new PrismaClient().$extends(\n  createPermitClientExtension({\n    permitConfig: {\n      token: "<YOUR_PERMIT_API_KEY>", // Permit API Key (required)\n      pdp: "http://localhost:7766",   // PDP address (required)\n    },\n    enableAutomaticChecks: true,      // Enable automatic permission checks\n    enableResourceSync: true,         // Sync resource instances with Permit.io\n    enableAttributeSync: true,        // Sync resource attributes with Permit.io\n    enableDataFiltering: true,        // Enable automatic query filtering by permissions\n  })\n);\n'})}),"\n",(0,r.jsx)(n.h3,{id:"configuration-options",children:"Configuration options"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"createPermitClientExtension()"})," takes one object with these fields:"]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Option"}),(0,r.jsx)(n.th,{children:"Type"}),(0,r.jsx)(n.th,{children:"Default"}),(0,r.jsx)(n.th,{children:"Description"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"permitC
1onfig.token"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Permit API key of the environment."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"permitConfig.pdp"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Required"}),(0,r.jsx)(n.td,{children:"Address of the PDP."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"permitConfig.debug"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsx)(n.td,{children:"Logs each intercepted operation, permission check, and decision."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"permitConfig.apiUrl"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:"Node.js SDK default"}),(0,r.jsx)(n.td,{children:"Address of the Permit API."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"permitConfig.throwOnError"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsxs)(n.td,{children:["If ",(0,r.jsx)(n.code,{children:"true"}),", a failed permission check request throws a ",(0,r.jsx)(n.code,{children:"PermitError"})," with the cause. If ",(0,r.jsx)(n.code,{children:"false"}),", a failed request counts as a denied check."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableAutomaticChecks"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsxs)(n.td,{children:["Checks permissions on every Prisma operation. Data filtering and resource sync run only when ",(0,r.jsx)(n.code,{children:"enableAutomaticChecks"})," is ",(0,r.jsx)(n.code,{children:"true"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableDataFiltering"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsxs)(n.td,{children:["Filters ",(0,r.jsx)(n.code,{children:"findMany"})," results to the records the active user can read."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableResourceSync"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsxs)(n.td,{children:["Syncs resource instances to Permit after ",(0,r.jsx)(n.code,{children:"create"}),", ",(0,r.jsx)(n.code,{children:"update"}),", and ",(0,r.jsx)(n.code,{children:"delete"})," operations. The instance key is the ",(0,r.jsx)(n.code,{children:"id"})," of the returned record, and the instance attributes are the ",(0,r.jsx)(n.code,{children:"attributes"})," field of the returned record, if the model has one."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"enableAttributeSync"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"boolean"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"false"})}),(0,r.jsxs)(n.td,{children:["Runs the same instance sync as ",(0,r.jsx)(n.code,{children:"enableResourceSync"}),". Turn on either option to sync instances."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"defaultTenant"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:'"default"'})}),(0,r.jsx)(n.td,{children:"Tenant key of synced resource instances."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"resourceTypeMapping"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"Record<string, string>"})}),(0,r.jsx)(n.td,{children:"None"}),(0,r.jsxs)(n.td,{children:["Maps Prisma model names to Permit resource keys, for example ",(0,r.jsx)(n.code,{children:'{ BlogPost: "article" }'}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"excludedModels"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string[]"})}),(0,r.jsx)(n.td,{children:"None"}),(0,r.jsx)(n.td,{children:"Prisma models that skip automatic checks."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"excludedOperations"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"string[]"})}),(0,r.jsx)(n.td,{children:"None"}),(0,r.jsxs)(n.td,{children:["Prisma operations that skip automatic checks, for example ",(0,r.jsx)(n.code,{children:'["count"]'}),"."]})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"how-prisma-operations-map-to-permit-actions",children:"How Prisma operations map to Permit actions"}),"\n",(0,r.jsxs)(n.p,{children:["The extension converts the Prisma model name to a resource key in snake_case, for example ",(0,r.jsx)(n.code,{children:"MedicalRecord"})," becomes ",(0,r.jsx)(n.code,{children:"medical_record"}),", unless ",(0,r.jsx)(n.code,{children:"resourceTypeMapping"})," sets a different key. The extension maps each Prisma operation to an action:"]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Prisma operations"}),(0,r.jsx)(n.th,{children:"Permit action"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"findUnique"}),", ",(0,r.jsx)(n.code,{children:"findUniqueOrThrow"}),", ",(0,r.jsx)(n.code,{children:"findFirst"}),", ",(0,r.jsx)(n.code,{children:"findFirstOrThrow"}),", ",(0,r.jsx)(n.code,{children:"findMany"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"read"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"create"}),", ",(0,r.jsx)(n.code,{children:"createMany"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"create"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"update"}),", ",(0,r.jsx)(n.code,{children:"updateMany"}),", ",(0,r.jsx)(n.code,{children:"upsert"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"update"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"delete"}),", ",(0,r.jsx)(n.code,{children:"deleteMany"})]}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"delete"})})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:["Any other operation, such as ",(0,r.jsx)(n.code,{children:"count"})]}),(0,r.jsx)(n.td,{children:"The operation name"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["The resource in each check has the resource key as ",(0,r.jsx)(n.code,{children:"type"}),", the ",(0,r.jsx)(n.code,{children:"id"})," from the ",(0,r.jsx)(n.code,{children:"where"})," clause as ",(0,r.jsx)(n.code,{children:"key"}),", and the fields of ",(0,r.jsx)(n.code,{children:"data"})," as ",(0,r.jsx)(n.code,{children:"attributes"}),". A query without ",(0,r.jsx)(n.code,{children:"data"})," sends the fields of ",(0,r.jsx)(n.code,{children:"where"})," as ",(0,r.jsx)(n.code,{children:"attributes"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"permission-enforcement",children:"Enforce permissions on Prisma queries"}),"\n",(0,r.jsxs)(n.p,{children:["With ",(0,r.jsx)(n.code,{children:"enableAutomaticChecks: true"}
1),", call ",(0,r.jsx)(n.code,{children:"prisma.$permit.setUser()"})," with the key of the signed-in user before you run queries. Pass a user key string, or an object with ",(0,r.jsx)(n.code,{children:"key"})," and ",(0,r.jsx)(n.code,{children:"attributes"})," for ABAC policies. The user key must match the key of a user synced to Permit."]}),"\n",(0,r.jsx)(n.h3,{id:"permission-check-flow",children:"Permission check flow"}),"\n",(0,r.jsx)(n.p,{children:"For each Prisma operation, the extension:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Intercepts the operation."}),"\n",(0,r.jsx)(n.li,{children:"Maps the operation to a Permit action and the model to a resource."}),"\n",(0,r.jsxs)(n.li,{children:["Calls ",(0,r.jsx)(n.code,{children:"permit.check()"})," for the active user, action, and resource."]}),"\n",(0,r.jsxs)(n.li,{children:["Runs the operation if the PDP allows it, or throws a ",(0,r.jsx)(n.code,{children:"PermitError"})," if the PDP denies it."]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'import { PermitError } from "@permitio/permit-prisma";\n\n// Set the active user for permission checks\nprisma.$permit.setUser("[email protected]");\n\n// This will be checked against Permit.io policies automatically\ntry {\n  const document = await prisma.document.create({\n    data: {\n      title: "New Document",\n      content: "Document content",\n    }\n  });\n  console.log("Document created successfully");\n} catch (error) {\n  if (error instanceof PermitError) {\n    console.error("Permission denied");\n  }\n}\n'})}),"\n",(0,r.jsx)(n.admonition,{title:"Set the user before every query",type:"warning",children:(0,r.jsxs)(n.p,{children:["If no user is set, the extension skips the permission check and runs the operation. ",(0,r.jsx)(n.code,{children:"setUser()"})," stores one user on the extended client, and every later query on that client runs as that user until the next ",(0,r.jsx)(n.code,{children:"setUser()"})," call. Call ",(0,r.jsx)(n.code,{children:"setUser()"})," at the start of each request, before the first query."]})}),"\n",(0,r.jsx)(n.h2,{id:"configuration-examples-for-different-models",children:"Configure the extension for your policy model"}),"\n",(0,r.jsx)(n.p,{children:"Turn on the options that your policy model needs. Permit allows an operation if any policy model grants the permission."}),"\n",(0,r.jsx)(n.h3,{id:"rbac-example",children:"RBAC configuration"}),"\n",(0,r.jsxs)(n.p,{children:["RBAC grants permissions on a resource type through roles, for example an ",(0,r.jsx)(n.code,{children:"admin"})," role that can read every document. Automatic checks are enough for RBAC."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'// Configure for RBAC\nconst prisma = new PrismaClient().$extends(\n  createPermitClientExtension({\n    permitConfig: { token: "YOUR_API_KEY", pdp: "http://localhost:7766" },\n    enableAutomaticChecks: true\n  })\n);\n\n// Simple role-based check\nprisma.$permit.setUser("[email protected]");\nconst documents = await prisma.document.findMany(); // Will succeed if admin role has read permission\n'})}),"\n",(0,r.jsx)(n.h3,{id:"abac-example",children:"ABAC configuration"}),"\n",(0,r.jsxs)(n.p,{children:["ABAC decides access from attributes of the user and the resource, for example allow access if the user department matches the record department. Set the user attributes in ",(0,r.jsx)(n.code,{children:"setUser()"}),". The extension sends the fields of the query as resource attributes. When you update a record, include every attribute that the policy evaluates in ",(0,r.jsx)(n.code,{children:"data"}),". ABAC checks need an Edge PDP."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'// Configure for ABAC\nconst prisma = new PrismaClient().$extends(\n  createPermitClientExtension({\n    permitConfig: { token: "YOUR_API_KEY", pdp: "http://localhost:7766" },\n    enableAutomaticChecks: true,\n    enableAttributeSync: true\n  })\n);
1\n\n// Set user with attributes\nprisma.$permit.setUser({\n  key: "[email protected]",\n  attributes: { department: "cardiology" }\n});\n\n// Will succeed only if user department matches record department based on policy\nconst records = await prisma.medicalRecord.findMany({\n  where: { department: "cardiology" }\n});\n'})}),"\n",(0,r.jsx)(n.h3,{id:"rebac-example",children:"ReBAC configuration"}),"\n",(0,r.jsxs)(n.p,{children:["ReBAC grants roles on specific resource instances, for example ",(0,r.jsx)(n.code,{children:"owner"})," of one file and ",(0,r.jsx)(n.code,{children:"viewer"})," of another. Turn on ",(0,r.jsx)(n.code,{children:"enableResourceSync"})," so that each created record becomes a resource instance in Permit, with the record ",(0,r.jsx)(n.code,{children:"id"})," as the instance key. Turn on ",(0,r.jsx)(n.code,{children:"enableDataFiltering"})," so that ",(0,r.jsx)(n.code,{children:"findMany"})," returns only the instances on which the user has the ",(0,r.jsx)(n.code,{children:"read"})," permission. If the user can't read any instance, ",(0,r.jsx)(n.code,{children:"findMany"})," returns an empty array."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'// Configure for ReBAC\nconst prisma = new PrismaClient().$extends(\n  createPermitClientExtension({\n    permitConfig: { token: "YOUR_API_KEY", pdp: "http://localhost:7766" },\n    enableAutomaticChecks: true,\n    enableResourceSync: true,\n    enableDataFiltering: true\n  })\n);\n\n// Set user for instance-level permissions\nprisma.$permit.setUser("[email protected]");\n\n// Will only succeed if the user has permission on this specific file instance\nconst file = await prisma.file.findUnique({\n  where: { id: "file-123" }\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The extension syncs resource instances only. Create role assignments and relationship tuples on the instances with the Permit API or an SDK. See ",(0,r.jsx)(n.a,{href:"/how-to/build-policies/rebac/building-rebac-policies",children:"Build ReBAC policies"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"combined-model-example",children:"Combined model configuration"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'// Configure for combined models (RBAC + ABAC + ReBAC)\nconst prisma = new PrismaClient().$extends(\n  createPermitClientExtension({\n    permitConfig: { token: "YOUR_API_KEY", pdp: "http://localhost:7766" },\n    enableAutomaticChecks: true,\n    enableResourceSync: true,\n    enableAttributeSync: true,\n    enableDataFiltering: true\n  })\n);\n'})}),"\n",(0,r.jsx)(n.h2,{id:"manual-permission-checks",children:"Check permissions manually"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"prisma.$permit.check(user, action, resource)"})," returns ",(0,r.jsx)(n.code,{children:"true"})," or ",(0,r.jsx)(n.code,{children:"false"}),". ",(0,r.jsx)(n.code,{children:"prisma.$permit.enforceCheck()"})," takes the same arguments and throws a ",(0,r.jsx)(n.code,{children:"PermitError"})," if the PDP denies the check. Both methods work without ",(0,r.jsx)(n.code,{children:"enableAutomaticChecks"})," and don't use the user from ",(0,r.jsx)(n.code,{children:"setUser()"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:'// Check permission manually\nconst canUpdateDocument = await prisma.$permit.check(\n  "[email protected]", // user\n  "update",           // action\n  "document"          // resource\n);\n\nif (canUpdateDocument) {\n  await prisma.document.update({\n    where: { id: "doc-123" },\n    data: { title: "Updated Title" }\n  });\n}\n\n// Or enforce check (throws if denied)\nawait prisma.$permit.enforceCheck(\n  "[email protected]",\n  "delete",\n  { type: "document", key: "doc-123" }\n);\nawait prisma.document.delete({ where: { id: "doc-123" } });\n'})}),"\n",(0,r.jsx)(n.h2,{id:"verify-that-the-extension-enforces-permissions",children:"Verify that the extension enforces permissions"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Add ",(0,r.jsx)(n.code,{children:"debug: true"}
1)," to ",(0,r.jsx)(n.code,{children:"permitConfig"})," and restart your application."]}),"\n",(0,r.jsxs)(n.li,{children:["Call ",(0,r.jsx)(n.code,{children:"setUser()"})," with a user who lacks the ",(0,r.jsx)(n.code,{children:"create"})," permission on a resource, and run a ",(0,r.jsx)(n.code,{children:"create"})," query on the matching model. The query throws a ",(0,r.jsx)(n.code,{children:"PermitError"})," with a ",(0,r.jsx)(n.code,{children:"Permission denied"})," message, and the log shows ",(0,r.jsx)(n.code,{children:"Permission result: DENIED"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"Open the audit log in the Permit dashboard. The denied check appears with the user, action, and resource."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/how-to/sync-users",children:"Sync users"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/how-to/enforce-permissions/data-filtering",children:"Filter data by permission"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/how-to/build-policies/rebac/building-rebac-policies",children:"Build ReBAC policies"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/sdk/nodejs/quickstart-nodejs",children:"Check permissions with the Node.js SDK"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/permitio/permit-prisma",children:"permit-prisma repository on GitHub"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},28453(e,n,i){i.d(n,{R:()=>c,x:()=>d});var s=i(96540);const r={},t=s.createContext(r);function c(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:c(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.