Most AI applications eventually need multi-tenancy — workspaces, teams, or organisations where users share data and resources. Clerk's Organizations feature handles the membership model, role assignment, and permission checks so you don't build this infrastructure yourself.
Enabling Organizations
In the Clerk Dashboard, go to Organizations and enable the feature. Choose whether users can create their own organizations or only be invited. Set the roles you need (Clerk provides admin and member by default; you can add custom roles).
Creating and Joining Organizations
// Client component — create an organization
'use client';
import { useOrganizationList } from '@clerk/nextjs';
export function CreateOrgButton() {
const { createOrganization } = useOrganizationList();
const handleCreate = async () => {
await createOrganization({ name: 'Acme Corp' });
};
return <button onClick={handleCreate}>Create Organisation</button>;
}// Show the organization switcher UI
import { OrganizationSwitcher } from '@clerk/nextjs';
export function OrgSwitcher() {
return (
<OrganizationSwitcher
afterCreateOrganizationUrl="/org/:id/dashboard"
afterSelectOrganizationUrl="/org/:id/dashboard"
/>
);
}Reading the Active Organization Server-Side
// app/api/org-documents/route.ts
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId, orgId, orgRole } = auth();
if (!userId || !orgId) {
return Response.json({ error: 'No active organization' }, { status: 401 });
}
// orgId scopes the data query to the active org
const docs = await getDocumentsForOrg(orgId);
return Response.json(docs);
}Permission Checks
// Check role in server component
import { auth } from '@clerk/nextjs/server';
export default async function AdminPage() {
const { orgRole } = auth();
if (orgRole !== 'org:admin') {
return <p>Admin access required.</p>;
}
return <AdminDashboard />;
}
// Check fine-grained permission
import { auth } from '@clerk/nextjs/server';
export async function DELETE(req: Request) {
const { has } = auth();
if (!has({ permission: 'org:documents:delete' })) {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
// proceed with deletion
}Clerk custom permissions follow the format org:resource:action. Define them in the Clerk Dashboard under Organizations > Roles & Permissions. Assign permissions to roles, then check them with has({ permission: '...' }).Syncing Organization Data to Your Database
When a user joins or leaves an organization, or their role changes, you need to update your database. Use Clerk webhooks to sync these events.
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
export async function POST(req: Request) {
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
const headerPayload = headers();
const svixId = headerPayload.get('svix-id')!;
const svixTimestamp = headerPayload.get('svix-timestamp')!;
const svixSignature = headerPayload.get('svix-signature')!;
const payload = await req.text();
const event = wh.verify(payload, {
'svix-id': svixId,
'svix-timestamp': svixTimestamp,
'svix-signature': svixSignature,
}) as { type: string; data: any };
if (event.type === 'organizationMembership.created') {
await db.insert(orgMembers).values({
orgId: event.data.organization.id,
userId: event.data.public_user_data.user_id,
role: event.data.role,
});
}
if (event.type === 'organizationMembership.deleted') {
await db.delete(orgMembers)
.where(and(
eq(orgMembers.orgId, event.data.organization.id),
eq(orgMembers.userId, event.data.public_user_data.user_id),
));
}
return Response.json({ received: true });
}| Metadata | Value |
|---|---|
| Title | Clerk Multi-Tenant Organizations: Roles, Permissions, and Member Management |
| Tool | Clerk |
| Primary SEO keyword | clerk organizations multi-tenant |
| Secondary keywords | clerk roles permissions, clerk organization membership, clerk webhook sync, clerk orgId |
| Estimated read time | 8 minutes |
| Research date | 2026-04-14 |