@dariomvg/create
clerkorganizationsteamsnextjsintegracion

Módulo de Organizations/Teams — Clerk (Next.js 16 / App Router)

Es un módulo separado del módulo de auth. Los roles/permisos personalizados viven en tu otro módulo, también separado: este solo usa los roles nativos de organización de Clerk (org:admin / org:member).

Dependencias

  • @clerk/nextjs (ya instalado para el módulo de auth: no hay nada nuevo)

Mapa de archivos

lib/organizations/
├── service.ts   # Server-only logic. Don't touch.
├── actions.ts   # Server Actions for Server Components ("use server")
├── types.ts     # OrgSummary, OrgMember, OrgInvitation, OrgRole
└── client.ts    # Client component/hook re-exports + copy-paste examples

No hay un .env.local nuevo (misma instancia/keys de Clerk que el módulo de auth) ni un proxy.ts nuevo (Next.js solo permite un archivo de middleware; mirá "Protección de rutas" más abajo).

Necesito...Ir a
Crear/actualizar/eliminar una organizaciónlib/organizations/service.ts
Llamar acciones de organización desde un Server Component (formulario, botón)lib/organizations/actions.ts
Agregar un tipo para algo relacionado con organizacioneslib/organizations/types.ts
Renderizar <OrganizationSwitcher/>, cambiar la organización activa, aceptar invitacioneslib/organizations/client.ts
Mostrar/ocultar UI según el rol o permiso en la organizaciónlib/clerk/client.ts → Show (módulo de auth, no se duplica acá)

Prerrequisito: habilitar Organizations

Nada de esto funciona hasta que hagas esto en el Dashboard:

  1. Dashboard → Organizations settings → Enable Organizations.
  2. Elegí el Membership mode:
    • Membership required — solo B2B, sin cuentas personales; después de iniciar sesión, los usuarios pasan por una pantalla de "elegir organización".
    • Membership optional — las cuentas personales siguen disponibles junto con las organizaciones (B2C + B2B).
  3. Creator role — qué rol recibe automáticamente un usuario cuando crea una organización. Por defecto es org:admin (no existe un rol "Owner" separado; mirá más abajo).

Checklist del Clerk Dashboard (sin código)

  • Organizations habilitado + Membership mode elegido
  • Creator role confirmado (por defecto org:admin)
  • Límite de miembros (maxAllowedMemberships) definido, si querés un tope estricto; también se puede pasar por organización en createOrganization()
  • Roles personalizados más allá de admin/member — no acá, eso corresponde a tu módulo separado de roles/permisos
  • Dominios verificados / Enterprise SSO — no están cubiertos por este módulo

Sobre los roles: Owner = Admin

Clerk trae dos roles de sistema: org:admin y org:member. En todo este módulo se usa org:admin como equivalente de Owner; no hay un rol personalizado.

Sobre "rechazar invitación"

La API de Clerk solo ofrece:

  • accept() — del lado del cliente, la llama el invitado sobre su UserOrganizationInvitation pendiente
  • revoke() — del lado del servidor, la llama un admin para cancelar una invitación pendiente

revokeInvitationAction en este módulo cubre eso.

Protección de rutas

La protección a nivel de organización ocurre por completo a nivel de Server Component/Server Action/Route Handler, con el mismo patrón que requireUser() en el módulo de auth: no hay una alternativa a nivel de middleware a la que recurrir, ya que Clerk deprecó createRouteMatcher() y ya no recomienda bloquear rutas (por path o por rol) en proxy.ts.

import { requireOrganization, requireOrgRole } from "@/lib/organizations/service";

export default async function OrgSettingsPage() {
  const org = await requireOrgRole("org:admin"); // redirects if no active org or not admin
  return <p>Settings for {org.name}</p>;
}

Ejemplos de uso

Listar las organizaciones del usuario y dejar que cambie entre ellas:

import { OrganizationSwitcher } from "@/lib/organizations/client";

<OrganizationSwitcher hidePersonal />

Invitar a un miembro desde una Server Action:

import { inviteMemberAction } from "@/lib/organizations/actions";

<form action={() => inviteMemberAction(orgId, email, "org:member")}>
  ...
</form>

Aceptar una invitación pendiente (del lado del cliente, en la lista propia del invitado):

"use client";
import { useOrganizationList } from "@/lib/organizations/client";

const { userInvitations } = useOrganizationList({
  userInvitations: { infinite: true },
});

Restringir una sección por rol o permiso:

import { Show } from "@/lib/clerk/client";

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