Skip to content

Form-Safe Parsing ​

Problem ​

Form submissions often arrive as strings, optional fields, and checkboxes. You need typed output without repeating conversion code for every field.

Solution ​

Use coercion and wrappers to normalize the form payload in one step, then read messages directly by path.

ts
import { SpellValidationError, s } from '@vielzeug/spell';

const SignupForm = s.object({
  age: s.coerce.number().int().min(18),
  email: s.string().email(),
  marketing: s.coerce.boolean().default(false),
  referralCode: s.string().trim().optional().nullable().required(),
});

const result = SignupForm.safeParse({
  age: '29',
  email: 'ada@example.com',
  marketing: 'true',
  referralCode: null,
});

if (result.success) {
  console.log(result.data.referralCode);
} else if (result.error instanceof SpellValidationError) {
  console.log(result.error.messagesAt('email'));
}

Pitfalls ​

  • .required() removes undefined but keeps null. That matters after .optional().nullable() chains.
  • s.coerce.boolean() is for loosely typed input at the boundary. Keep your app state typed after parsing.
  • Call safeParseAsync() if any nested field uses checkAsync().