BuildBaseBuildBase

Guest users

Let people try your app with one click as a guest, then keep everything they made by adding an email to the same account.

A sign-up form is a wall between a curious visitor and the thing they came to try. A guest method takes the wall down: Continue as guest on your sign-in page makes a real account with no address, signs the person in, and lets them use your app straight away. When they decide to stay, they add an email, and that same account, with the same user ID and everything hanging off it, becomes an ordinary one.

Step by step, with screenshots: Set up guest users.

import { GuestUpgrade, useSaaSAuth } from '@buildbase/sdk/react';

function SaveYourWork() {
  const { user } = useSaaSAuth();
  if (!user?.isAnonymous) return null;

  return <GuestUpgrade />;
}

Before you start

Guest users need a plan that includes them. Without one, the console shows the method locked and adding it is refused with a 402.

Setting it up

  1. Open User Management → Access → Authentication → Methods, choose Add auth method, then Guest. The title is your own name for the method, shown in the console; the button always reads "Continue as guest", in the language the page is showing.
  2. Tick it on your client in the Settings tab.

The button sits on the sign-in screen, below every other way in. It shows only while public sign-up is open and Who can sign up is set to Anyone: a guest has no address, so no allowlist or waitlist could name one.

What a guest is

  • A real account. Named Guest, with a placeholder address of the form <userId>@anonymous.invalid that is never shown. In the SDK, user.isAnonymous is true and user.email is empty.
  • An end user. Guests count toward your plan's end-user limit. When the limit is reached, Continue as guest is refused. The idle cleanup below is what keeps the count honest.
  • Never mailed. No welcome, no verification, no notification: BuildBase sends nothing to an .invalid address, whatever asks for it. Guests are not added to your audience, and making one is not a sign-up, so user.registered waits for the upgrade.
  • Marked in the console. The users list shows them with a Guest badge, and filters to them.
  • No second step. There is no factor to ask for and no address to set one up against. The second step starts once they upgrade. See Two-step sign-in.

A guest cannot change their address, connect a wallet, add a passkey or set up two-step sign-in (each answers 409 guest_account): they create an account first.

Bot protection. Whenever bot protection is on, the guest button is checked like a sign-up, since one click makes an account. One network address can make at most 10 guests an hour, then gets 429 rate_limited.

Upgrading in place

<GuestUpgrade /> is the ready-made form, titled "Create your account": an address, an optional name and password, then a 6-digit code mailed to the address, with Resend counting down. The SDK's Profile screen shows it in place of the email for a guest, and the Security screen shows it in place of the password, phone, two-step and passkey sections.

For a form of your own, useGuestUpgrade() holds the same steps:

import { useState } from 'react';
import { useGuestUpgrade } from '@buildbase/sdk/react';

function CreateAccount() {
  const upgrade = useGuestUpgrade();
  const [value, setValue] = useState('');
  if (!upgrade.isGuest) return null;

  const codeStep = upgrade.step === 'code';
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        if (codeStep) upgrade.confirm(value);
        else upgrade.start({ email: value });
        setValue('');
      }}
    >
      <input
        value={value}
        onChange={(event) => setValue(event.target.value)}
        placeholder={codeStep ? '6-digit code' : '[email protected]'}
      />
      <button disabled={upgrade.pending !== null}>
        {codeStep ? 'Create my account' : 'Email me a code'}
      </button>
      {upgrade.error && <p role="alert">{upgrade.error.message}</p>}
    </form>
  );
}

step moves from details to code to done. pending, error (with the server's sentence) and waitSeconds drive the buttons; resend() sends the code again and reset() goes back to the details. <GuestUpgrade onUpgraded={...} /> tells you when it is done. Once the code is accepted, useSaaSAuth().user is the account at once, with isAnonymous false and the address verified. The session stays signed in.

The upgrade is held to everything a sign-up is: who can sign up, closed sign-up, disposable addresses if you refuse them, and breached passwords. The address has to be free here. Without a password the account gets one nobody knows, as after a social sign-up, and Forgot password? sets a real one.

A social account upgrades a guest too. Connecting Google or another provider from the Security screen turns the guest into an account with the provider's address, when that address is verified, free and allowed to sign up. Otherwise nothing is linked, so a guest never ends up with a way in but no account to keep.

Either way, user.registered fires at the upgrade, with fromGuest: true, so a workflow on sign-ups sees the person once, when they gave you an address.

Idle guests

A guest nobody has used for 30 days is deleted, through the same scrub as a deleted account, with user.deleted and by: 'guest_expired'. Change the 30 under Security, in the Account deletion card, with Remove idle guests after (days): from 1 to 365. It is guests.idleDays on the auth config. A guest who upgrades is no longer removed for being idle.

"Used" means the session was used: the last-seen time moves at most once an hour while it is. A guest who created a workspace that other people joined is held, as a deletion would be.

A guest can delete their own account at any time without re-confirming, since there is no way for them to sign in again.

The API

RouteBodyAnswers
POST /api/v1/auth/guest{ turnstileToken? }A sign-in, as /login answers
POST /api/v1/public/guest/upgrade{ email, name?, password? }{ retryAfter }, the seconds before another code
POST /api/v1/public/guest/upgrade/confirm{ code }The profile, now an account

The first is the sign-in page's, with its state header. The other two use the guest's session, as WorkspaceApi.requestGuestUpgrade() and confirmGuestUpgrade() send them. The code lasts 10 minutes and allows 5 tries.

RefusalWhen
403 method_unavailableThe client has no guest method
403 signup_closedSign-up is closed, the mode is not Anyone, or the end-user limit is reached
403 bot_check_failedThe bot protection check failed
429 rate_limited10 guests from this address in the last hour
409 email_unavailableThe address is taken here, or cannot be used
409 not_a_guestThe user is already an account
403 signup_restricted, waitlist_requiredThe address is not allowed to sign up
400 disposable_email, breached_passwordAs for any sign-up
429 cooldownA code went out moments ago. Carries retryAfter
400 invalid_codeThe code did not match
410 code_expiredNo code is waiting, it lapsed, or its tries are used

Next steps