Skip to main content
Docs

Build a custom flow for creating Organizations

Warning

This guide is for users who want to build a . To use a prebuilt UI, use the Account Portal pages or prebuilt components.

This guide demonstrates how to use Clerk's API to build a custom flow for creating Organizations.

The following example uses these hooks:

  • The useOrganizationList() hook to access the createOrganization() method. This method is used to create a new Organization with the provided name.

  • The useOrganizationCreationDefaults() hook to pre-populate the form with a suggested organization name based on your application's default naming rules, and to display a warning if an organization with that name or domain already exists.

    src/components/CreateOrganizationWithWarning.tsx
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/clerk-react'
    import { FormEventHandler, useEffect, useState } from 'react'
    
    export default function CreateOrganizationWithWarning() {
      const { isLoaded, createOrganization } = useOrganizationList()
      const { data: defaults, isLoading } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoading) return <div>Loading...</div>
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
        e.preventDefault()
        try {
          await createOrganization?.({ name: organizationName })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={organizationName}
            onChange={(e) => setOrganizationName(e.target.value)}
            placeholder="Organization name"
          />
          {showWarning && (
            <p style={{ color: 'orange' }}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </p>
          )}
          <button type="submit">Create organization</button>
        </form>
      )
    }
    app/components/CreateOrganizationWithWarning.tsx
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/react-router'
    import { FormEventHandler, useEffect, useState } from 'react'
    
    export default function CreateOrganizationWithWarning() {
      const { isLoaded, createOrganization, setActive } = useOrganizationList()
      const { data: defaults, isLoading } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoading) return <div>Loading...</div>
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
        e.preventDefault()
        try {
          const newOrganization = await createOrganization?.({ name: organizationName })
          // Set the created Organization as the Active Organization
          if (newOrganization) setActive({ organization: newOrganization.id })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={organizationName}
            onChange={(e) => setOrganizationName(e.target.value)}
            placeholder="Organization name"
          />
          {showWarning && (
            <p style={{ color: 'orange' }}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </p>
          )}
          <button type="submit">Create organization</button>
        </form>
      )
    }
    src/components/CreateOrganizationWithWarning.tsx
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/chrome-extension'
    import { FormEventHandler, useEffect, useState } from 'react'
    
    export default function CreateOrganizationWithWarning() {
      const { isLoaded, createOrganization, setActive } = useOrganizationList()
      const { data: defaults, isLoading } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoading) return <div>Loading...</div>
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
        e.preventDefault()
        try {
          const newOrganization = await createOrganization?.({ name: organizationName })
          // Set the created Organization as the Active Organization
          if (newOrganization) setActive({ organization: newOrganization.id })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={organizationName}
            onChange={(e) => setOrganizationName(e.target.value)}
            placeholder="Organization name"
          />
          {showWarning && (
            <p style={{ color: 'orange' }}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </p>
          )}
          <button type="submit">Create organization</button>
        </form>
      )
    }
    app/components/CreateOrganizationWithWarning.tsx
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/tanstack-react-start'
    import { FormEventHandler, useEffect, useState } from 'react'
    
    export default function CreateOrganizationWithWarning() {
      const { isLoaded, createOrganization, setActive } = useOrganizationList()
      const { data: defaults, isLoading } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoading) return <div>Loading...</div>
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
        e.preventDefault()
        try {
          const newOrganization = await createOrganization?.({ name: organizationName })
          // Set the created Organization as the Active Organization
          if (newOrganization) setActive({ organization: newOrganization.id })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={organizationName}
            onChange={(e) => setOrganizationName(e.target.value)}
            placeholder="Organization name"
          />
          {showWarning && (
            <p style={{ color: 'orange' }}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </p>
          )}
          <button type="submit">Create organization</button>
        </form>
      )
    }
    components/CreateOrganizationWithWarning.tsx
    import { ThemedText } from '@/components/themed-text'
    import { ThemedView } from '@/components/themed-view'
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/clerk-expo'
    import { useEffect, useState } from 'react'
    import { Pressable, StyleSheet, TextInput } from 'react-native'
    
    export default function CreateOrganizationWithWarning() {
      const { isLoaded, createOrganization, setActive } = useOrganizationList()
      const { data: defaults, isLoading } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoading) {
        return (
          <ThemedView style={styles.container}>
            <ThemedText>Loading...</ThemedText>
          </ThemedView>
        )
      }
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit = async () => {
        try {
          const newOrganization = await createOrganization?.({ name: organizationName })
          // Set the created Organization as the Active Organization
          if (newOrganization) setActive({ organization: newOrganization.id })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    
      return (
        <ThemedView style={styles.container}>
          <ThemedText style={styles.label}>Organization name</ThemedText>
          <TextInput
            style={styles.input}
            value={organizationName}
            onChangeText={setOrganizationName}
            placeholder="Organization name"
            placeholderTextColor="#666666"
          />
          {showWarning && (
            <ThemedText style={styles.warning}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </ThemedText>
          )}
          <Pressable
            style={({ pressed }) => [
              styles.button,
              !organizationName && styles.buttonDisabled,
              pressed && styles.buttonPressed,
            ]}
            onPress={handleSubmit}
            disabled={!organizationName}
          >
            <ThemedText style={styles.buttonText}>Create organization</ThemedText>
          </Pressable>
        </ThemedView>
      )
    }
    
    const styles = StyleSheet.create({
      container: {
        gap: 12,
      },
      label: {
        fontWeight: '600',
        fontSize: 14,
      },
      input: {
        borderWidth: 1,
        borderColor: '#ccc',
        borderRadius: 8,
        padding: 12,
        fontSize: 16,
        backgroundColor: '#fff',
      },
      button: {
        backgroundColor: '#0a7ea4',
        paddingVertical: 12,
        paddingHorizontal: 24,
        borderRadius: 8,
        alignItems: 'center',
        marginTop: 8,
      },
      buttonPressed: {
        opacity: 0.7,
      },
      buttonDisabled: {
        opacity: 0.5,
      },
      buttonText: {
        color: '#fff',
        fontWeight: '600',
      },
      warning: {
        color: '#f57c00',
        fontSize: 14,
        marginTop: -4,
      },
    })
    app/components/CreateOrganization.tsx
    'use client'
    
    import { useOrganizationCreationDefaults, useOrganizationList } from '@clerk/nextjs'
    import { FormEventHandler, useEffect, useState } from 'react'
    
    export default function CreateOrganization() {
      const { isLoaded, createOrganization, setActive } = useOrganizationList()
      const { data: defaults, isLoading: isLoadingDefaults } = useOrganizationCreationDefaults()
      const [organizationName, setOrganizationName] = useState('')
    
      // Pre-populate the form with suggested organization name
      useEffect(() => {
        if (defaults?.form.name) {
          setOrganizationName(defaults.form.name)
        }
      }, [defaults?.form.name])
    
      if (!isLoaded || isLoadingDefaults) return <p>Loading...</p>
    
      // Check if an organization with this name/domain already exists
      const advisory = defaults?.advisory
      const showWarning = advisory?.code === 'organization_already_exists'
      const existingOrgName = advisory?.meta?.organization_name
      const existingOrgDomain = advisory?.meta?.organization_domain
    
      const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
        e.preventDefault()
        try {
          const newOrganization = await createOrganization?.({ name: organizationName })
          // Set the created Organization as the Active Organization
          if (newOrganization) setActive({ organization: newOrganization.id })
        } catch (err) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
        setOrganizationName('')
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            value={organizationName}
            onChange={(e) => setOrganizationName(e.currentTarget.value)}
            placeholder="Organization name"
          />
          {showWarning && (
            <p style={{ color: 'orange' }}>
              An organization "{existingOrgName}" already exists for the domain "{existingOrgDomain}".
            </p>
          )}
          <button type="submit">Create organization</button>
        </form>
      )
    }

