clerk-orgs▌
clerk/skills · updated Apr 8, 2026
Multi-tenant B2B SaaS with organization switching, role-based access control, and enterprise SSO.
- ›Supports dynamic org-based routing via URL slugs, role-based access checks ( org:admin , org:member ), and custom role creation through the dashboard
- ›Includes OrganizationSwitcher component for user-facing org selection and <Show> conditional rendering for role-gated UI
- ›Provides server-side organization context via auth() helper with membership verification and permission checks
Organizations (B2B SaaS)
Prerequisite: Enable Organizations in Clerk Dashboard first.
Version: Check
package.jsonfor the SDK version — seeclerkskill for the version table. Core 2 differences are noted inline with> **Core 2 ONLY (skip if current SDK):**callouts.
Quick Start
- Create an organization via dashboard or through Clerk API
- Use OrganizationSwitcher to let users switch between orgs
- Protect routes using orgSlug from URL and role checks
Documentation Reference
| 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 |
Key Patterns
1. Get Organization from Auth
Server-side access to organization:
import { auth } from '@clerk/nextjs/server'
const { orgId, orgSlug } = await auth()
console.log(`Current org: ${orgSlug}`)
2. Dynamic Routes with Org Slug
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>
}
3. Check Organization Membership
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>
}
4. Role-Based Access Control
Check if user has specific role:
const { has } = await auth()
if (!has({ role: 'org:admin' })) {
return <div>Admin access required</div>
}
Custom Permissions
Create custom roles and permissions in Dashboard → Organizations → Roles. Permission format: org:resource:action.
// Server component — check a custom billing permission
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function BillingPage() {
const { has } = await auth()
if (!has({ permission: 'org:billing:manage' })) {
redirect('/unauthorized')
}
return <BillingDashboard />
}
// Middleware — protect an entire route segment
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isBillingRoute = createRouteMatcher(['/orgs/:slug/billing(.*)'])
export default clerkMiddleware(async (auth, req) => {
if (isBillingRoute(req)) {
await auth.protect({ permission: 'org:billing:manage' })
}
})
// Client component — conditional rendering
import { Show } from '@clerk/nextjs'
<Show when={{ permission: 'org:billing:manage' }}>
<BillingSettings />
</Show>
Permission naming convention: org:resource:action (e.g., org:billing:manage, org:reports:view, org:api_keys:create). Always prefix with org: for organization-scoped permissions.
5. OrganizationSwitcher Component
Let users switch between organizations:
import { OrganizationSwitcher } from '@clerk/nextjs'
export default function Nav() {
return (
<header>
<h1>Dashboard</h1>
<OrganizationSwitcher />
</header>
)
}
Default Roles
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.
Default Permissions
| 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) |
Authorization Pattern
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>
}
Conditional Rendering with <Show>
Use <Show> for role-based conditional rendering in client components:
import { Show } from '@clerk/nextjs'
<Show when={{ role: 'org:admin' }}>
<AdminPanel />
</Show>
<Show when={{ permission: 'org:billing:manage' }}>
<BillingSettings />
</Show>
Core 2 ONLY (skip if current SDK): Use
<Protect role="org:admin">and<Protect permission="org:billing:manage">instead of<Show>.
Billing Checks
The has() method supports billing plan and feature checks for gating access:
const { has } = await auth()
has({ plan: 'gold' }) // Check subscription plan
has({ feature: 'widgets' }) // Check feature entitlement
Core 2 ONLY (skip if current SDK):
has()only supportsroleandpermissionparameters. Billing checks are not available.
Session Tasks
When personal accounts are disabled, users must choose an organization after sign-in. This is handled by the choose-organization session task:
import { TaskChooseOrganization } from '@clerk/nextjs'
// Renders when user must select an org
<TaskChooseOrganization redirectUrlComplete="/dashboard" />
Core 2 ONLY (skip if current SDK): Session tasks are not available. Use
<OrganizationSwitcher>for org selection.
Enterprise SSO
Organizations can use Enterprise SSO (SAML/OIDC) for member authentication:
// Strategy name for Enterprise SSO
strategy: 'enterprise_sso'
// Access enterprise accounts on user object
user.enterpriseAccounts
Core 2 ONLY (skip if current SDK): Uses
strategy: 'saml'instead ofstrategy: 'enterprise_sso', anduser.samlAccountsinstead ofuser.enterpriseAccounts.
Configuring Enterprise SSO per Organization
Enterprise SSO is configured per organization in the Clerk Dashboard under Organizations > SSO Connections. Steps:
- Go to Dashboard → Organizations → select the org → SSO Connections
- Add a SAML or OIDC connection with the customer's IdP metadata
- Set the verified domain for the org — Clerk verifies ownership via DNS TXT record
- Once verified, users signing in with that email domain are automatically routed to the SSO flow and joined to the org
// Check if the active user authenticated via enterprise SSO
import { currentUser } from '@clerk/nextjs/server'
const user = await currentUser()
const ssoAccount = user?.enterpriseAccounts?.[0]
if (ssoAccount) {
console.log(`SSO provider: ${ssoAccount.provider}`)
console.log(`SSO domain: ${ssoAccount.emailAddress}`)
}
Key facts:
- Strategy name:
enterprise_sso(used insignIn.supportedFirstFactors) - Domain verification required: org claims a domain, Clerk verifies via DNS TXT record
- Users with a verified email domain auto-join the org on first SSO sign-in
- Each org can have multiple SSO connections (e.g., SAML + OIDC)
Gotchas
Capping Org Seats with maxAllowedMemberships
Pass maxAllowedMemberships when creating an org to cap the number of seats. Attempts to add members beyond the cap will return an error.
const clerk = await clerkClient()
const org = await clerk.organizations.createOrganization({
name: 'Acme Corp',
createdBy: userId,
maxAllowedMemberships: 10,
})
You can also update the cap after creation:
await clerk.organizations.updateOrganization(orgId, {
maxAllowedMemberships: 25,
})
Billing Gates Permissions
When Clerk Billing is enabled, has({ permission: 'org:posts:edit' }) returns false if the Feature associated with that permission is not included in the organization's active Plan — even if the user has the permission assigned via their role. Billing gates permissions at the feature level. Ensure the required Feature is attached to the active Plan in Dashboard → Billing → Plans → Features before debugging role assignments.
Metadata Overwrites (Not Merges)
updateOrganization({ publicMetadata: { tier: 'enterprise' } }) REPLACES all public metadata, not merges it. Read first, spread, then write.
Wrong:
await clerk.organizations.updateOrganization(orgId, {
publicMetadata: { newField: 'value' },
})
Right:
const org = await clerk.organizations.getOrganization(orgId)
await clerk.organizations.updateOrganization(orgId, {
publicMetadata: { ...org.publicMetadata, newField: 'value' },
})
The same rule applies to privateMetadata and to user metadata via clerkClient.users.updateUser.
Common Pitfalls
| 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 |
Invitation API
Send an Invitation (Server Action / Route Handler)
import { clerkClient } from '@clerk/nextjs/server'
import { auth } from '@clerk/nextjs/server'
export async function inviteMember(organizationId: string, emailAddress: string, role: string) {
const { has } = await auth()
if (!has({ permission: 'org:sys_memberships:manage' })) {
throw new Error('Not authorized to invite members')
}
const clerk = await clerkClient()
const invitation = await clerk.organizations.createOrganizationInvitation({
organizationId,
emailAddress,
role, // e.g. 'org:member' or 'org:admin'
redirectUrl: 'https://yourapp.com/accept-invite',
})
return invitation
}
List Pending Invitations
const clerk = await clerkClient()
const { data: invitations } = await clerk.organizations.getOrganizationInvitationList({
organizationId,
status: ['pending'], // 'pending' | 'accepted' | 'revoked'
})
Revoke an Invitation
await clerk.organizations.revokeOrganizationInvitation({
organizationId,
invitationId,
requestingUserId: userId,
})
Built-in Invite UI
<OrganizationSwitcher /> includes a built-in member invitation UI when personal accounts are hidden:
<OrganizationSwitcher
hidePersonal
afterCreateOrganizationUrl="/orgs/:slug/dashboard"
afterSelectOrganizationUrl="/orgs/:slug/dashboard"
/>
The <OrganizationProfile /> component also provides a full members management tab with invitation and role management.
Workflow
- Setup - Enable Organizations in Clerk Dashboard
- Create org - Users create org or admin creates via API
- Add members - Send invitations or add directly
- Assign roles - Default member role, promote to admin as needed
- Build protected routes - Use auth() to check orgSlug and roles
- Use OrganizationSwitcher - Let users switch between orgs
See Also
clerk-setup- Initial Clerk installclerk-webhooks- Sync org events to your databaseclerk-backend-api- Manage members programmatically
Ratings
4.5★★★★★10 reviews- ★★★★★Shikha Mishra· Oct 10, 2024
clerk-orgs is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Piyush G· Sep 9, 2024
Keeps context tight: clerk-orgs is the kind of skill you can hand to a new teammate without a long onboarding doc.
- ★★★★★Chaitanya Patil· Aug 8, 2024
Registry listing for clerk-orgs matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★Sakshi Patil· Jul 7, 2024
clerk-orgs reduced setup friction for our internal harness; good balance of opinion and flexibility.
- ★★★★★Ganesh Mohane· Jun 6, 2024
I recommend clerk-orgs for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★Oshnikdeep· May 5, 2024
Useful defaults in clerk-orgs — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Dhruvi Jain· Apr 4, 2024
clerk-orgs has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Rahul Santra· Mar 3, 2024
Solid pick for teams standardizing on skills: clerk-orgs is focused, and the summary matches what you get after install.
- ★★★★★Pratham Ware· Feb 2, 2024
We added clerk-orgs from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Yash Thakker· Jan 1, 2024
clerk-orgs fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.