Clerk Organizations for B2B SaaS - create multi-tenant apps with org switching, role-based access, verified domains, and enterprise SSO...
Prerequisite: Enable Organizations in Clerk Dashboard first.
| Task | Link |
|---|---|
| Overview | https://clerk.com/docs/guides/organizations/overview |
| Org slugs in URLs | https://clerk.com/docs/guides/organizations/org-slugs-in-urls |
| Roles & permissions | https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions |
| Check access | https://clerk.com/docs/guides/organizations/control-access/check-access |
| Invitations | https://clerk.com/docs/guides/organizations/add-members/invitations |
| OrganizationSwitcher | https://clerk.com/docs/reference/components/organization/organization-switcher |
| Verified domains | https://clerk.com/docs/guides/organizations/verified-domains |
| Enterprise SSO | https://clerk.com/docs/guides/organizations/add-members/sso |
Server-side access to organization:
import { auth } from '@clerk/nextjs/server'
const { orgId, orgSlug } = await auth()
console.log(`Current org: ${orgSlug}`)
Create routes that accept org slug:
app/orgs/[slug]/page.tsx
app/orgs/[slug]/settings/page.tsx
Access the slug:
export default function DashboardPage({ params }: { params: { slug: string } }) {
return <div>Organization: {params.slug}</div>
}
Verify user has access to specific org:
import { auth } from '@clerk/nextjs/server'
export default async function ProtectedPage() {
const { orgId, orgSlug } = await auth()
if (!orgId) {
return <div>Not in an organization</div>
}
return <div>Welcome to {orgSlug}</div>
}
Check if user has specific role:
const { has } = await auth()
if (!has({ role: 'org:admin' })) {
return <div>Admin access required</div>
}
Let users switch between organizations:
import { OrganizationSwitcher } from '@clerk/nextjs'
export default function Nav() {
return (
<header>
<h1>Dashboard</h1>
<OrganizationSwitcher />
</header>
)
}
All new members get assigned a role:
| Role | Permissions |
|---|---|
org:admin |
Full access, manage members, settings |
org:member |
Limited access, read-only |
Custom roles can be created in the dashboard.
| Permission | Role |
|---|---|
org:create |
Can create new organizations |
org:manage_members |
Can invite/remove members (default: admin) |
org:manage_roles |
Can change member roles (default: admin) |
org:update_metadata |
Can update org metadata (default: admin) |
Complete example protecting a route:
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function AdminPage({ params }: { params: { slug: string } }) {
const { orgSlug, has } = await auth()
// Verify user is in the org
if (orgSlug !== params.slug) {
redirect('/dashboard')
}
// Check if admin
if (!has({ role: 'org:admin' })) {
redirect(`/orgs/${orgSlug}`)
}
return <div>Admin settings for {orgSlug}</div>
}
| Symptom | Cause | Solution |
|---|---|---|
orgSlug is undefined |
Not calling await auth() |
Use const { orgSlug } = await auth() |
| Role check always fails | Not awaiting auth() |
Add await before auth() |
| Users can access other orgs | Not checking orgSlug matches URL | Verify orgSlug === params.slug |
| Org not appearing in switcher | Organizations not enabled | Enable in Clerk Dashboard → Organizations |
| Invitations not working | Wrong role configuration | Ensure members have invite role permissions |