laurent22/joplin · error · ErrorUnprocessableEntity

password must be set

Error message

password must be set

What it means

UserModel.validate throws ErrorUnprocessableEntity('password must be set') when creating a new user (options.isNew) with neither a password nor must_set_password set. New users must either have a (pre-hashed) password or be flagged to set one later, e.g. via an invite/email link. The password itself is not validated here because it is already hashed by the time validate runs.

Solutions

  1. Include a password field (which will be hashed upstream) in the user object when saving a new user.
  2. If the user should set the password later, set must_set_password: true on the new user.
  3. Verify the hashing middleware/hook did not drop or clear the password field before save().

Example fix

// before
await models.user().save({ email: 'a@b.com' }, { isNew: true });
// after
await models.user().save({ email: 'a@b.com', password: 'plain', must_set_password: false }, { isNew: true });
// or, for invited users:
await models.user().save({ email: 'a@b.com', must_set_password: true }, { isNew: true });
Defensive patterns

Strategy: validation

Validate before calling

if (isNewUser && !user.password && !user.must_set_password) throw new Error('password or must_set_password required');

Type guard

function hasNewUserCredential(u: { password?: string; must_set_password?: boolean }): boolean { return !!u.password || u.must_set_password === true; }

Try / catch

try { await models.user().save(user, { isNew: true }); } catch (e) { if (e.message === 'password must be set') /* prompt for password or set must_set_password */; }

Prevention

When it happens

Trigger: Calling UserModel.save() with options.isNew true and a user object that has empty/undefined password and must_set_password falsy (falsy or absent).

Common situations: Admin scripts or API calls creating users while omitting the password field; provisioning code that intends to email a set-password link but forgets must_set_password: true; migrating users from another system without hashes.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at packages/server/src/models/UserModel.ts:466

		const result = zxcvbn(password);
		if (result.score < 3) {
			let msg: string[] = [result.feedback.warning];
			if (result.feedback.suggestions) {
				msg = msg.concat(result.feedback.suggestions);
			}
			throw new ErrorUnprocessableEntity(msg.join(' '));
		}
	}

	protected async validate(object: User, options: ValidateOptions = {}): Promise<User> {
		const user: User = await super.validate(object, options);

		// Note that we don't validate the password here because it's already
		// been hashed by then.
		if (options.isNew) {
			if (!user.email) throw new ErrorUnprocessableEntity('email must be set');
			if ('email' in user && !user.email.includes('@')) throw new ErrorUnprocessableEntity(`Should include @ in email address, email: ${user.email}`);
			if (!user.password && !user.must_set_password) throw new ErrorUnprocessableEntity('password must be set');
		} else {
			if ('email' in user && !user.email) throw new ErrorUnprocessableEntity('email must be set');
			if (user.email && !user.email.includes('@')) throw new ErrorUnprocessableEntity(`Should include @ in email address, email: ${user.email}`);
			if ('password' in user && !user.password) throw new ErrorUnprocessableEntity('password must be set');
		}

		if (user.email) {
			const existingUser = await this.loadByEmail(user.email);
			if (existingUser && existingUser.id !== user.id) throw new ErrorUnprocessableEntity(`there is already a user with this email: ${user.email}`);
			// See https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690 (found via https://stackoverflow.com/a/574698)
			if (user.email.length > 254) throw new ErrorUnprocessableEntity('Please enter an email address between 0 and 254 characters');
			validateEmail(user.email);
		}

		if (user.full_name && user.full_name.length > 256) throw new ErrorUnprocessableEntity('Full name must be at most 256 characters');

		return super.validate(user, options);
	}

View on GitHub (pinned to 981a03c5c9)