The following example uses the clerk.createOrganization() method to create a new Organization with the provided name.

Use the tabs to view the code necessary for the index.html and main.js files.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Clerk + JavaScript App</title>
  </head>
  <body>
    <div id="app"></div>

    <h1>Create an organization</h1>
    <form id="create-organization">
      <label for="name">Name</label>
      <input id="name" name="name" />
      <button>Create organization</button>
    </form>

    <script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
  </body>
</html>
main.js
import { Clerk } from '@clerk/clerk-js'

const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

if (!pubKey) {
  throw new Error('Add your VITE_CLERK_PUBLISHABLE_KEY to .env file')
}

const clerk = new Clerk(pubKey)
await clerk.load()

if (clerk.isSignedIn) {
  const form = document.getElementById('create-organization')

  form.addEventListener('submit', async (e) => {
    e.preventDefault()

    const inputEl = document.getElementById('name')

    if (!inputEl) {
      // ... handle empty input
      return
    }

    try {
      await clerk.createOrganization({ name: inputEl.value })
      if (newOrganization) clerk.setActive({ organization: newOrganization.id })
    } catch (error) {
      console.log('An error occurred:', error)
    }
  })
} else {
  // If there is no active user, mount Clerk's <SignIn />
  document.getElementById('app').innerHTML = `
    <div id="sign-in"></div>
  `

  const signInDiv = document.getElementById('sign-in')

  clerk.mountSignIn(signInDiv)
}

Examples for this SDK aren't available yet. For now, try switching to a supported SDK, such as Next.js, and converting the code to fit your SDK.

Feedback

What did you think of this content?

Last updated on