Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
414db9f
Run `pnpm dedupe`
LauraBeatris Nov 3, 2025
e9ea329
Add HOC to open modal based on org settings
LauraBeatris Nov 5, 2025
c01e05c
Replace throwing error with opening modal
LauraBeatris Nov 5, 2025
da0ef4a
Move to shared styles
LauraBeatris Nov 7, 2025
18d151f
Revert pnpm-lock.yaml changes
LauraBeatris Nov 10, 2025
6d2d46c
Update `PromptContainer` to exclude keyless styles
LauraBeatris Nov 11, 2025
66e9f7d
Fix line height and use monospace font
LauraBeatris Nov 11, 2025
820f9db
Introduce mutation to enable organization settings
LauraBeatris Nov 11, 2025
65b8905
Mount components once mutation is resolved
LauraBeatris Nov 11, 2025
aa64e19
Introduce draft for enabled prompt state
LauraBeatris Nov 12, 2025
7cf82a8
Extract button to generic component
LauraBeatris Nov 12, 2025
aba82d7
Extract button to generic component
LauraBeatris Nov 12, 2025
5a48d71
Always throw error when rejecting prompt
LauraBeatris Nov 12, 2025
c26c4d5
Adjust focus and hover styles
LauraBeatris Nov 12, 2025
189c3b7
Extract Dashboard URL to display within anchor tag
LauraBeatris Nov 12, 2025
8a1f192
Bump bundlewatch
LauraBeatris Nov 13, 2025
bd81f0b
Add separate chunk on bundlwatch
LauraBeatris Nov 13, 2025
17891ea
Refactor UI to include personal account toogle
LauraBeatris Nov 13, 2025
4fdbcc9
Add changeset
LauraBeatris Nov 13, 2025
f6362dd
Send default value for `allow_personal_account` option
LauraBeatris Nov 17, 2025
22f576a
Adjust bundlwatch
LauraBeatris Nov 17, 2025
a988488
Show warning if there is not an active session
LauraBeatris Nov 18, 2025
43bae8d
feat: Add support for `initialFocusRef` (#7247)
alexcarpenter Nov 18, 2025
5e42490
refactor: Button variant improvements (#7249)
alexcarpenter Nov 18, 2025
56fbc71
Fix `dev_tools` endpoint
LauraBeatris Nov 18, 2025
5c1571c
Display prompt even without session
LauraBeatris Nov 18, 2025
f349e35
refactor: Switch improvements (#7253)
alexcarpenter Nov 18, 2025
ad77be6
Fix `EnableEnvironmentSettingParams`
LauraBeatris Nov 18, 2025
4ff0e54
Add a "sign-in to continue" button when unauthenticated
LauraBeatris Nov 18, 2025
864643f
Fix isomorphic clerk types
LauraBeatris Nov 18, 2025
938457e
Continue to display personal account toggle regardless of session
LauraBeatris Nov 19, 2025
f3b01ea
update switch color
alexcarpenter Nov 19, 2025
3f94b70
Use `useOrganizationContext` in `useCheckout`
LauraBeatris Nov 19, 2025
7c06473
Conditionally display personal accounts toggle based on environment
LauraBeatris Nov 19, 2025
cf9a27c
Fix check for `forceOrganizationSelection`
LauraBeatris Nov 19, 2025
fe2d1ed
Conditionally send `organization_allow_personal_accounts` param
LauraBeatris Nov 19, 2025
8ec969c
Update `organization_allow_personal_accounts` to optional
LauraBeatris Nov 19, 2025
b3b3cee
Fix import for `@emotion/react`
LauraBeatris Nov 19, 2025
4dfdabc
feat: animate height of switch (#7263)
alexcarpenter Nov 19, 2025
e1b1273
Use `useOrganizationContext` in `APIKeys`
LauraBeatris Nov 19, 2025
1ad837b
Update copy
LauraBeatris Nov 19, 2025
e67acbc
Display Clerk icon with animation
LauraBeatris Nov 19, 2025
21aed04
Update commerce context to use `useOrganizationContext`
LauraBeatris Nov 20, 2025
04ceaa5
Update mock on commerce unit tests
LauraBeatris Nov 20, 2025
728ab6e
Adjust vue composable to execute when Clerk is loaded
LauraBeatris Nov 20, 2025
517f532
Safely execute prompt guard within hooks
LauraBeatris Nov 20, 2025
3e14e69
Use `pnpm` for nuxt integration test script
LauraBeatris Nov 20, 2025
08a4cbd
Rollback publishable key changes from sandbox
LauraBeatris Nov 20, 2025
4974e2e
Attempt to fix Nuxt errors
LauraBeatris Nov 20, 2025
a7cdb01
Hydrate preopenEnableOrganizationsPrompt
LauraBeatris Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/flat-ravens-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/clerk-react': minor
'@clerk/vue': minor
---

Introduce in-app development prompt to enable the Organizations feature

In development instances, when using organization components or hooks for the first time, developers will see a prompt to enable the Organizations feature directly in their app, eliminating the need to visit the Clerk Dashboard.
9 changes: 5 additions & 4 deletions packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"files": [
{ "path": "./dist/clerk.js", "maxSize": "840KB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "81KB" },
{ "path": "./dist/clerk.channel.browser.js", "maxSize": "81KB" },
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "123KB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "83KB" },
{ "path": "./dist/clerk.channel.browser.js", "maxSize": "83KB" },
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "125KB" },
{ "path": "./dist/clerk.headless*.js", "maxSize": "65KB" },
{ "path": "./dist/ui-common*.js", "maxSize": "117.1KB" },
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "120.1KB" },
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "122KB" },
{ "path": "./dist/vendors*.js", "maxSize": "47KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "38KB" },
{ "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" },
Expand All @@ -23,6 +23,7 @@
{ "path": "./dist/onetap*.js", "maxSize": "1KB" },
{ "path": "./dist/waitlist*.js", "maxSize": "1.5KB" },
{ "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" },
{ "path": "./dist/enableOrganizationsPrompt*.js", "maxSize": "6.5KB" },
{ "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" },
{ "path": "./dist/checkout*.js", "maxSize": "8.82KB" },
{ "path": "./dist/up-billing-page*.js", "maxSize": "3.0KB" },
Expand Down
143 changes: 122 additions & 21 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
import type {
__experimental_CheckoutInstance,
__experimental_CheckoutOptions,
__internal_AttemptToEnableEnvironmentSettingParams,
__internal_CheckoutProps,
__internal_EnableOrganizationsPromptProps,
__internal_OAuthConsentProps,
__internal_PlanDetailsProps,
__internal_SubscriptionDetailsProps,
Expand Down Expand Up @@ -745,6 +747,58 @@ export class Clerk implements ClerkInterface {
void this.#componentControls.ensureMounted().then(controls => controls.closeModal('userVerification'));
};

public __internal_attemptToEnableEnvironmentSetting = (
params: __internal_AttemptToEnableEnvironmentSettingParams,
): { status: 'enabled' | 'prompt-shown' } => {
const { for: setting, caller } = params;

if (!this.user) {
console.warn(
`Clerk: "${caller}" requires an active user session. Ensure a user is signed in before executing ${caller}.`,
);
}

if (
// If not in development instance, return enabled status in order to not open the prompt
this.#instanceType !== 'development'
) {
return { status: 'enabled' };
}

switch (setting) {
case 'organizations':
if (!disabledOrganizationsFeature(this, this.environment)) {
return { status: 'enabled' };
}

this.__internal_openEnableOrganizationsPrompt({
caller,
// Reload current window to all invalidate all resources
// related to organizations, eg: roles
onSuccess: () => window.location.reload(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still some delay in which the window is reloading and the screen kepts showing a blank state without having the component mounted due to the stale environment data.

I'm thinking of applying an optimistic update, at least on the "enabled" status, to have the components mounted meanwhile, or not closing the modal and introduce a loading status until the window gets reloaded.

onClose: params.onClose,
} as __internal_EnableOrganizationsPromptProps);

return { status: 'prompt-shown' };
default:
return { status: 'enabled' };
}
};

public __internal_openEnableOrganizationsPrompt = (props: __internal_EnableOrganizationsPromptProps): void => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls
.ensureMounted({ preloadHint: 'EnableOrganizationsPrompt' })
.then(controls => controls.openModal('enableOrganizationsPrompt', props || {}));

this.telemetry?.record(eventPrebuiltComponentMounted('EnableOrganizationsPrompt', props));
};

public __internal_closeEnableOrganizationsPrompt = (): void => {
this.assertComponentsReady(this.#componentControls);
void this.#componentControls.ensureMounted().then(controls => controls.closeModal('enableOrganizationsPrompt'));
};

public __internal_openBlankCaptchaModal = (): Promise<unknown> => {
this.assertComponentsReady(this.#componentControls);
return this.#componentControls
Expand Down Expand Up @@ -816,14 +870,21 @@ export class Clerk implements ClerkInterface {

public openOrganizationProfile = (props?: OrganizationProfileProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'OrganizationProfile',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

if (noOrganizationExists(this)) {
if (this.#instanceType === 'development') {
throw new ClerkRuntimeError(warnings.cannotRenderComponentWhenOrgDoesNotExist, {
Expand All @@ -846,14 +907,21 @@ export class Clerk implements ClerkInterface {

public openCreateOrganization = (props?: CreateOrganizationProps): void => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'CreateOrganization',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

void this.#componentControls
.ensureMounted({ preloadHint: 'CreateOrganization' })
.then(controls => controls.openModal('createOrganization', props || {}));
Expand Down Expand Up @@ -988,14 +1056,21 @@ export class Clerk implements ClerkInterface {

public mountOrganizationProfile = (node: HTMLDivElement, props?: OrganizationProfileProps) => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'OrganizationProfile',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

const userExists = !noUserExists(this);
if (noOrganizationExists(this) && userExists) {
if (this.#instanceType === 'development') {
Expand Down Expand Up @@ -1028,14 +1103,21 @@ export class Clerk implements ClerkInterface {

public mountCreateOrganization = (node: HTMLDivElement, props?: CreateOrganizationProps) => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'CreateOrganization',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

void this.#componentControls?.ensureMounted({ preloadHint: 'CreateOrganization' }).then(controls =>
controls.mountComponent({
name: 'CreateOrganization',
Expand All @@ -1059,14 +1141,21 @@ export class Clerk implements ClerkInterface {

public mountOrganizationSwitcher = (node: HTMLDivElement, props?: OrganizationSwitcherProps) => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'OrganizationSwitcher',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationSwitcher'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationSwitcher' }).then(controls =>
controls.mountComponent({
name: 'OrganizationSwitcher',
Expand Down Expand Up @@ -1098,14 +1187,21 @@ export class Clerk implements ClerkInterface {

public mountOrganizationList = (node: HTMLDivElement, props?: OrganizationListProps) => {
this.assertComponentsReady(this.#componentControls);
if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {

const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'OrganizationList',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationList'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationList' }).then(controls =>
controls.mountComponent({
name: 'OrganizationList',
Expand Down Expand Up @@ -1294,12 +1390,17 @@ export class Clerk implements ClerkInterface {
public mountTaskChooseOrganization = (node: HTMLDivElement, props?: TaskChooseOrganizationProps) => {
this.assertComponentsReady(this.#componentControls);

if (disabledOrganizationsFeature(this, this.environment)) {
if (this.#instanceType === 'development') {
const { status } = this.__internal_attemptToEnableEnvironmentSetting({
for: 'organizations',
caller: 'TaskChooseOrganization',
onClose: () => {
throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('TaskChooseOrganization'), {
code: CANNOT_RENDER_ORGANIZATIONS_DISABLED_ERROR_CODE,
});
}
},
});

if (status === 'prompt-shown') {
return;
}

Expand Down
21 changes: 21 additions & 0 deletions packages/clerk-js/src/core/resources/DevTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ClerkResourceJSON, DevToolsResource, EnableEnvironmentSettingParams } from '@clerk/shared/types';

import { BaseResource } from './Base';

/**
* @internal
*/
export class DevTools extends BaseResource implements DevToolsResource {
pathRoot = '/dev_tools';

protected fromJSON(_data: ClerkResourceJSON | null): this {
return this;
}

async __internal_enableEnvironmentSetting(params: EnableEnvironmentSettingParams) {
await this._basePatch({
path: `${this.pathRoot}/enable_environment_setting`,
body: params,
});
}
}
Loading
Loading