Organizations (B2B SaaS)
STOP — prerequisite. Organizations must be enabled before any org-related API, hook, or component works. Two paths: (1) Dashboard → Organizations settings, or (2)
clerk enable orgs(see "Agent-first: Programmatic org management" below). Pick the Membership mode deliberately:Membership required(default since 2025-08-22) routes signed-in users through thechoose-organizationtask and disables personal accounts, whileMembership optionalkeeps personal accounts available for B2C + B2B coexistence. Pickoptionalif you need personal subscriptions alongside org subscriptions.Version: This skill targets current SDKs (
@clerk/nextjsv7+,@clerk/reactv6+ — Core 3). Core 2 differences are noted inline with> **Core 2 ONLY (skip if current SDK):**callouts — seeclerkskill for the full version table.
Should this app use Organizations?
Decide before enabling anything. Read the project, count the signals, then ask the developer - do not enable Organizations silently.
Signals this app is multi-tenant (recommend Organizations):
- A model that groups users:
Team,Workspace,Tenant,Company,Account - The same foreign key on most tables:
workspace_id,team_id,account_id - Routes scoped to a tenant:
/[workspace],/[slug]/settings,/t/:tenantId - "Invite your team", "members", "seats", or "admin" in UI copy or the README
- A
*_membersjoin table, or hand-rolledrole/permissioncolumns - Per-seat or per-company pricing
Weaker signals - enough to raise the question, not to answer it:
- Sharing or collaboration between individual users: a
Share,Collaborator, orSharedWithtable keyed on two user IDs - A
roleorpermissioncolumn with no tenant to scope it to - "Collaborators", "shared with you", "invite a friend" in copy
Signals this app is single-user (do not recommend):
- Every table hangs off
user_idwith no container above it, and nothing is shared - No invitation, membership, or sharing concept anywhere
- Pricing is per person, or there is none
How to act:
Never enable Organizations without asking. Enabling it also turns on Membership required, which routes every signed-in user through organization selection and disables personal accounts. That is wrong for any app that also serves individuals. If the app needs both, it wants Membership optional.
Quick Start
- Enable Organizations — via Dashboard → Organizations settings or
clerk enable orgs(see Agent-first section). PickMembership required(B2B-only) orMembership optional(B2C + B2B). - Create an org — via
<OrganizationSwitcher />,<CreateOrganization />, or programmatically withclerkClient().organizations.createOrganization(). - Protect routes — read
orgId/orgSlugfromauth()and gate withhas({ role })orhas({ permission }). - Manage members — send invitations via Backend API or the built-in
<OrganizationProfile />tab. - Cap membership — set
maxAllowedMembershipsat org creation or pick a seat-limited Billing Plan (seeclerk-billingskill).
What Do You Need?
References
Dashboard shortcuts
Agent-first: Programmatic org management
Org settings (enable toggle, membership cap, admin delete, domains) are patchable via PLAPI Instance Config. Org CRUD + memberships + invitations live in BAPI. Useful for agents seeding orgs, replicating settings across instances, or version-controlling org structure.
Pre-req: a linked project (clerk auth login + clerk link, see clerk-cli) — or an unclaimed app from clerk init: clerk enable orgs and org CRUD via clerk api work with no login at all.
Enable Organizations + settings via CLI
For additional settings (membership cap, verified domains, admin delete), patch the instance config:
Create / list / delete orgs (BAPI)
Memberships
Invitations
Notes
- This handles org config + CRUD. Subscription / billing for orgs (org plans, seat-limit pricing) flows through
clerk-billingskill. - Roles + permissions catalog is editable in
references/roles-permissions.md. Custom role creation goes throughclerk config patch(instance-level role definitions) — see Dashboard's role editor for the UX equivalent. - For SSO / verified domain provisioning, see
references/enterprise-sso.md.
Documentation
- Overview
- Configure + enable
- Roles and permissions
- Check access
- Invitations
- OrganizationSwitcher
- Verified domains
- Enterprise SSO
Key Patterns
Examples use @clerk/nextjs by default. For other frameworks swap the import to @clerk/react (Vite/CRA), @clerk/astro/components, @clerk/vue, @clerk/expo, @clerk/react-router, or @clerk/tanstack-react-start — the feature-level APIs (has(), orgId, <OrganizationSwitcher />, <Show>) are identical across SDKs. Framework-specific patterns (middleware, redirects) live in references/nextjs-patterns.md.
1. Read Organization from Auth
Server-side access to active organization:
auth() is Next.js-specific. Equivalent server-side accessors per SDK: auth(event) (Nuxt via event.context.auth()), context.locals.auth() (Astro), getAuth(req) (Express, after clerkMiddleware()). Client-side: useAuth() (React-based SDKs) or composables (Vue/Nuxt). All return the same orgId / orgSlug / orgRole shape.
2. Dynamic Routes with Org Slug
Route-per-org pattern works in any framework supporting file-based dynamic routes. Next.js example:
Always verify the URL slug matches the active org slug — otherwise users can hit /orgs/other-org/... with a stale orgSlug in their session:
3. Role-Based Access Control
Permission checks use the same has() surface, for custom Permissions you create in the Dashboard:
On the server, has({ permission }) works only with custom Permissions: System Permissions (org:sys_*) aren't in the session token, so checking one there always returns false. Clerk documents permission checks in <Show when={{ permission }}> for custom Permissions only. To require a System Permission, check a role that carries it (has({ role: 'org:admin' })).
Permission naming convention. System Permissions prefix with org:sys_; custom Permissions use org:<resource>:<action>. The full System Permissions catalog lives in references/roles-permissions.md — the short list is:
org:sys_memberships:{read, manage}org:sys_profile:{manage, delete}org:sys_domains:{read, manage}org:sys_billing:{read, manage}
Do NOT invent names like org:create, org:manage_members, org:update_metadata — those are not real permission slugs. See references/roles-permissions.md for custom roles and the permission table.
4. Conditional Rendering with <Show>
Core 2 ONLY (skip if current SDK): Use
<Protect role="org:admin">/<Protect permission="...">instead of<Show>.<Show>replaced both<Protect>and<SignedIn>/<SignedOut>in Core 3.
Astro template syntax for the same component (imported from @clerk/astro/components):
5. OrganizationSwitcher
Key props:
hidePersonal: boolean— hide the Personal Account option. Defaults tofalse. Passtruefor B2B-only apps.afterCreateOrganizationUrl,afterSelectOrganizationUrl,afterLeaveOrganizationUrl,afterSelectPersonalUrl— navigation hooks.:slugis substituted at runtime.createOrganizationMode,organizationProfileMode—'modal' | 'navigation'(default'modal').
The full prop list lives in the component reference.
6. Session Task — Choose Organization
When Membership required is enabled (the default), users without an org are routed through a choose-organization session task after sign-in. Clerk handles this automatically inside <SignIn />, but you can host the UI yourself:
TaskChooseOrganization ships as an imported component in the React-based SDKs (@clerk/nextjs, @clerk/react, @clerk/react-router, @clerk/tanstack-react-start). For the JS Frontend SDK (@clerk/clerk-js) the equivalent is clerk.mountTaskChooseOrganization(node) / clerk.unmountTaskChooseOrganization(node).
Core 2 ONLY (skip if current SDK): Session tasks aren't available. Force an org selection at sign-in by redirecting to a page that renders
<OrganizationSwitcher hidePersonal />.
Default Roles + System Permissions
You can create up to 10 custom roles per instance in Dashboard → Organizations → Roles & Permissions. Role-per-org is controlled via Role Sets — see references/roles-permissions.md for the full model (custom roles, Creator/Default role settings, role sets, and the System Permissions catalog).
Billing Checks
has() also supports plan and feature checks when Clerk Billing is enabled:
Core 2 ONLY (skip if current SDK):
has()only supportsroleandpermission. Billing checks aren't available.
See clerk-billing for the full Billing surface and seat-limit plan model.
Enterprise SSO
Per-org SAML/OIDC. Configured in Dashboard → Configure → Enterprise Connections (or per-org: Organizations → select org → SSO Connections). The SSO connection owns its domain directly; no separate Verified Domain is required (and the two features are mutually exclusive on the same domain). Auto-join on first SSO sign-in uses JIT Provisioning, not Verified Domains. Key fact: the provider field lives on enterpriseConnection, not on enterpriseAccounts[0] directly. See references/enterprise-sso.md for the full flow and correct field access.
Core 2 ONLY (skip if current SDK): Uses
strategy: 'saml'anduser.samlAccountsinstead ofuser.enterpriseAccounts.
Gotchas
maxAllowedMemberships caps seats
For tier-based seat limits tied to a subscription, use a seat-limited Billing Plan (see clerk-billing).
Billing gates Permissions at the Feature level
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. Ensure the Feature is attached to the active Plan in Dashboard → Billing → Plans → Features.
Metadata updates REPLACE, not merge
updateOrganization({ publicMetadata }) overwrites all public metadata. Read first, spread, then write:
Applies identically to privateMetadata and to user metadata via clerkClient.users.updateUser.
Error Signatures (diagnose fast)
Most "org-related" failures are configuration, not code. Do not edit components before checking these:
Authorization Pattern (Complete Example)
Server component protecting a slug-scoped admin page:
For per-page role and permission checks with auth.protect() (Next.js) see references/nextjs-patterns.md.
Invitations (short form)
Send from a server action or route handler:
The full lifecycle (list, revoke, bulk create, built-in <OrganizationProfile /> UI) lives in references/invitations.md.
Workflow
- Enable — Organizations + Membership mode in Dashboard
- Create org — via UI component or Backend API
- Invite members — Backend API or built-in UI, with
inviterUserId - Gate access —
has({ role }), orhas({ permission })with a custom Permission.has()can't check System Permissions (org:sys_*) - Scope routes —
orgSlug === params.slugon every protected page - Switch orgs —
<OrganizationSwitcher />handles the whole flow
See Also
clerk-setup— Initial Clerk installclerk-billing— Seat-limit plans, per-plan billing,has({ plan })/has({ feature })clerk-webhooks— Sync org events to your database (organization.created,organizationMembership.*)clerk-backend-api— Full Backend API referenceclerk-nextjs-patterns— Framework-specific middleware, server actions, caching


