Role-Based Access Control
Restrict what different users can read, create, edit, and delete based on their role.
Access control in Dyrected comes down to two things: giving each user roles, then writing small access functions that decide what those roles can do. This guide walks through both, from the roles field up to per-field restrictions and a frontend role check.
1. Add a roles field to your users collection
Every auth collection gets a roles field automatically — an array of role strings on each user. Declare it yourself when you want to set the available options and lock down who can change it, so only admins can reassign roles:
// dyrected.config.ts (same for both frameworks)
{
slug: 'users',
auth: true,
fields: [
{ name: 'name', label: 'Name', type: 'text', required: true },
{
name: 'roles',
label: 'Roles',
type: 'multiSelect',
options: ['admin', 'editor', 'viewer'],
defaultValue: [],
access: {
update: ({ user }) => user?.roles?.includes('admin'), // only admins can change roles
},
},
],
}2. Lock down the users collection
With roles in place, restrict the users collection itself so nobody can self-register or edit another person's account. Each access function receives the signed-in user and returns whether the action is allowed:
// dyrected.config.ts (same for both frameworks)
{
slug: 'users',
auth: true,
access: {
read: ({ user }) => !!user,
create: () => false, // use invite instead
update: ({ user, doc }) => user?.roles?.includes('admin') || user?.sub === doc?.id,
delete: ({ user }) => user?.roles?.includes('admin'),
},
}3. Apply roles to a content collection
Now put those roles to work on real content. This posts collection lets anyone read published posts, lets editors create and manage their own, and reserves full control for admins. The "edit own" rule leans on createdBy, which Dyrected injects and stamps with the author's id automatically on create — you don't declare it:
// dyrected.config.ts (same for both frameworks)
{
slug: 'posts',
access: {
read: ({ user, doc }) => doc?.status === 'published' || !!user,
create: ({ user }) => user?.roles?.includes('admin') || user?.roles?.includes('editor'),
update: ({ user, doc }) => {
if (user?.roles?.includes('admin')) return true
return user?.roles?.includes('editor') && doc?.createdBy === user?.sub
},
delete: ({ user }) => user?.roles?.includes('admin'),
},
fields: [
{ name: 'title', label: 'Title', type: 'text' },
{ name: 'content', label: 'Content', type: 'richText' },
{ name: 'status', label: 'Status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
],
}4. Hide sensitive fields from lower roles
Access rules also work on a single field, not just the whole collection. Attach access to a field to keep it out of reach for lower roles:
// dyrected.config.ts (same for both frameworks)
{
name: 'internalNotes',
type: 'textarea',
access: {
read: ({ user }) => user?.roles?.includes('admin') || user?.roles?.includes('editor'),
update: ({ user }) => user?.roles?.includes('admin'),
},
}Fields stripped by read access never appear in API responses for that user — they're also hidden in the Admin UI.
5. Check the role on the frontend
Access rules protect your data on the server. On the frontend you'll often want to match them — showing admin controls only to admins, for example. Read the signed-in user and check their roles. Because me() reads the full user record, roles is always populated here:
import { createClient } from '@dyrected/sdk'
const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL! })
client.setToken(userToken)
const user = await client.collection('users').me()
// user.roles is an array, e.g. ['editor']// Server action protection
import { cookies } from 'next/headers'
import { getDyrectedClient } from '@dyrected/next'
const client = getDyrectedClient()
client.setToken(cookies().get('dyrected-token')?.value ?? '')
const user = await client.collection('users').me()
if (!user?.roles?.includes('admin')) {
throw new Error('Forbidden')
}<script setup lang="ts">
const { user } = useDyrectedAuth('users')
</script>
<template>
<AdminControls v-if="user?.roles?.includes('admin')" />
<EditorControls v-else-if="user?.roles?.includes('editor')" />
</template>Summary
| Role | Read posts | Create | Edit own | Edit any | Delete | Manage users |
|---|---|---|---|---|---|---|
admin | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
editor | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
viewer | ✅ (published) | ❌ | ❌ | ❌ | ❌ | ❌ |
| Anonymous | ✅ (published) | ❌ | ❌ | ❌ | ❌ | ❌ |
See Access Control for the full reference on access function signatures.