ProvenPin Install

Yup schemas for signup forms with conditional rules: working versions (tested 2026-10-15)

Build a Yup v1 signup schema with refs for password confirmation, when() conditional fields, transforms and InferType, collecting every error per field.

passed4 passed · 0 failed2026-10-15 17:11 UTC
Status
passed
Last passed
2026-10-15 17:11 UTC
Tests
4 / 4
Language
TypeScript
Runtime
node v22.23.3
pinned versions3
yup@1.7.1@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38775053802

The code

Exactly the files that passed. Your agent can pull them with get_recipe("yup-form-schemas") (install), or see them on GitHub.

package.json
{
  "name": "yup-form-schemas",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "yup": "1.7.1"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/signup.ts
import * as yup from "yup";

export const signupSchema = yup.object({
  email: yup.string().trim().lowercase().email("invalid email").required("email is required"),
  password: yup.string().min(8, "password must be at least 8 characters").required(),
  confirmPassword: yup
    .string()
    .oneOf([yup.ref("password")], "passwords must match")
    .required(),
  accountType: yup.string().oneOf(["personal", "business"] as const).default("personal"),
  company: yup.string().when("accountType", {
    is: "business",
    then: (s) => s.required("company is required for business accounts"),
    otherwise: (s) => s.strip(),
  }),
  age: yup.number().transform((v, orig) => (orig === "" ? undefined : v)).integer().min(16).optional(),
  tags: yup.array(yup.string().required()).max(3).default([]),
});

export type Signup = yup.InferType<typeof signupSchema>;

export type FieldErrors = Record<string, string[]>;

export async function validateSignup(input: unknown): Promise<{ ok: true; value: Signup } | { ok: false; errors: FieldErrors }> {
  try {
    const value = await signupSchema.validate(input, { abortEarly: false, stripUnknown: true });
    return { ok: true, value };
  } catch (err) {
    if (!(err instanceof yup.ValidationError)) throw err;
    const errors: FieldErrors = {};
    for (const e of err.inner) {
      const key = e.path ?? "_";
      (errors[key] ??= []).push(e.message);
    }
    return { ok: false, errors };
  }
}
test/signup.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { signupSchema, validateSignup } from "../src/signup.ts";

const base = { email: " [email protected] ", password: "s3cretpass", confirmPassword: "s3cretpass" };

test("personal signup applies transforms and defaults and strips company", async () => {
  const r = await validateSignup({ ...base, company: "ignored", age: "" , extra: 1 });
  assert.equal(r.ok, true);
  if (!r.ok) return;
  assert.deepEqual(r.value, { email: "[email protected]", password: "s3cretpass", confirmPassword: "s3cretpass", accountType: "personal", tags: [] });
});

test("business accounts require a company", async () => {
  const r = await validateSignup({ ...base, accountType: "business" });
  assert.equal(r.ok, false);
  if (r.ok) return;
  assert.deepEqual(r.errors.company, ["company is required for business accounts"]);
});

test("collects all errors per field", async () => {
  const r = await validateSignup({ email: "x", password: "short", confirmPassword: "other", age: 12, tags: ["a", "b", "c", "d"] });
  assert.equal(r.ok, false);
  if (r.ok) return;
  assert.deepEqual(Object.keys(r.errors).sort(), ["age", "confirmPassword", "email", "password", "tags"]);
  assert.deepEqual(r.errors.confirmPassword, ["passwords must match"]);
});

test("isValidSync and cast work without async", () => {
  assert.equal(signupSchema.isValidSync({ ...base, age: 30 }), true);
  const cast = signupSchema.cast({ ...base, age: "42" });
  assert.equal(cast.age, 42);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.