Dyrected
Guides

Role-Based Access Control

Restrict what different users can read, create, edit, and delete based on their role.

1. Add a role field to your users collection

// dyrected.config.ts  (same for both frameworks)
{
  slug: 'users',
  auth: true,
  fields: [
    { name: 'name', type: 'text', required: true },
    {
      name: 'role',
      type: 'select',
      options: ['admin', 'editor', 'viewer'],
      defaultValue: 'viewer',
      access: {
        update: ({ user }) => user?.role === 'admin',  // only admins can change roles
      },
    },
  ],
}

2. Lock down the users collection

// dyrected.config.ts  (same for both frameworks)
{
  slug: 'users',
  auth: true,
  access: {
    read:   ({ user }) => !!user,
    create: () => false,                                    // use invite instead
    update: ({ user, id }) => user?.role === 'admin' || user?.id === id,
    delete: ({ user }) => user?.role === 'admin',
  },
}

3. Apply roles to a content collection

// dyrected.config.ts  (same for both frameworks)
{
  slug: 'posts',
  access: {
    read: ({ user, doc }) => doc?.status === 'published' || !!user,
    create: ({ user }) => user?.role === 'admin' || user?.role === 'editor',
    update: ({ user, doc }) => {
      if (user?.role === 'admin') return true
      return user?.role === 'editor' && doc?.createdBy === user?.id
    },
    delete: ({ user }) => user?.role === 'admin',
  },
  hooks: {
    beforeChange: [
      ({ data, operation, user }) => {
        if (operation === 'create') return { ...data, createdBy: user?.id }
        return data
      },
    ],
  },
  fields: [
    { name: 'title',     type: 'text' },
    { name: 'content',   type: 'richtext' },
    { name: 'status',    type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
    { name: 'createdBy', type: 'relationship', relationTo: 'users' },
  ],
}

4. Hide sensitive fields from lower roles

// dyrected.config.ts  (same for both frameworks)
{
  name: 'internalNotes',
  type: 'textarea',
  access: {
    read:   ({ user }) => user?.role === 'admin' || user?.role === 'editor',
    update: ({ user }) => user?.role === '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

import { createClient } from '@dyrected/sdk'

const client = createClient({ baseUrl: '/dyrected' })
client.setToken(userToken)

const user = await client.collection('users').me()
// user.role === 'admin' | 'editor' | 'viewer'
// In a Server Component
import { createClient } from '@dyrected/sdk'

const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL! })

async function getCurrentUser() {
  const token = cookies().get('dyrected-token')?.value
  if (!token) return null
  
  client.setToken(token)
  try {
    return await client.collection('users').me()
  } catch {
    return null
  }
}

// In your component:
const user = await getCurrentUser()
if (user?.role === 'admin') { /* show admin controls */ }
<script setup lang="ts">
const { user } = useDyrectedAuth('users')
</script>

<template>
  <AdminControls v-if="user?.role === 'admin'" />
  <EditorControls v-else-if="user?.role === 'editor'" />
</template>

Summary

RoleRead postsCreateEdit ownEdit anyDeleteManage users
admin
editor
viewer✅ (published)
Anonymous✅ (published)

See Access Control for the full reference on access function signatures.

On this page