Skip to content

password-confirmation-schema

Edit

Validates a password twice with the same schema and reports parsed-value mismatches on confirmation.

password-confirmation-schema.ts
import * as z from "zod";
const PASSWORD_MIN_LENGTH = 8;
const PASSWORD_MAX_LENGTH = 128;
const codePointLength = (value: string): number => Array.from(value).length;
const isWellFormedUnicode = (value: string): boolean => {
for (let index = 0; index < value.length; index += 1) {
const codeUnit = value.charCodeAt(index);
if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
const nextCodeUnit = value.charCodeAt(index + 1);
if (!(nextCodeUnit >= 0xdc00 && nextCodeUnit <= 0xdfff)) return false;
index += 1;
} else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
return false;
}
}
return true;
};
/**
* Applies one string-output schema to a password and its confirmation, then
* reports mismatches on the confirmation field.
*/
export const createPasswordConfirmationSchema = <
const Schema extends z.ZodType<string>,
>(
schema: Schema,
) =>
z
.object({
password: schema,
confirmPassword: schema,
})
.check(
z.refine(
(value) => {
const fields = value as {
password: z.output<Schema>;
confirmPassword: z.output<Schema>;
};
return fields.password === fields.confirmPassword;
},
{
error: "Passwords do not match",
path: ["confirmPassword"],
},
),
)
.brand<"PasswordConfirmation">()
.meta({
title: "Password confirmation",
description:
"A password and confirmation validated by the same schema and required to match.",
});
const confirmationPasswordValueSchema = z
.string({ error: "Password must be a string" })
.check(
z.refine(isWellFormedUnicode, {
error: "Password must contain only well-formed Unicode",
}),
z.normalize("NFC"),
z.refine(
(value) => codePointLength(value) >= PASSWORD_MIN_LENGTH,
{
error: `Password must contain at least ${PASSWORD_MIN_LENGTH} characters`,
},
),
z.refine(
(value) => codePointLength(value) <= PASSWORD_MAX_LENGTH,
{
error: `Password must contain at most ${PASSWORD_MAX_LENGTH} characters`,
},
),
)
.brand<"ValidatedPassword">()
.brand<"Password">();
export const passwordConfirmationSchema =
createPasswordConfirmationSchema(confirmationPasswordValueSchema);
export type PasswordConfirmation = z.infer<
typeof passwordConfirmationSchema
>;
Terminal
wget -O src/schemas/password-confirmation-schema.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/zod-schemas/password-confirmation-schema.ts
password-confirmation-schema.example.ts
import {
passwordConfirmationSchema,
type PasswordConfirmation,
} from "./password-confirmation-schema";
const values = passwordConfirmationSchema.parse({
password: "correct horse",
confirmPassword: "correct horse",
});
const submitPassword = (input: PasswordConfirmation) => {
console.log(`Confirmed ${Array.from(input.password).length} characters`);
};
submitPassword(values);
  • createPasswordConfirmationSchema(schema) — Creates a confirmation object from any Schema extends z.ZodType<string>.
  • schema — The same schema is applied independently to password and confirmPassword.
  • password — The primary input value.
  • confirmPassword — The repeated input value.

The factory preserves the supplied schema’s input and output types and returns a parsed object branded with "PasswordConfirmation". It compares the two parsed outputs, so normalization or other transforms supplied by schema happen before equality is checked.

The exported passwordConfirmationSchema preset applies the same NFC normalization and 8–128 Unicode-code-point policy as passwordSchema. A mismatch produces "Passwords do not match" at the confirmPassword path. Both parsed fields carry the default Password type, including its "ValidatedPassword" and "Password" brands. The preset accepts valid surrogate pairs as single code points and rejects an unpaired UTF-16 surrogate in either field. A custom schema supplied to the factory controls its own Unicode policy.