Core ConceptsAuthenticationPermissions

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:

prisma/schema.prisma
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:

arkos.config.ts
import { defineConfig } from "arkos";

export default defineConfig({
  authentication: {
    mode: "dynamic",
    // ... rest of your config unchanged
  },
});
arkos.config.ts
import { ArkosConfig } from "arkos";

const arkosConfig: ArkosConfig = {
  authentication: {
    mode: "dynamic",
    // ... rest of your config unchanged
  },
};

export default arkosConfig;
src/app.ts
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:

src/modules/post/post.policy.ts
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/:id

Or 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

  1. Add AuthRole, AuthPermission, UserRole models to your schema
  2. Replace role/roles enum field on User with roles UserRole[]
  3. Run arkos prisma generate
  4. Change mode to "dynamic" in your config
  5. Create AuthRole records matching your previous enum values
  6. Create AuthPermission records based on your existing rules
  7. Assign users to roles via UserRole
  8. Remove roles from your policy rules — they'll be ignored anyway