diff --git a/.changeset/nervous-flies-sleep.md b/.changeset/nervous-flies-sleep.md
new file mode 100644
index 0000000000..c77a171356
--- /dev/null
+++ b/.changeset/nervous-flies-sleep.md
@@ -0,0 +1,6 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/types': patch
+---
+
+Introduce the `skipInvitationScreen` prop on `` component
diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx
index e8d7a510d2..812d66b196 100644
--- a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx
+++ b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationPage.tsx
@@ -25,7 +25,7 @@ export const CreateOrganizationPage = withCardStateProvider(() => {
const [file, setFile] = React.useState();
const { createOrganization, isLoaded } = useCoreOrganizations();
const { setActive, closeCreateOrganization } = useCoreClerk();
- const { mode, navigateAfterCreateOrganization } = useCreateOrganizationContext();
+ const { mode, navigateAfterCreateOrganization, skipInvitationScreen } = useCreateOrganizationContext();
const { organization } = useCoreOrganization();
const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
@@ -63,7 +63,7 @@ export const CreateOrganizationPage = withCardStateProvider(() => {
await setActive({ organization });
- if (organization.maxAllowedMemberships === 1) {
+ if (skipInvitationScreen ?? organization.maxAllowedMemberships === 1) {
return completeFlow();
}
diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx
new file mode 100644
index 0000000000..061f44be19
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx
@@ -0,0 +1,160 @@
+import type { OrganizationResource } from '@clerk/types';
+import { describe, jest } from '@jest/globals';
+import { waitFor } from '@testing-library/dom';
+import React from 'react';
+
+import { render } from '../../../../testUtils';
+import { bindCreateFixtures } from '../../../utils/test/createFixtures';
+import { CreateOrganization } from '../CreateOrganization';
+
+const { createFixtures } = bindCreateFixtures('CreateOrganization');
+
+type FakeOrganizationParams = {
+ id: string;
+ createdAt?: Date;
+ imageUrl?: string;
+ logoUrl?: string;
+ slug: string;
+ name: string;
+ membersCount: number;
+ pendingInvitationsCount: number;
+ adminDeleteEnabled: boolean;
+ maxAllowedMemberships: number;
+};
+
+const createFakeOrganization = (params: FakeOrganizationParams): OrganizationResource => {
+ return {
+ logoUrl: null,
+ pathRoot: '',
+ id: params.id,
+ name: params.name,
+ slug: params.slug,
+ imageUrl: params.imageUrl || '',
+ membersCount: params.membersCount,
+ pendingInvitationsCount: params.pendingInvitationsCount,
+ publicMetadata: {},
+ adminDeleteEnabled: params.adminDeleteEnabled,
+ maxAllowedMemberships: params?.maxAllowedMemberships,
+ createdAt: params?.createdAt || new Date(),
+ updatedAt: new Date(),
+ update: jest.fn() as any,
+ getMemberships: jest.fn() as any,
+ getPendingInvitations: jest.fn() as any,
+ addMember: jest.fn() as any,
+ inviteMember: jest.fn() as any,
+ inviteMembers: jest.fn() as any,
+ updateMember: jest.fn() as any,
+ removeMember: jest.fn() as any,
+ destroy: jest.fn() as any,
+ setLogo: jest.fn() as any,
+ reload: jest.fn() as any,
+ };
+};
+
+const getCreatedOrg = (params: Partial) =>
+ createFakeOrganization({
+ id: '1',
+ adminDeleteEnabled: false,
+ maxAllowedMemberships: 1,
+ membersCount: 1,
+ name: 'new org',
+ pendingInvitationsCount: 0,
+ slug: 'new-org',
+ ...params,
+ });
+
+describe('CreateOrganization', () => {
+ it('renders component', async () => {
+ const { wrapper } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withUser({
+ email_addresses: ['test@clerk.dev'],
+ });
+ });
+ const { getByText } = render(, { wrapper });
+ expect(getByText('Create Organization')).toBeInTheDocument();
+ });
+
+ it('skips invitation screen', async () => {
+ const { wrapper, fixtures, props } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withUser({
+ email_addresses: ['test@clerk.dev'],
+ });
+ });
+
+ fixtures.clerk.createOrganization.mockReturnValue(
+ Promise.resolve(
+ getCreatedOrg({
+ maxAllowedMemberships: 3,
+ }),
+ ),
+ );
+
+ props.setProps({ skipInvitationScreen: true });
+ const { getByRole, userEvent, getByLabelText, queryByText } = render(, {
+ wrapper,
+ });
+ await userEvent.type(getByLabelText(/Organization name/i), 'new org');
+ await userEvent.click(getByRole('button', { name: /create organization/i }));
+
+ await waitFor(() => {
+ expect(queryByText(/Invite members/i)).not.toBeInTheDocument();
+ });
+ });
+
+ it('always visit invitation screen', async () => {
+ const { wrapper, fixtures, props } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withUser({
+ email_addresses: ['test@clerk.dev'],
+ });
+ });
+
+ fixtures.clerk.createOrganization.mockReturnValue(
+ Promise.resolve(
+ getCreatedOrg({
+ maxAllowedMemberships: 1,
+ }),
+ ),
+ );
+
+ props.setProps({ skipInvitationScreen: false });
+ const { getByRole, userEvent, getByLabelText, queryByText } = render(, {
+ wrapper,
+ });
+ await userEvent.type(getByLabelText(/Organization name/i), 'new org');
+ await userEvent.click(getByRole('button', { name: /create organization/i }));
+
+ await waitFor(() => {
+ expect(queryByText(/Invite members/i)).toBeInTheDocument();
+ });
+ });
+
+ it('auto skip invitation screen', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.withOrganizations();
+ f.withUser({
+ email_addresses: ['test@clerk.dev'],
+ });
+ });
+
+ fixtures.clerk.createOrganization.mockReturnValue(
+ Promise.resolve(
+ getCreatedOrg({
+ maxAllowedMemberships: 1,
+ }),
+ ),
+ );
+
+ const { getByRole, userEvent, getByLabelText, queryByText } = render(, {
+ wrapper,
+ });
+ await userEvent.type(getByLabelText(/Organization name/i), 'new org');
+ await userEvent.click(getByRole('button', { name: /create organization/i }));
+
+ await waitFor(() => {
+ expect(queryByText(/Invite members/i)).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 86ad31e340..5445475b1b 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -662,6 +662,12 @@ export type CreateOrganizationProps = {
* @default undefined
*/
afterCreateOrganizationUrl?: string;
+ /**
+ * Hides the screen for sending invitations after an organization is created.
+ * @default undefined When left undefined Clerk will automatically hide the screen if
+ * the number of max allowed members is equal to 1
+ */
+ skipInvitationScreen?: boolean;
/**
* Customisation options to fully match the Clerk components to your own brand.
* These options serve as overrides and will be merged with the global `appearance`