clerk-orgs

Organizations (B2B SaaS)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "clerk-orgs" with this command: npx skills add clerk/skills/clerk-skills-clerk-orgs

Organizations (B2B SaaS)

STOP — Dashboard-only prerequisite. Organizations must be enabled in the Clerk Dashboard before any org-related API, hook, or component works. Open Dashboard → Organizations settings and enable Organizations. Pick the Membership mode deliberately: Membership required (default since 2025-08-22) routes signed-in users through the choose-organization task and disables personal accounts, while Membership optional keeps personal accounts available for B2C + B2B coexistence. Pick optional if you need personal subscriptions alongside org subscriptions.

Version: This skill targets current SDKs (@clerk/nextjs v7+, @clerk/react v6+ — Core 3). Core 2 differences are noted inline with > Core 2 ONLY (skip if current SDK): callouts — see clerk skill for the full version table.

Quick Start

  • Enable Organizations — Dashboard → Organizations settings. Pick Membership required (B2B-only) or Membership optional (B2C + B2B). Dashboard-only; no CLI path.

  • Create an org — via <OrganizationSwitcher /> , <CreateOrganization /> , or programmatically with clerkClient().organizations.createOrganization() .

  • Protect routes — read orgId / orgSlug from auth() and gate with has({ role }) or has({ permission }) .

  • Manage members — send invitations via Backend API or the built-in <OrganizationProfile /> tab.

  • Cap membership — set maxAllowedMemberships at org creation or pick a seat-limited Billing Plan (see clerk-billing skill).

What Do You Need?

Task Reference

System permissions catalog, custom roles, role sets references/roles-permissions.md

Invitation lifecycle (create, list, revoke, built-in UI) references/invitations.md

Enterprise SSO setup, provider field access, domain verification references/enterprise-sso.md

Next.js adaptations for orgs (role/permission middleware, slug invariants, orgId-scoped writes) references/nextjs-patterns.md

References

Reference Description

references/roles-permissions.md

Default + custom roles, System Permissions catalog, permission naming

references/invitations.md

Backend API for invitations + built-in UI

references/enterprise-sso.md

SAML/OIDC per-org, domain verification, correct field access

references/nextjs-patterns.md

Next.js adaptations specific to orgs. For generic Next.js patterns see clerk-nextjs-patterns skill.

Dashboard shortcuts

Action URL

Enable Organizations + Membership mode https://dashboard.clerk.com/last-active?path=organizations-settings

Manage roles + permissions https://dashboard.clerk.com/last-active?path=organizations-settings/roles

Create/edit an organization https://dashboard.clerk.com/last-active?path=organizations

Webhooks for org events https://dashboard.clerk.com/last-active?path=webhooks

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:

import { auth } from '@clerk/nextjs/server'

