PermDock
Typed permissions for TypeScript apps, APIs, databases, and AI agents.
PermDock lets you define permissions once as typed references over the Zod, Valibot or ArkType schemas you already have, grant them to roles with portable conditions, and check them in React, React Native, Next.js, Hono, tRPC and MCP servers. The same conditions compile to SQL where clauses and Postgres Row Level Security policies, and the same decision object drives tool approvals in the Vercel AI SDK and the Claude Agent SDK.
import { definePermissions, resource, definePolicy, role, allow, subject, createPermDock } from 'permdock'
export const permissions = definePermissions({
post: resource(Post, { id: 'id', actions: ['read', 'update', 'delete'], collection: ['create', 'list'] }),
})
export const policy = definePolicy(permissions, {
roles: [
role('member', [
allow(permissions.post.read),
allow(permissions.post.create),
allow(permissions.post.update, { where: { authorId: subject.id } }),
]),
],
subject: (user: User | null) => user && { id: user.id, roles: user.roles },
})
const permdock = await createPermDock(policy, user)
permdock.can(permissions.post.update, post) // boolean
permdock.decide(permissions.post.delete, post) // { outcome: 'granted' | 'denied' | 'approval-required', ... }What makes it different
- Reference-based.
permissions.post.updateis a typed object with akey, ascopeand a schema. Go-to-definition works, renames are safe, and there are no template-literal unions for TypeScript 7 to chew through. - Standard-Schema-native. Resources are defined from any Standard Schema validator; instance types are inferred; untrusted inputs are validated at trust boundaries.
- Policy as data. Roles are arrays of
allow/denygrants with portable conditions. One condition evaluates in the browser, filters arrays, compiles to Drizzle / Prisma / Kyselywhere, and generates RLS. - Decisions, not booleans.
decide()returnsgranted,deniedorapproval-requiredwith reasons and alternatives; adapters turn that into Problem Details, MCP refusals and AI SDK approval states. - Non-blocking UI. Snapshots carry conditions to the client so
usePermissionanswers ownership checks offline and<Protected>never blocks a Next.js 16.3 instant navigation. - Agent-native. Two-principal subject (principal + actor + delegation), MCP, AI SDK and Claude Agent SDK adapters, an AuthZEN decision endpoint, skills and
llms.txt. - Authentication stays upstream. PermDock consumes verified material only: sessions, JWKS-verified JWTs via
permdock/jwt(with a FAPI 2.0 profile), Supabase, Clerk and Better Auth claims, MCPauthInfo, workload identities. Core never verifies a token.
Where to go next
- Installation and the quick start.
- Larger apps: define per feature, merge centrally, generate from RLS or OpenAPI.
- Adapters: one page per framework, runtime and data layer, each with its status and phase; includes your own CLI.
- Standards, the standards watch list and security.
- Comparison with CASL, Kilpi, permix, Cedar, OPA, the Zanzibar family and hosted PDPs, with a pick-by-use-case guide.
- Research and decision records explaining why PermDock looks the way it does.
Status
PermDock is in Phase 0: the product plan, this documentation tree, README.md, PRODUCT.md, AGENTS.md and the Fumadocs app at /docs exist; the permdock package does not yet. Every adapter and standards page carries a Status and Phase line. See the roadmap.