# Sign-in-or-up custom flow (Legacy)

> This guide is for users who want to build a custom flow. To use a _prebuilt_ UI, use the [Account Portal pages](https://clerk.com/docs/guides/account-portal/overview.md) or [prebuilt components](https://clerk.com/docs/reference/components/overview.md).

> This guide uses the Core 2 `useSignIn()` and `useSignUp()` hooks, which are available in Core 3 SDKs by adding the `/legacy` subpath to the import path. If you're using a Core 2 SDK, remove the `/legacy` subpath.

This guide demonstrates how to build a custom user interface that **allows users to sign up or sign in within a single flow**. It uses email and password authentication, but you can modify this approach according to the needs of your application.

## Enable email and password authentication

To use email and password authentication, you first need to ensure they are enabled for your application.

1. In the Clerk Dashboard, navigate to the [**User & authentication**](https://dashboard.clerk.com/last-active?path=user-authentication/user-and-authentication) page.
2. Enable **Sign-up with email** and **Sign-in with email**.
3. Select the **Password** tab and enable **Sign-up with password**. Leave **Require a password at sign-up** enabled.

> By default, **Email verification code** is enabled for both sign-up and sign-in. This means that when a user signs up using their email address, Clerk sends a one-time code to their email address. The user must then enter this code to verify their email and complete the sign-up process. When the user uses the email address to sign in, they are emailed a one-time code to sign in. If you'd like to use **Email verification link** instead, see the [custom flow for email links](https://clerk.com/docs/guides/development/custom-flows/authentication/email-links.md).

## Sign-in-or-up flow

Because this guide uses email and password authentication, the example uses the code examples from the [email/password custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/email-password.md) guide. If you are using a different authentication method, such as [email or phone OTP](https://clerk.com/docs/guides/development/custom-flows/authentication/email-sms-otp.md) or [email links](https://clerk.com/docs/guides/development/custom-flows/authentication/email-links.md), you will need to adapt the code accordingly.

To blend a sign-up and sign-in flow into a single flow, you must treat it as a sign-in flow, but with the ability to sign up a new user if they don't have an account. You can do this by **checking for the `form_identifier_not_found` error** if the sign-in process fails, and then starting the sign-up process.

filename: app/sign-in/page.tsx
```tsx
'use client'

import * as React from 'react'
import { useSignIn, useSignUp } from '@clerk/nextjs/legacy'
import { useRouter } from 'next/navigation'
import type { EmailCodeFactor } from '@clerk/shared/types'

export default function SignInForm() {
  const { isLoaded, signIn, setActive } = useSignIn()
  const { signUp } = useSignUp()
  const [email, setEmail] = React.useState('')
  const [password, setPassword] = React.useState('')
  const [code, setCode] = React.useState('')
  const [showEmailCode, setShowEmailCode] = React.useState(false)
  const router = useRouter()

  // Handle the submission of the form
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    if (!isLoaded) return

    // Start the sign-in process using the email and password provided
    // If the user is not signed up yet, this will catch the `form_identifier_not_found` error
    try {
      const signInAttempt = await signIn.create({
        identifier: email,
        password,
      })

      // If sign-in process is complete, set the created session as active
      // and redirect the user
      if (signInAttempt.status === 'complete') {
        await setActive({
          session: signInAttempt.createdSessionId,
          navigate: async ({ session, decorateUrl }) => {
            if (session?.currentTask) {
              // Handle pending session tasks
              // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
              console.log(session?.currentTask)
              return
            }

            const url = decorateUrl('/')
            if (url.startsWith('http')) {
              window.location.href = url
            } else {
              router.push(url)
            }
          },
        })
      } else if (signInAttempt.status === 'needs_second_factor') {
        // Check if email_code is a valid second factor
        // This is required when Client Trust is enabled and the user
        // is signing in from a new device.
        // See https://clerk.com/docs/guides/secure/client-trust
        const emailCodeFactor = signInAttempt.supportedSecondFactors?.find(
          (factor): factor is EmailCodeFactor => factor.strategy === 'email_code',
        )

        if (emailCodeFactor) {
          await signIn.prepareSecondFactor({
            strategy: 'email_code',
            emailAddressId: emailCodeFactor.emailAddressId,
          })

          // Display second form to capture the verification code
          setShowEmailCode(true)
        }
      } else {
        // If the status is not complete, check why. User may need to
        // complete further steps.
        console.error(JSON.stringify(signInAttempt, null, 2))
      }
    } catch (err: any) {
      // See https://clerk.com/docs/guides/development/custom-flows/error-handling
      // for more info on error handling
      console.error(JSON.stringify(err, null, 2))

      // If the identifier is not found, the user is not signed up yet
      // So this includes the flow for signing up a new user
      if (err.errors[0].code === 'form_identifier_not_found') {
        // Start the sign-up process using the email and password provided
        try {
          await signUp?.create({
            emailAddress: email,
            password,
          })

          // Send the user an email with the verification code
          await signUp?.prepareEmailAddressVerification({
            strategy: 'email_code',
          })

          // Display second form to capture the verification code
          setShowEmailCode(true)
        } catch (err: any) {
          // See https://clerk.com/docs/guides/development/custom-flows/error-handling
          // for more info on error handling
          console.error(JSON.stringify(err, null, 2))
        }
      }
    }
  }

  // Handle the submission of the email verification code
  const handleEmailCode = async (e: React.FormEvent) => {
    e.preventDefault()

    if (!isLoaded) return

    // Flow for signing up a new user
    if (signUp) {
      try {
        // Use the code the user provided to attempt verification
        const signUpAttempt = await signUp.attemptEmailAddressVerification({
          code,
        })

        // If verification was completed, set the session to active
        // and redirect the user
        if (signUpAttempt.status === 'complete') {
          await setActive({
            session: signUpAttempt.createdSessionId,
            navigate: async ({ session, decorateUrl }) => {
              if (session?.currentTask) {
                // Handle pending session tasks
                // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
                console.log(session?.currentTask)
                return
              }

              const url = decorateUrl('/')
              if (url.startsWith('http')) {
                window.location.href = url
              } else {
                router.push(url)
              }
            },
          })
        } else {
          // If the status is not complete, check why. User may need to
          // complete further steps.
          console.error('Sign-up attempt not complete:', signUpAttempt)
          console.error('Sign-up attempt status:', signUpAttempt.status)
        }
      } catch (err: any) {
        // See https://clerk.com/docs/guides/development/custom-flows/error-handling
        // for more info on error handling
        console.error(JSON.stringify(err, null, 2))
      }
    }

    // Flow for signing in an existing user
    try {
      const signInAttempt = await signIn.attemptSecondFactor({
        strategy: 'email_code',
        code,
      })

      if (signInAttempt.status === 'complete') {
        await setActive({
          session: signInAttempt.createdSessionId,
          navigate: async ({ session, decorateUrl }) => {
            if (session?.currentTask) {
              console.log(session?.currentTask)
              return
            }

            const url = decorateUrl('/')
            if (url.startsWith('http')) {
              window.location.href = url
            } else {
              router.push(url)
            }
          },
        })
      } else {
        // If the status is not complete, check why. User may need to
        // complete further steps.
        console.error('Sign-up attempt not complete:', signInAttempt)
        console.error('Sign-up attempt status:', signInAttempt.status)
      }
    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
    }
  }

  // Display email code verification form
  if (showEmailCode) {
    return (
      <>
        <h1>Verify your email</h1>
        <p>A verification code has been sent to your email.</p>
        <form onSubmit={handleEmailCode}>
          <div>
            <label htmlFor="code">Enter verification code</label>
            <input
              onChange={(e) => setCode(e.target.value)}
              id="code"
              name="code"
              type="text"
              inputMode="numeric"
              value={code}
            />
          </div>
          <button type="submit">Verify</button>
        </form>
      </>
    )
  }

  // Display a form to capture the user's email and password
  return (
    <>
      <h1>Sign up/sign in</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Enter email address</label>
          <input
            onChange={(e) => setEmail(e.target.value)}
            id="email"
            name="email"
            type="email"
            value={email}
          />
        </div>
        <div>
          <label htmlFor="password">Enter password</label>
          <input
            onChange={(e) => setPassword(e.target.value)}
            id="password"
            name="password"
            type="password"
            value={password}
          />
        </div>
        <button type="submit">Continue</button>
      </form>

      {/* Required for sign-up flows
  Clerk's bot sign-up protection is enabled by default */}
      <div id="clerk-captcha" />
    </>
  )
}
```

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
