Dynamic
Dynamic mode is Arkos's database-driven permission system. Roles and permissions live in AuthRole, AuthPermission, and UserRole models and can be created, updated, and assigned at runtime without a redeploy — making it the right choice for multi-tenant apps, SaaS platforms, or any system where roles change frequently.
Before using Dynamic mode make sure you have authentication configured. See Authentication Setup.
User Model
Dynamic mode replaces the role/roles enum field with a UserRole relation, and adds three required models:
model User {
// ... required Arkos fields
roles UserRole[]
// your own fields
email String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AuthRole {
id String @id @default(uuid())
name String @unique
permissions AuthPermission[]
users UserRole[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AuthPermission {
id String @id @default(uuid())
resource String
action String
roleId String
role AuthRole @relation(fields: [roleId], references: [id])
@@unique([resource, action, roleId])
}
model UserRole {
id String @id @default(uuid())
userId String
roleId String
user User @relation(fields: [userId], references: [id])
role AuthRole @relation(fields: [roleId], references: [id])
@@unique([userId, roleId])
}See the full required User model at User Model Authentication Setup.
Configuration
Change mode to "dynamic" — everything else stays the same as the base Authentication Setup:
import { defineConfig } from "arkos";
export default defineConfig({
authentication: {
mode: "dynamic",
// ... rest of your config unchanged
},
});import { ArkosConfig } from "arkos";
const arkosConfig: ArkosConfig = {
authentication: {
mode: "dynamic",
// ... rest of your config unchanged
},
};
export default arkosConfig;import arkos from "arkos";
arkos.init({
authentication: {
mode: "dynamic",
// ... rest of your config unchanged
},
});Defining Dynamic Permissions
Same ArkosPolicy and .auth.ts API as Static mode — the only difference is that roles inside rules are ignored at enforcement time. Actual enforcement comes from database records instead. Define your policy with names and descriptions for discovery, and skip the roles — or keep roles: ["*"] where all authenticated users should have access:
import { ArkosPolicy } from "arkos";
const postPolicy = ArkosPolicy("post")
.rule("Create", { name: "Create Post", description: "Create new posts" })
.rule("Update", { name: "Update Post" })
.rule("Delete", { name: "Delete Post" })
.rule("View", { roles: ["*"] }); // * still works — all authenticated users
export default postPolicy;Wiring to routes is identical to Static mode — see Static Mode — Using Permissions in Routes.
Managing Permissions
Arkos auto-generates full CRUD endpoints for AuthRole, AuthPermission, and UserRole:
GET /api/auth-roles
POST /api/auth-roles
PATCH /api/auth-roles/:id
DELETE /api/auth-roles/:id
GET /api/auth-permissions
POST /api/auth-permissions
PATCH /api/auth-permissions/:id
DELETE /api/auth-permissions/:id
GET /api/user-roles
POST /api/user-roles
DELETE /api/user-roles/:idOr manage them programmatically:
const adminRole = await prisma.authRole.create({ data: { name: "Admin" } });
const editorRole = await prisma.authRole.create({ data: { name: "Editor" } });
await prisma.authPermission.createMany({
data: [
{ resource: "post", action: "Create", roleId: editorRole.id },
{ resource: "post", action: "Update", roleId: editorRole.id },
{ resource: "post", action: "Delete", roleId: adminRole.id },
],
});
await prisma.userRole.create({
data: { userId: user.id, roleId: adminRole.id },
});Imperative Checks
ArkosPolicy can* methods work in Dynamic mode too, checking against database permissions. See Fine-Grained Access Control for full usage.
Imperative checks in fine-grained access control also work with auth config files. See the full guide at Fine-Grained Access Control.
Migrating from Static
- Add
AuthRole,AuthPermission,UserRolemodels to your schema - Replace
role/rolesenum field onUserwithroles UserRole[] - Run
arkos prisma generate - Change
modeto"dynamic"in your config - Create
AuthRolerecords matching your previous enum values - Create
AuthPermissionrecords based on your existing rules - Assign users to roles via
UserRole - Remove
rolesfrom your policy rules — they'll be ignored anyway