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
- 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.
- 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.invalidthat is never shown. In the SDK,user.isAnonymousistrueanduser.emailis 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
.invalidaddress, whatever asks for it. Guests are not added to your audience, and making one is not a sign-up, souser.registeredwaits 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
| Route | Body | Answers |
|---|---|---|
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.
| Refusal | When |
|---|---|
403 method_unavailable | The client has no guest method |
403 signup_closed | Sign-up is closed, the mode is not Anyone, or the end-user limit is reached |
403 bot_check_failed | The bot protection check failed |
429 rate_limited | 10 guests from this address in the last hour |
409 email_unavailable | The address is taken here, or cannot be used |
409 not_a_guest | The user is already an account |
403 signup_restricted, waitlist_required | The address is not allowed to sign up |
400 disposable_email, breached_password | As for any sign-up |
429 cooldown | A code went out moments ago. Carries retryAfter |
400 invalid_code | The code did not match |
410 code_expired | No code is waiting, it lapsed, or its tries are used |
Next steps
- Who can sign up - why guests need sign-up open to anyone.
- Connected Accounts - the other way a guest becomes an account.
- Deleting accounts - the scrub idle guests go through.