HeyPuter/puter · error · HttpError
signup_disabled
signup_disabled
Error message
User registration is disabled.
What it means
Thrown by POST /signup with HTTP 403 (legacyCode 'signup_disabled') when config.disable_user_signup is true AND the request is not 'claiming' an existing pseudo-user. A pseudo-user is an unconfirmed, password-null placeholder row (e.g. admin-pre-provisioned); claiming one is still allowed when signup is globally disabled. The check intentionally runs before duplicate checks so a closed endpoint does not leak which usernames/emails exist.
Source
Thrown at src/backend/controllers/auth/AuthController.ts:790
// disabled endpoint doesn't reveal which usernames or emails
// exist. Claiming a pre-existing placeholder row is still
// allowed, so permanent signups look the email up first.
if (this.config.disable_user_signup) {
let claimable = false;
if (!is_temp) {
const existing =
(await this.stores.user.getByEmail(body.email)) ??
(await this.stores.user.getByCleanEmail(
cleanEmail(body.email),
));
claimable = Boolean(
existing &&
!existing.email_confirmed &&
existing.password === null,
);
}
if (!claimable) {
throw new HttpError(403, 'User registration is disabled.', {
legacyCode: 'signup_disabled',
});
}
}
// Duplicate username check
if (await this.stores.user.getByUsername(body.username)) {
throw new HttpError(
400,
'This username already exists in our database. Please use another one.',
{ legacyCode: 'bad_request' },
);
}
// Duplicate confirmed-email check. A confirmed account (any
// credential type — password OR OIDC) on this email → reject.
//
// A pseudo-user is an UNCONFIRMED placeholder row: emailView on GitHub (pinned to 908ec23eda)
Solutions
- Confirm whether disable_user_signup is intended; if registration should be open, unset/flip it in the backend config.
- If invite-only is intended, have an admin pre-create the pseudo-user row (unconfirmed, password null) for the invitee's email, then have them sign up with that exact email to claim it.
- Surface this state to users with a clear 'registration is closed' message rather than retry loops.
Example fix
// before (operator): registration closed, users see signup_disabled
// config.json
{ "disable_user_signup": true }
// after: open registration
{ "disable_user_signup": false }
// — or, invite-only: admin pre-creates a pseudo-user row with the invitee's email,
// password NULL and email_confirmed=0, then the invitee signs up with that email. Defensive patterns
Strategy: try-catch
Validate before calling
// operator check: expose the flag to clients if you can
// GET /config/public -> { disable_user_signup: true }
if (serverConfig.disable_user_signup && !isClaimingPseudoUser(email)) {
showNotice('Registration is closed on this server');
return;
} Try / catch
try {
await signup(payload);
} catch (e) {
if (e.statusCode === 403 && e.legacyCode === 'signup_disabled') {
showRegistrationClosedNotice();
} else throw e;
} Prevention
- Surface disable_user_signup to clients so the UI can hide/disable the signup form.
- For invite-only, pre-create pseudo-user rows (password NULL, email_confirmed 0) and have invitees claim by email.
- Communicate 'registration closed' clearly instead of letting users retry.
When it happens
Trigger: Server configured with disable_user_signup:true, and the caller submits a fresh username/email pair that has no matching unconfirmed/password-null placeholder row. Claiming a pre-provisioned pseudo-user by submitting its email is the only path through.
Common situations: Self-hosted or locked-down deployments that set disable_user_signup to forbid open registration; an invite-only system where admins pre-create pseudo-user rows that invitees then claim; accidentally leaving the flag on after a migration.
Related errors
- Signup blocked Request Code: ${requestCode}
- must_login_or_signup
- forbidden
- username_already_in_use
- forbidden
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/e79802655163692e.
Report an issue: GitHub.