Invite-Only Registration
Restrict sign-up so new users can only join via invitation.
Lock sign-up so people can only join through an invitation you send — no public registration form. You'll turn off self-registration, send invites from an admin, and let invitees set their password from an emailed link.
1. Disable open registration
The first step is to stop anyone from creating their own account. Set the users collection's create access to always return false, so the only way in is an invite:
// dyrected.config.ts (same for both frameworks)
{
slug: 'users',
auth: true,
access: {
create: () => false, // no self-registration
read: ({ user }) => !!user,
update: ({ user, doc }) => user?.sub === doc?.id || user?.roles?.includes('admin'),
delete: ({ user }) => user?.roles?.includes('admin'),
},
fields: [
{ name: 'name', label: 'Name', type: 'text' },
{ name: 'roles', label: 'Roles', type: 'multiSelect', 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 token (a JWT — a tamper-proof signed token) marked purpose: 'invite' and emails it. In development, when you haven't configured an email provider yet, the link is printed to the console via Ethereal, a built-in dev mail catcher — so you can test the flow without sending real email.
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
The invite email is a built-in template you can override. Provide an invite function under email.templates that receives the token and returns the HTML. The send callback is your email provider — see Sending Email for the full setup; here it is wired to Resend:
// dyrected.config.ts (same for both frameworks)
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
export default defineConfig({
email: {
from: '[email protected]',
send: async ({ to, subject, html }) => {
await resend.emails.send({ from: '[email protected]', to, subject, html })
},
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.