From 728440bf67eabacaeb1f2fc1b1ed6a961d89a1fb Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Mon, 21 Jul 2025 21:07:15 -0300
Subject: [PATCH 1/5] Add `tasksUrl` as option
---
.changeset/whole-knives-attend.md | 16 ++++++++++++++++
.../clerk-js/src/core/__tests__/clerk.test.ts | 19 +++++++++++++++++--
packages/clerk-js/src/core/clerk.ts | 4 ++--
packages/clerk-js/src/core/sessionTasks.ts | 7 +++----
packages/clerk-js/src/ui/common/redirects.ts | 8 +++++---
.../src/ui/components/SessionTasks/index.tsx | 4 ++--
.../src/ui/contexts/components/SignIn.ts | 1 +
.../src/ui/contexts/components/SignUp.ts | 1 +
packages/types/src/clerk.ts | 12 ++++++++++--
packages/types/src/session.ts | 7 +++++++
packages/types/src/utils.ts | 4 ++++
11 files changed, 68 insertions(+), 15 deletions(-)
create mode 100644 .changeset/whole-knives-attend.md
diff --git a/.changeset/whole-knives-attend.md b/.changeset/whole-knives-attend.md
new file mode 100644
index 00000000000..8761ae9c8f1
--- /dev/null
+++ b/.changeset/whole-knives-attend.md
@@ -0,0 +1,16 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/types': patch
+---
+
+Add `taskUrls` option to customize task flow URLs:
+
+```tsx
+
+```
+
+**Breaking**: Task key renamed from `'select-organization'` to `'org'`.
diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts
index 1be58ac9236..be65d9b6952 100644
--- a/packages/clerk-js/src/core/__tests__/clerk.test.ts
+++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts
@@ -2331,7 +2331,7 @@ describe('Clerk singleton', () => {
});
});
- describe('nextTask', () => {
+ describe('navigateToTask', () => {
describe('with `pending` session status', () => {
const mockSession = {
id: '1',
@@ -2360,7 +2360,7 @@ describe('Clerk singleton', () => {
mockResource.touch.mockReset();
});
- it('navigates to next task', async () => {
+ it('navigates to next task with default internal routing for AIOs', async () => {
const sut = new Clerk(productionPublishableKey);
await sut.load(mockedLoadOptions);
@@ -2369,6 +2369,21 @@ describe('Clerk singleton', () => {
expect(mockNavigate.mock.calls[0][0]).toBe('/sign-in#/tasks/add-organization');
});
+
+ it('navigates to next task with custom routing from clerk options', async () => {
+ const sut = new Clerk(productionPublishableKey);
+ await sut.load({
+ ...mockedLoadOptions,
+ taskUrls: {
+ org: '/onboarding/select-organization',
+ },
+ });
+
+ await sut.setActive({ session: mockResource as any as PendingSessionResource });
+ await sut.__internal_navigateToTaskIfAvailable();
+
+ expect(mockNavigate.mock.calls[0][0]).toBe('/onboarding/select-organization');
+ });
});
describe('with `active` session status', () => {
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index 2439952460e..9c978e9e219 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -30,9 +30,9 @@ import type {
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
AuthenticateWithOKXWalletParams,
- Clerk as ClerkInterface,
ClerkAPIError,
ClerkAuthenticateWithWeb3Params,
+ Clerk as ClerkInterface,
ClerkOptions,
ClientJSONSnapshot,
ClientResource,
@@ -1317,7 +1317,7 @@ export class Clerk implements ClerkInterface {
eventBus.emit(events.TokenUpdate, { token: null });
}
- // Only triggers navigation for internal AIO components routing
+ // Only triggers navigation for internal AIO components routing or custom URLs
const shouldNavigateOnSetActive = this.#componentNavigationContext;
if (newSession?.currentTask && shouldNavigateOnSetActive) {
await navigateToTask(session.currentTask.key, {
diff --git a/packages/clerk-js/src/core/sessionTasks.ts b/packages/clerk-js/src/core/sessionTasks.ts
index 1971d68e870..23858a8116c 100644
--- a/packages/clerk-js/src/core/sessionTasks.ts
+++ b/packages/clerk-js/src/core/sessionTasks.ts
@@ -7,7 +7,7 @@ import type {
import { buildURL } from '../utils';
-export const SESSION_TASK_ROUTE_BY_KEY: Record = {
+export const INTERNAL_SESSION_TASK_ROUTE_BY_KEY: Record = {
org: 'add-organization',
} as const;
@@ -24,10 +24,10 @@ interface NavigateToTaskOptions {
* @internal
*/
export function navigateToTask(
- routeKey: keyof typeof SESSION_TASK_ROUTE_BY_KEY,
+ routeKey: keyof typeof INTERNAL_SESSION_TASK_ROUTE_BY_KEY,
{ componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
) {
- const taskRoute = `/tasks/${SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
+ const taskRoute = options.taskUrls?.[routeKey] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
if (componentNavigationContext) {
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
@@ -38,7 +38,6 @@ export function navigateToTask(
const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
const sessionTaskUrl = buildURL(
- // TODO - Accept custom URL option for custom flows in order to eject out of `signInUrl/signUpUrl`
{
base: isReferrerSignUpUrl ? signUpUrl : signInUrl,
hashPath: taskRoute,
diff --git a/packages/clerk-js/src/ui/common/redirects.ts b/packages/clerk-js/src/ui/common/redirects.ts
index 59636a9bb64..354cb48272f 100644
--- a/packages/clerk-js/src/ui/common/redirects.ts
+++ b/packages/clerk-js/src/ui/common/redirects.ts
@@ -1,6 +1,6 @@
-import type { SessionTask } from '@clerk/types';
+import type { ClerkOptions, SessionTask } from '@clerk/types';
-import { SESSION_TASK_ROUTE_BY_KEY } from '../../core/sessionTasks';
+import { INTERNAL_SESSION_TASK_ROUTE_BY_KEY } from '../../core/sessionTasks';
import { buildURL } from '../../utils/url';
import type { SignInContextType, SignUpContextType, UserProfileContextType } from './../contexts';
@@ -33,9 +33,11 @@ export function buildSessionTaskRedirectUrl({
path,
baseUrl,
task,
+ taskUrls,
}: Pick & {
baseUrl: string;
task?: SessionTask;
+ taskUrls?: ClerkOptions['taskUrls'];
}) {
if (!task) {
return null;
@@ -45,7 +47,7 @@ export function buildSessionTaskRedirectUrl({
routing,
baseUrl,
path,
- endpoint: `/tasks/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
+ endpoint: taskUrls?.[task.key] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
authQueryString: null,
});
}
diff --git a/packages/clerk-js/src/ui/components/SessionTasks/index.tsx b/packages/clerk-js/src/ui/components/SessionTasks/index.tsx
index 23627b0bfe5..20f4229594d 100644
--- a/packages/clerk-js/src/ui/components/SessionTasks/index.tsx
+++ b/packages/clerk-js/src/ui/components/SessionTasks/index.tsx
@@ -6,7 +6,7 @@ import { Card } from '@/ui/elements/Card';
import { withCardStateProvider } from '@/ui/elements/contexts';
import { LoadingCardContainer } from '@/ui/elements/LoadingCard';
-import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
+import { INTERNAL_SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
import { SignInContext, SignUpContext } from '../../../ui/contexts';
import { SessionTasksContext, useSessionTasksContext } from '../../contexts/components/SessionTasks';
import { Route, Switch, useRouter } from '../../router';
@@ -38,7 +38,7 @@ const SessionTasksStart = () => {
function SessionTaskRoutes(): JSX.Element {
return (
-
+
diff --git a/packages/clerk-js/src/ui/contexts/components/SignIn.ts b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
index 67f6442bb3a..75aa4b4b1b5 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignIn.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
@@ -126,6 +126,7 @@ export const useSignInContext = (): SignInContextType => {
path: ctx.path,
routing: ctx.routing,
baseUrl: signInUrl,
+ taskUrls: clerk.__internal_getOption('taskUrls'),
});
return {
diff --git a/packages/clerk-js/src/ui/contexts/components/SignUp.ts b/packages/clerk-js/src/ui/contexts/components/SignUp.ts
index 7632e782f2d..f91efbea7b1 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignUp.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignUp.ts
@@ -121,6 +121,7 @@ export const useSignUpContext = (): SignUpContextType => {
path: ctx.path,
routing: ctx.routing,
baseUrl: signUpUrl,
+ taskUrls: clerk.__internal_getOption('taskUrls'),
});
return {
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 543f45d40a0..f55b669abc4 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -48,7 +48,7 @@ import type {
SignUpFallbackRedirectUrl,
SignUpForceRedirectUrl,
} from './redirects';
-import type { PendingSessionOptions, SignedInSessionResource } from './session';
+import type { PendingSessionOptions, SessionTask, SignedInSessionResource } from './session';
import type { SessionVerificationLevel } from './sessionVerification';
import type { SignInResource } from './signIn';
import type { SignUpResource } from './signUp';
@@ -56,7 +56,7 @@ import type { ClientJSONSnapshot, EnvironmentJSONSnapshot } from './snapshots';
import type { Web3Strategy } from './strategies';
import type { TelemetryCollector } from './telemetry';
import type { UserResource } from './user';
-import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils';
+import type { Autocomplete, CamelCase, DeepPartial, DeepSnakeToCamel } from './utils';
import type { WaitlistResource } from './waitlist';
type __experimental_CheckoutStatus = 'awaiting_initialization' | 'awaiting_confirmation' | 'completed';
@@ -1050,6 +1050,14 @@ export type ClerkOptions = PendingSessionOptions &
* @internal
*/
__internal_keyless_dismissPrompt?: (() => Promise) | null;
+
+ /**
+ * Customize the URL paths users are redirected to after sign-in or sign-up when specific
+ * session tasks need to be completed.
+ *
+ * @default undefined - Uses Clerk's default task flow URLs
+ */
+ taskUrls?: Record, string>;
};
export interface NavigateOptions {
diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts
index a6adc59c33e..84ebb5eaf1d 100644
--- a/packages/types/src/session.ts
+++ b/packages/types/src/session.ts
@@ -326,7 +326,14 @@ export interface PublicUserData {
userId?: string;
}
+/**
+ * Represents a required action that a user must complete
+ * before their session becomes fully active
+ */
export interface SessionTask {
+ /**
+ * The unique identifier for the type of task that needs to be completed
+ */
key: 'org';
}
diff --git a/packages/types/src/utils.ts b/packages/types/src/utils.ts
index 166ce7a1e58..b47b0689aa2 100644
--- a/packages/types/src/utils.ts
+++ b/packages/types/src/utils.ts
@@ -26,6 +26,10 @@ export type CamelToSnake = T extends `${infer C0}${infer R}`
}
: T;
+export type CamelCase = S extends `${infer P1}_${infer P2}${infer P3}`
+ ? `${Lowercase}${Uppercase}${CamelCase}`
+ : Lowercase;
+
/**
* @internal
*/
From 9865e723d6b926b782a4e76203cc89ff66a082e3 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 22 Jul 2025 05:16:22 -0300
Subject: [PATCH 2/5] Update changeset
---
.changeset/whole-knives-attend.md | 2 --
packages/clerk-js/src/core/sessionTasks.ts | 4 ++--
packages/clerk-js/src/ui/common/redirects.ts | 17 ++++++++++-------
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/.changeset/whole-knives-attend.md b/.changeset/whole-knives-attend.md
index 8761ae9c8f1..232c5603fc6 100644
--- a/.changeset/whole-knives-attend.md
+++ b/.changeset/whole-knives-attend.md
@@ -12,5 +12,3 @@ Add `taskUrls` option to customize task flow URLs:
}}
/>
```
-
-**Breaking**: Task key renamed from `'select-organization'` to `'org'`.
diff --git a/packages/clerk-js/src/core/sessionTasks.ts b/packages/clerk-js/src/core/sessionTasks.ts
index 23858a8116c..da44e2a5756 100644
--- a/packages/clerk-js/src/core/sessionTasks.ts
+++ b/packages/clerk-js/src/core/sessionTasks.ts
@@ -27,7 +27,7 @@ export function navigateToTask(
routeKey: keyof typeof INTERNAL_SESSION_TASK_ROUTE_BY_KEY,
{ componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
) {
- const taskRoute = options.taskUrls?.[routeKey] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
+ const taskRoute = `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
if (componentNavigationContext) {
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
@@ -45,5 +45,5 @@ export function navigateToTask(
{ stringify: true },
);
- return globalNavigate(sessionTaskUrl);
+ return globalNavigate(options.taskUrls?.[routeKey] ?? sessionTaskUrl);
}
diff --git a/packages/clerk-js/src/ui/common/redirects.ts b/packages/clerk-js/src/ui/common/redirects.ts
index 354cb48272f..23e805815e0 100644
--- a/packages/clerk-js/src/ui/common/redirects.ts
+++ b/packages/clerk-js/src/ui/common/redirects.ts
@@ -43,13 +43,16 @@ export function buildSessionTaskRedirectUrl({
return null;
}
- return buildRedirectUrl({
- routing,
- baseUrl,
- path,
- endpoint: taskUrls?.[task.key] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
- authQueryString: null,
- });
+ return (
+ taskUrls?.[task.key] ??
+ buildRedirectUrl({
+ routing,
+ baseUrl,
+ path,
+ endpoint: `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
+ authQueryString: null,
+ })
+ );
}
export function buildSSOCallbackURL(
From 7c37f274cf42c3c76bae921b5619fcaed334c706 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 22 Jul 2025 07:57:45 -0300
Subject: [PATCH 3/5] Fix session mock on unit tests
---
packages/clerk-js/src/core/__tests__/clerk.test.ts | 2 +-
packages/clerk-js/src/core/clerk.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts
index be65d9b6952..035da4a48cb 100644
--- a/packages/clerk-js/src/core/__tests__/clerk.test.ts
+++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts
@@ -2350,7 +2350,7 @@ describe('Clerk singleton', () => {
reload: jest.fn(() => Promise.resolve(mockSession)),
};
- beforeAll(() => {
+ beforeEach(() => {
mockResource.touch.mockReturnValueOnce(Promise.resolve());
mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockResource] }));
});
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index 9c978e9e219..6389fb02bc7 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -30,9 +30,9 @@ import type {
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
AuthenticateWithOKXWalletParams,
+ Clerk as ClerkInterface,
ClerkAPIError,
ClerkAuthenticateWithWeb3Params,
- Clerk as ClerkInterface,
ClerkOptions,
ClientJSONSnapshot,
ClientResource,
From 22d3ab8978ecf3d2a9db6acc90b064a27d36a6f2 Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 22 Jul 2025 08:54:36 -0300
Subject: [PATCH 4/5] Add `session-task` to file structure for typedocs
---
.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap | 1 +
1 file changed, 1 insertion(+)
diff --git a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
index f24a109e99b..a36c2dbcd77 100644
--- a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
+++ b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap
@@ -106,6 +106,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
"types/server-get-token.mdx",
"types/session-resource.mdx",
"types/session-status-claim.mdx",
+ "types/session-task.mdx",
"types/session-verification-level.mdx",
"types/session-verification-types.mdx",
"types/set-active-params.mdx",
From 3222c49b6bbcc36f365c4bcfaaf83e7d49344bbe Mon Sep 17 00:00:00 2001
From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com>
Date: Tue, 22 Jul 2025 12:33:48 -0300
Subject: [PATCH 5/5] Remove `CamelCase` type
---
packages/types/src/clerk.ts | 4 ++--
packages/types/src/utils.ts | 4 ----
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index f55b669abc4..3abdb10a664 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -56,7 +56,7 @@ import type { ClientJSONSnapshot, EnvironmentJSONSnapshot } from './snapshots';
import type { Web3Strategy } from './strategies';
import type { TelemetryCollector } from './telemetry';
import type { UserResource } from './user';
-import type { Autocomplete, CamelCase, DeepPartial, DeepSnakeToCamel } from './utils';
+import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils';
import type { WaitlistResource } from './waitlist';
type __experimental_CheckoutStatus = 'awaiting_initialization' | 'awaiting_confirmation' | 'completed';
@@ -1057,7 +1057,7 @@ export type ClerkOptions = PendingSessionOptions &
*
* @default undefined - Uses Clerk's default task flow URLs
*/
- taskUrls?: Record, string>;
+ taskUrls?: Record;
};
export interface NavigateOptions {
diff --git a/packages/types/src/utils.ts b/packages/types/src/utils.ts
index b47b0689aa2..166ce7a1e58 100644
--- a/packages/types/src/utils.ts
+++ b/packages/types/src/utils.ts
@@ -26,10 +26,6 @@ export type CamelToSnake = T extends `${infer C0}${infer R}`
}
: T;
-export type CamelCase = S extends `${infer P1}_${infer P2}${infer P3}`
- ? `${Lowercase}${Uppercase}${CamelCase}`
- : Lowercase;
-
/**
* @internal
*/