Dyrected
Guides

Invite-Only Registration

Restrict sign-up so new users can only join via invitation.

1. Disable open registration

// dyrected.config.ts  (same for both frameworks)
{
  slug: 'users',
  auth: true,
  access: {
    create: () => false,   // no self-registration
    read:   ({ user }) => !!user,
    update: ({ user, doc }) => user?.id === doc.id || user?.role === 'admin',
    delete: ({ user }) => user?.role === 'admin',
  },
  fields: [
    { name: 'name', type: 'text' },
    { name: 'role', type: 'select', options: ['member', 'admin'], defaultValue: 'member' },
  ],
}

With create: () => false, POST /api/collections/users without a valid invite token returns 403.


2. Send an invitation

import { createClient } from '@dyrected/sdk'

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

await client.collection('users').invite('[email protected]')
// app/admin/invite/actions.ts
'use server'
import { createClient } from '@dyrected/sdk'

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

export async function inviteUser(email: string, adminToken: string) {
  client.setToken(adminToken)
  return client.collection('users').invite(email)
}
// Using the auth composable in a component or server action
const { token } = useDyrectedAuth('users')
const client = useDyrected()

async function inviteUser(email: string) {
  client.setToken(token.value)
  return client.collection('users').invite(email)
}

Dyrected signs a 7-day JWT with purpose: 'invite' and emails it. In development the link is logged to the console via Ethereal — no email config needed.


3. Accept the invitation

// app/accept-invite/page.tsx
'use client'
import { useSearchParams, useRouter } from 'next/navigation'
import { createClient } from '@dyrected/sdk'

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

export default function AcceptInvitePage() {
  const params = useSearchParams()
  const router = useRouter()
  const inviteToken = params.get('token') ?? ''

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const form = e.currentTarget
    const password = (form.elements.namedItem('password') as HTMLInputElement).value
    const name     = (form.elements.namedItem('name')     as HTMLInputElement).value

    const { token } = await client.collection('users').acceptInvite(inviteToken, password, { name })
    document.cookie = `dyrected-token=${token}; path=/`
    router.push('/dashboard')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name"     type="text"     placeholder="Your name"      required />
      <input name="password" type="password" placeholder="Choose a password" required />
      <button type="submit">Join</button>
    </form>
  )
}
<!-- pages/accept-invite.vue -->
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const inviteToken = route.query.token as string

const name     = ref('')
const password = ref('')

async function submit() {
  const { token } = await useDyrected().collection('users').acceptInvite(
    inviteToken, 
    password.value, 
    { name: name.value }
  )
  
  useDyrectedAuth('users').setToken(token)
  router.push('/dashboard')
}
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="name"     type="text"     placeholder="Your name"       required />
    <input v-model="password" type="password" placeholder="Choose a password" required />
    <button type="submit">Join</button>
  </form>
</template>
const { token, user } = await client.collection('users').acceptInvite(
  tokenFromEmailLink,
  'their-chosen-password',
  { name: 'Jane Smith' }
)

4. Customise the invite email

// dyrected.config.ts  (same for both frameworks)
export default defineConfig({
  email: {
    from: '[email protected]',
    send: async ({ to, subject, html }) => { /* your provider */ },
    templates: {
      invite: ({ token, invitedByEmail }) => ({
        subject: `You've been invited to MyApp`,
        html: `
          <p>${invitedByEmail ?? 'Someone'} has invited you to join MyApp.</p>
          <p><a href="https://myapp.com/accept-invite?token=${token}">Accept your invitation</a></p>
          <p>This link expires in 7 days.</p>
        `,
      }),
    },
  },
})

See Email for all template options and production provider examples.

On this page