diff --git a/.changeset/wild-insects-design.md b/.changeset/wild-insects-design.md
new file mode 100644
index 00000000000..b0051fad7ec
--- /dev/null
+++ b/.changeset/wild-insects-design.md
@@ -0,0 +1,5 @@
+---
+"@clerk/ui": patch
+---
+
+Fixed unhandled TypeError when `unsafeMetadata` is passed to ``
diff --git a/packages/ui/src/components/SignUp/SignUpStart.tsx b/packages/ui/src/components/SignUp/SignUpStart.tsx
index 8400c8984ce..e719bbaf5c3 100644
--- a/packages/ui/src/components/SignUp/SignUpStart.tsx
+++ b/packages/ui/src/components/SignUp/SignUpStart.tsx
@@ -259,7 +259,15 @@ function SignUpStartInternal(): JSX.Element {
}, [] as Array);
if (unsafeMetadata) {
- fieldsToSubmit.push({ id: 'unsafeMetadata', value: unsafeMetadata } as any);
+ const noop = () => {};
+ fieldsToSubmit.push({
+ id: 'unsafeMetadata',
+ value: unsafeMetadata,
+ clearFeedback: noop,
+ setValue: noop,
+ onChange: noop,
+ setError: noop,
+ } as any);
}
if (fields.ticket || hasExistingSignUpWithTicket) {
diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
index e85ee5184fa..d5f4349e365 100644
--- a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
+++ b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx
@@ -1,3 +1,4 @@
+import { ClerkAPIResponseError } from '@clerk/shared/error';
import { OAUTH_PROVIDERS } from '@clerk/shared/oauth';
import type { SignUpResource } from '@clerk/shared/types';
import { describe, expect, it, vi } from 'vitest';
@@ -522,4 +523,57 @@ describe('SignUpStart', () => {
await waitFor(() => screen.getByText(/create your account/i));
});
});
+
+ describe('unsafeMetadata', () => {
+ it('does not throw when signUp.create rejects with an API error', async () => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: { href: 'http://localhost/sign-up' },
+ });
+
+ let unhandledError: unknown = null;
+ const onUnhandledRejection = (reason: unknown) => {
+ unhandledError = reason;
+ };
+ process.on('unhandledRejection', onUnhandledRejection);
+
+ const { wrapper, fixtures, props } = await createFixtures(f => {
+ f.withEmailAddress({ required: true });
+ f.withPassword({ required: true });
+ });
+ fixtures.signUp.create.mockRejectedValueOnce(
+ new ClerkAPIResponseError('Error', {
+ data: [
+ {
+ code: 'form_password_pwned',
+ long_message: 'Password has been found in an online data breach.',
+ message: 'Password has been found in an online data breach.',
+ meta: { param_name: 'password' },
+ },
+ ],
+ status: 422,
+ }),
+ );
+ props.setProps({ unsafeMetadata: { foo: 'bar' } });
+
+ const { userEvent } = render(
+
+
+ ,
+ { wrapper },
+ );
+
+ await userEvent.type(screen.getByLabelText(/email address/i), 'test@example.com');
+ await userEvent.type(screen.getByPlaceholderText(/create a password/i), 'password123');
+ await userEvent.click(screen.getByText(/continue/i));
+
+ await waitFor(() => expect(fixtures.signUp.create).toHaveBeenCalled());
+ await screen.findByTestId('form-feedback-error');
+ // Flush pending microtasks so any unhandled rejection event has a chance to fire.
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ process.off('unhandledRejection', onUnhandledRejection);
+ expect(unhandledError).toBeNull();
+ });
+ });
});