laurent22/joplin · error · ErrorForbidden

Signup is not enabled

Error message

Signup is not enabled

What it means

The POST signup route throws ErrorForbidden('Signup is not enabled') when config().signupEnabled is false. The Joplin Server administrator must explicitly enable self-service account creation; otherwise all signup attempts are rejected with 403.

Solutions

  1. Enable signup in the server config (set SIGNUP_ENABLED or the equivalent config value to true) and restart the server.
  2. Ask the server administrator to create the account via the admin user-management route instead.
  3. Check that you are posting to the correct server whose config you updated.

Example fix

// before (docker-compose / env)
# signup not configured
// after
environment:
  - Signup_Enabled=true
Defensive patterns

Strategy: try-catch

Try / catch

try { await api.post('/signup', form); } catch (e) { if (e.httpStatus === 403) showMessage('Signup is disabled on this server'); }

Prevention

When it happens

Trigger: POSTing registration form data (email/password/repeat password) to the signup endpoint on a server where signupEnabled is not set to true in the config.

Common situations: Self-hosted Joplin Server default configuration where signup is disabled; users sharing a server URL expecting open registration; after a config migration that dropped the signupEnabled flag.

Related errors


AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17). Data as JSON: /api/errors/46f6eef4165ce56b. Report an issue: GitHub.

Appendix: source

Thrown at packages/server/src/routes/index/signup.ts:41

}

export interface FormUser {
	full_name: string;
	email: string;
	password: string;
	password2: string;
}

const router: Router = new Router(RouteType.Web);

router.public = true;

router.get('signup', async (_path: SubPath, _ctx: AppContext) => {
	return makeView();
});

router.post('signup', async (_path: SubPath, ctx: AppContext) => {
	if (!config().signupEnabled) throw new ErrorForbidden('Signup is not enabled');

	await limiterSignupBruteForce(userIp(ctx));

	try {
		const formUser = await bodyFields<FormUser>(ctx.req);
		const password = checkRepeatPassword(formUser, true);

		const user = await ctx.joplin.models.user().save({
			account_type: AccountType.Basic,
			email: formUser.email,
			full_name: formUser.full_name,
			password,
		});

		const session = await ctx.joplin.models.session().createUserSession(user.id);
		cookieSet(ctx, 'sessionId', session.id);

		return redirect(ctx, `${config().baseUrl}/home`);

View on GitHub (pinned to 981a03c5c9)