Docs
Organization Creator
Experimental

Organization Creator

Creates organizations quickly and easily, streamlining the process for admins and users.

    Create Organization

    Creating a new organization will allow you to manage a separate group with unique settings and resources.

    Choose a unique name for your organization.

    Installation

    Install the following dependencies:

    npx shadcn-ui@latest add card button form input toast
    pnpm install zod react-hook-form @hookform/resolvers

    Copy and paste the following code into your project.

    "use client"; import * as React from "react"; import { useForm, UseFormReturn } from "react-hook-form"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Toaster } from "@/components/ui/toaster"; import { useToast } from "@/components/ui/use-toast"; import { zodResolver } from "@hookform/resolvers/zod"; export type OrganizationCreationResponse = { name: string; display_name?: string; branding?: { logo_url?: string; colors?: { primary: string; page_background: string; }; }; metadata?: { [key: string]: any; }; enabled_connections?: Array<{ connection_id: string; assign_membership_on_login?: boolean; show_as_button?: boolean; }>; id: string; }; type OrganizationCreateProps = { customFields?: any[]; schema?: any; defaultValues?: any; onCreate?: (organization: OrganizationCreationResponse) => Promise<{ organization?: OrganizationCreationResponse; status: number; }>; }; type BaseFormProps = { onSubmit: (values: z.infer<typeof formSchemaBase>) => void; form: UseFormReturn<z.infer<typeof formSchemaBase>>; }; type OrganizationFormProps = BaseFormProps & { children: React.ReactNode; customFields?: any[]; }; type PageModeProps = BaseFormProps & { working: boolean; customFields?: any[]; }; const formSchemaBase = z.object({ organization_name: z .string() .min(3, { message: "Organization name must be at least 2 characters.", }) .max(50, { message: "Organization name must be at most 50 characters.", }) .regex(/^(?:(?!org_))[a-z0-9]([a-z0-9\-_]*[a-z0-9])?$/, { message: "Invalid organization name.", }), }); function Spinner() { return ( <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2 animate-spin" > <path d="M21 12a9 9 0 1 1-6.219-8.56"></path> </svg> ); } function OrganizationForm({ form, children, customFields, onSubmit, }: OrganizationFormProps) { return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="organization_name" render={({ field }) => ( <FormItem> <FormLabel>Organization Name</FormLabel> <FormControl> <Input autoFocus {...field} /> </FormControl> <FormDescription> Choose a unique name for your organization. </FormDescription> <FormMessage /> </FormItem> )} /> {customFields && customFields.map((Field) => <Field key={typeof Field} form={form} />)} {children} </form> </Form> ); } function PageMode({ form, working, customFields, onSubmit }: PageModeProps) { return ( <div className="max-w-screen-lg mx-auto gap-5 md:gap-5 lg:gap-5 justify-center"> <Card> <CardHeader className="p-4 md:p-6"> <CardTitle>Create Organization</CardTitle> <CardDescription> Creating a new organization will allow you to manage a separate group with unique settings and resources. </CardDescription> </CardHeader> <CardContent className="grid gap-6 p-4 pt-0 md:p-6 md:pt-0"> <OrganizationForm form={form} onSubmit={onSubmit} customFields={customFields} > <div className="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"> <Button type="button" variant="ghost" disabled={working} className="disabled:opacity-50" > Cancel </Button> <Button type="submit" disabled={working} className="disabled:opacity-50" > {working && <Spinner />} Create </Button> </div> </OrganizationForm> </CardContent> </Card> </div> ); } export default function OrganizationCreate({ customFields, defaultValues, schema, onCreate, }: OrganizationCreateProps) { const { toast } = useToast(); const [working, setWorking] = React.useState<boolean>(false); let formSchema: any = formSchemaBase; if (schema) { formSchema = formSchemaBase.and(schema); } const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { organization_name: "", ...defaultValues, }, }); async function onSubmit(values: z.infer<typeof formSchema>) { setWorking(true); if (typeof onCreate === "function") { const response = await onCreate(values); if (response.status !== 200) { setWorking(false); return toast({ title: "Info", description: "There was a problem creating the organization. Try again later.", }); } } setWorking(false); } return ( <> <Toaster /> <PageMode working={working} form={form} onSubmit={onSubmit} customFields={customFields} /> </> ); }

    Component behavior

    By design, our components provide basic behavior without making any requests to the Auth0 Management API. To help you implement the full feature, we've also included React Hooks and NextJS routers for calling and proxying the Auth0 Management API.

    Update the import paths to match your project setup.

    React Hooks

    useOrganizations

    The hook can be used to create or update an organization.

    "use client"; import { useCallback } from "react"; interface KeyValueMap { [key: string]: any; } type OrganizationCreationResponse = { name: string; display_name?: string; branding?: { logo_url?: string; colors?: { primary: string; page_background: string; }; }; metadata?: { [key: string]: any; }; enabled_connections?: Array<{ connection_id: string; assign_membership_on_login?: boolean; show_as_button?: boolean; }>; id: string; }; export default function useOrganizations() { const createOrganization = useCallback( async ( values: any ): Promise<{ organization?: OrganizationCreationResponse; status: number; }> => { try { /** * '/api/auth/orgs' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-creator#nextjs-routers */ const response = await fetch("/api/auth/orgs", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: values.organization_name }), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: OrganizationCreationResponse = await response.json(); return { organization, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const fetchOrganization = useCallback( async (id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{id}' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}`, { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: KeyValueMap = await response.json(); return { organization, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const updateOrganization = useCallback( async ( id: string, values: KeyValueMap ): Promise<{ organization?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/{id}' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const organization: KeyValueMap = await response.json(); return { organization, status: response.status }; } catch (e) { console.error(e); return { status: 500 }; } }, [] ); const startSelfServiceConfiguration = useCallback( async ( values: KeyValueMap ): Promise<{ selfService?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/sso' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-sso#nextjs-routers */ const response = await fetch("/api/auth/orgs/sso", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ organizations_to_enable: values.organizations_to_enable, }), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const selfService = await response.json(); return { selfService, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const fetchOrganizationConnections = useCallback( async (id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{id}/connections' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-sso#nextjs-routers */ const response = await fetch(`/api/auth/orgs/${id}/connections`, { method: "GET", headers: { "Content-Type": "application/json", }, }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const connections: KeyValueMap[] = await response.json(); return { connections, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); const startSelfServiceConnectionUpdate = useCallback( async ( values: KeyValueMap ): Promise<{ selfService?: KeyValueMap; status: number; }> => { try { /** * '/api/auth/orgs/sso' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-metadata#nextjs-routers */ const response = await fetch(`/api/auth/orgs/sso`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const selfService: KeyValueMap = await response.json(); return { selfService, status: response.status }; } catch (e) { console.error(e); return { status: 500 }; } }, [] ); const deleteOrganizationConnection = useCallback( async (id: string, connection_id: string): Promise<KeyValueMap> => { try { /** * '/api/auth/orgs/{id}/connections/{connection_id}' is a custom endpoint which will proxy * the request to the Auth0 Management API. * * Proxy sample at: https://components.lab.auth0.com/docs/components/organization-sso#nextjs-routers */ const response = await fetch( `/api/auth/orgs/${id}/connections/${connection_id}`, { method: "DELETE", headers: { "Content-Type": "application/json", }, } ); // TODO: Better handling rate limits if (response.status === 429) { return { status: 429 }; } const connection: KeyValueMap[] = await response.json(); return { connection, status: response.status, }; } catch (error) { console.error(error); return { status: 500 }; } }, [] ); return { createOrganization, fetchOrganization, updateOrganization, fetchOrganizationConnections, deleteOrganizationConnection, startSelfServiceConfiguration, startSelfServiceConnectionUpdate, }; }

    NextJS routers

    Organizations router

    The route can be used to create or update an organization.

    import { AuthenticationClient, ManagementClient, PostOrganizationsRequest, } from "auth0"; import { NextRequest, NextResponse } from "next/server"; import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0"; /** * Make sure to install the withRateLimit from: * - https://components.lab.auth0.com/docs/rate-limit#helpers */ import { withRateLimit } from "./helpers/rate-limit"; const client = new ManagementClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); const authClient = new AuthenticationClient({ domain: new URL(process.env.AUTH0_ISSUER_BASE_URL!).host, clientId: process.env.AUTH0_CLIENT_ID_MGMT!, clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT!, }); type HandleOrganizationCreationParams = Pick< PostOrganizationsRequest, "enabled_connections" >; type SelfServiceParams = { clients_to_enable: string[]; }; /** * @example * * export const POST = handleOrganizationCreation({ * enabled_connections: [{ * connection_id: process.env.ORGANIZATIONS_ENABLED_CONNECTION!, * assign_membership_on_login: false, * }] * }); */ // TODO: better error handling export function handleOrganizationCreation( params?: HandleOrganizationCreationParams ) { return withRateLimit( withApiAuthRequired(async (request: Request): Promise<NextResponse> => { try { const session = await getSession(); const userId = session?.user.sub; const body: PostOrganizationsRequest = await request.json(); const postOrganization: PostOrganizationsRequest = { name: body.name, }; if (params && params.enabled_connections) { postOrganization.enabled_connections = params.enabled_connections; } // Create organization const { data: organization } = await client.organizations.create( postOrganization ); // Add current user to new organization await client.organizations.addMembers( { id: organization.id }, { members: [userId] } ); return NextResponse.json( { id: organization.id, name: organization.name, display_name: organization.display_name, }, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error creating organization" }, { status: 500 } ); } }) ); } /** * @example export const GET = handleFetchOrganization(); */ export function handleFetchOrganization() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const response = await client.organizations.get({ id: org_id }); const { data } = response; const org = { id: data.id, name: data.name, display_name: data.display_name, branding: data.branding || { logo_url: `https://cdn.auth0.com/avatars/c.png`, colors: {}, }, metadata: data.metadata || {}, }; return NextResponse.json(org, { status: response.status, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } } ) ); } /** * @example export const PUT = handleOrganizationUpdate(); */ // TODO: better error handling export function handleOrganizationUpdate() { return withRateLimit( withApiAuthRequired( async (request: Request, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const org = await request.json(); await client.organizations.update({ id: org_id }, org); return NextResponse.json(org, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error updating organization" }, { status: 500 } ); } } ) ); } /** * @example export const POST = handleSelfService(); */ export function handleSelfService(params: SelfServiceParams) { return withRateLimit( withApiAuthRequired(async (request: NextRequest): Promise<NextResponse> => { try { const body = await request.json(); const { data: tokens } = await authClient.oauth.clientCredentialsGrant({ audience: `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/`, }); const $selfServiceProfile = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles`, { headers: { Authorization: `Bearer ${tokens.access_token}`, }, } ); const profile = (await $selfServiceProfile.json()).pop(); const randomId = crypto.getRandomValues(new Uint32Array(4)).join("-"); const $ticket = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles/${profile.id}/sso-ticket`, { method: "POST", headers: { Authorization: `Bearer ${tokens.access_token}`, "Content-Type": "application/json", }, body: JSON.stringify({ connection_config: { name: `${randomId}`, }, enabled_clients: params.clients_to_enable, enabled_organizations: body.organizations_to_enable.map( (organization_id: string) => ({ organization_id }) ), }), } ); const data = await $ticket.json(); return NextResponse.json(data, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const PUT = handleSelfService(); */ export function handleConnectionUpdate(params: SelfServiceParams) { return withRateLimit( withApiAuthRequired(async (request: NextRequest): Promise<NextResponse> => { try { const body = await request.json(); const { data: tokens } = await authClient.oauth.clientCredentialsGrant({ audience: `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/`, }); const $selfServiceProfile = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles`, { headers: { Authorization: `Bearer ${tokens.access_token}`, }, } ); const profile = (await $selfServiceProfile.json()).pop(); const $ticket = await fetch( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/self-service-profiles/${profile.id}/sso-ticket`, { method: "POST", headers: { Authorization: `Bearer ${tokens.access_token}`, "Content-Type": "application/json", }, body: JSON.stringify({ connection_id: body.connection_id, }), } ); const data = await $ticket.json(); return NextResponse.json(data, { status: 200, }); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } }) ); } /** * @example export const GET = handleFetchEnabledConnections(); */ export function handleFetchEnabledConnections() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const response = await client.organizations.getEnabledConnections({ id: org_id, }); const { data } = response; return NextResponse.json( data.filter((d) => // Note: Only strategies support on self-service. ["oidc", "okta", "samlp", "waad", "google-apps", "adfs"].includes( d.connection.strategy ) ), { status: response.status, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error fetching user metadata" }, { status: 500 } ); } } ) ); } /** * @example export const DELETE = handleDeleteConnection(); */ export function handleDeleteConnection() { return withRateLimit( withApiAuthRequired( async (request: NextRequest, { params }): Promise<NextResponse> => { try { const org_id = params?.id as string; const connection_id = params?.connection_id as string; await client.organizations.deleteEnabledConnection({ id: org_id, connectionId: connection_id, }); await client.connections.delete({ id: connection_id }); return NextResponse.json( {}, { status: 200, } ); } catch (error) { console.error(error); return NextResponse.json( { error: "Error deleting connection" }, { status: 500 } ); } } ) ); }