const { orgId, orgSlug, orgRole } = await auth() if (!orgId) { // user has no active org — either not in any, or viewing Personal Account }

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.

  1. Dynamic Routes with Org Slug

Route-per-org pattern works in any framework supporting file-based dynamic routes. Next.js example:

app/orgs/[slug]/page.tsx app/orgs/[slug]/settings/page.tsx

Always verify the URL slug matches the active org slug — otherwise users can hit /orgs/other-org/... with a stale orgSlug in their session:

export default async function OrgPage({ params }: { params: { slug: string } }) { const { orgSlug } = await auth() if (orgSlug !== params.slug) { redirect('/dashboard') // or whatever your "no-access" flow is } return <div>Welcome to {orgSlug}</div> }

  1. Role-Based Access Control

const { has } = await auth()

if (!has({ role: 'org:admin' })) { return <div>Admin access required</div> }

Permission checks use the same has() surface:

if (!has({ permission: 'org:sys_memberships:manage' })) { redirect('/unauthorized') }

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.

  1. Conditional Rendering with <Show>

import { Show } from '@clerk/nextjs'

<Show when={{ role: 'org:admin' }}> <AdminPanel /> </Show>

<Show when={{ permission: 'org:sys_memberships:manage' }}> <MembersTab /> </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 ):

<Show when={{ role: 'org:admin' }}> <AdminPanel /> </Show>

  1. OrganizationSwitcher

import { OrganizationSwitcher } from '@clerk/nextjs'

<OrganizationSwitcher hidePersonal afterCreateOrganizationUrl="/orgs/:slug/dashboard" afterSelectOrganizationUrl="/orgs/:slug/dashboard" />

Key props:

  • hidePersonal: boolean — hide the Personal Account option. Defaults to false . Pass true for B2B-only apps.

  • afterCreateOrganizationUrl , afterSelectOrganizationUrl , afterLeaveOrganizationUrl , afterSelectPersonalUrl — navigation hooks. :slug is substituted at runtime.

  • createOrganizationMode , organizationProfileMode — 'modal' | 'navigation' (default 'modal' ).

The full prop list lives in the component reference.

  1. 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:

import { ClerkProvider } from '@clerk/nextjs'

<ClerkProvider taskUrls={{ 'choose-organization': '/session-tasks/choose-organization' }}> {children} </ClerkProvider>

// app/session-tasks/choose-organization/page.tsx import { TaskChooseOrganization } from '@clerk/nextjs'

export default function Page() { return <TaskChooseOrganization redirectUrlComplete="/dashboard" /> }

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

Role Default meaning

org:admin

Full access — all System Permissions, can manage org + memberships

org:member

Read members + Read billing Permissions only

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:

const { has } = await auth()

has({ plan: 'gold' }) // subscription plan has({ feature: 'widgets' }) // feature entitlement

Core 2 ONLY (skip if current SDK): has() only supports role and permission . 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.

// Strategy name for Enterprise SSO (Core 3) strategy: 'enterprise_sso'

Core 2 ONLY (skip if current SDK): Uses strategy: 'saml' and user.samlAccounts instead of user.enterpriseAccounts .

Gotchas

maxAllowedMemberships caps seats

const clerk = await clerkClient() await clerk.organizations.createOrganization({ name: 'Acme Corp', createdBy: userId, maxAllowedMemberships: 10, })

// Update later: await clerk.organizations.updateOrganization(orgId, { maxAllowedMemberships: 25, })

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:

const org = await clerk.organizations.getOrganization({ organizationId: orgId }) await clerk.organizations.updateOrganization(orgId, { publicMetadata: { ...org.publicMetadata, newField: 'value' }, })

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:

Error / symptom Root cause Fix

orgId / orgSlug is undefined for a signed-in user Organizations not enabled for this instance, OR user has no active org (personal account) Enable in Dashboard → Organizations; check Membership mode; surface <OrganizationSwitcher />

has({ permission: 'org:manage_members' }) always false

Using an invented permission slug Use org:sys_memberships:manage (see roles-permissions.md catalog)

has({ role }) returns false but user looks like an admin Session token stale after role change Re-sign-in, or refresh the session: await clerk.session?.reload()

has({ permission }) false even with the role assigned Feature not attached to active Plan (Billing gates permissions) Dashboard → Billing → Plans → attach Feature

<OrganizationSwitcher /> doesn't show "Personal Account" Membership required mode is on (the default since Aug 22, 2025) Dashboard → Organizations settings → Membership optional

TaskChooseOrganization throws "cannot render when a user doesn't have current session tasks" Rendered outside a choose-organization task context Wrap in a choose-organization session-task route only; don't render unconditionally

enterpriseAccounts[0].provider is undefined

Accessing provider at the wrong nesting level Use user.enterpriseAccounts[0].enterpriseConnection?.provider

Authorization Pattern (Complete Example)

Server component protecting a slug-scoped admin page:

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()

if (orgSlug !== params.slug) redirect('/dashboard') if (!has({ role: 'org:admin' })) redirect(/orgs/${orgSlug})

return <div>Admin settings for {orgSlug}</div> }

For middleware-level protection (Next.js) see references/nextjs-patterns.md .

Invitations (short form)

Send from a server action or route handler:

import { clerkClient, auth } from '@clerk/nextjs/server'

export async function inviteMember(organizationId: string, emailAddress: string, role: string) { const { userId, has } = await auth()

if (!userId) throw new Error('Not signed in') if (!has({ permission: 'org:sys_memberships:manage' })) { throw new Error('Not authorized to invite members') }

const clerk = await clerkClient() return clerk.organizations.createOrganizationInvitation({ organizationId, inviterUserId: userId, // required per Backend API emailAddress, role, // e.g. 'org:admin' or 'org:member' redirectUrl: 'https://yourapp.com/accept-invite', }) }

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 }) / has({ permission }) with canonical org:sys_* names

  • Scope routes — orgSlug === params.slug on every protected page

  • Switch orgs — <OrganizationSwitcher /> handles the whole flow

See Also

  • clerk-setup — Initial Clerk install

  • clerk-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 reference

  • clerk-nextjs-patterns — Framework-specific middleware, server actions, caching

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

clerk-nextjs-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
9.7K-clerk
General

clerk

No summary provided by upstream source.

Repository SourceNeeds Review
6.3K-clerk
General

clerk-setup

No summary provided by upstream source.

Repository SourceNeeds Review
5.5K-clerk
General

clerk-custom-ui

No summary provided by upstream source.

Repository SourceNeeds Review
5.2K-clerk