wellformed
Builder API

Objects

ObjectBuilder — shape syntax, composition, pick, omit, partial, required

w.object() creates schemas for objects with named, typed properties.

Shape Syntax

Pass a record of property name to builder:

const user = w.object({
  name: w.string().trim().minLen(1),
  age: w.integer().min(0),
  email: w.string().email(),
});

All properties are required by default.

Optional Properties

Mark individual properties as optional with .optional():

const user = w.object({
  name: w.string(),
  nickname: w.string().optional(),  // Can be undefined
  age: w.integer(),
});

Or use the optional() helper:

import { w, optional } from "wellformed-ts";

const user = w.object({
  name: w.string(),
  nickname: optional(w.string()),
});

Adding Properties

Use .prop() to add properties imperatively:

const schema = w.object()
  .prop("name", w.string())
  .prop("age", w.integer())
  .prop("nickname", w.string(), false); // false = optional

Additional Properties

By default, unknown properties are rejected. Pick the behavior explicitly so API boundaries are clear:

w.object({ name: w.string() }).strict()
// Reject unknown keys. This is also the default.

w.object({ name: w.string() }).strip()
// Omit unknown keys from the validated output.

w.object({ name: w.string() }).passthrough()
// Keep unknown keys without validating them.

w.object({ name: w.string() }).catchall(w.string())
// Keep unknown keys, but validate every unknown value as a string.

additionalProperties(true) is available for IR compatibility and maps to passthrough behavior. New code should prefer .strict(), .strip(), .passthrough(), or .catchall(...) because the intent is clearer.

Composition

extend

Add new properties to an existing schema:

const base = w.object({ id: w.string() });

const user = base.extend({
  name: w.string(),
  age: w.integer(),
});
// { id: string; name: string; age: number }

merge

Merge two object schemas:

const a = w.object({ name: w.string() });
const b = w.object({ age: w.integer() });
const merged = a.merge(b);
// { name: string; age: number }

Rules and additional properties settings are combined from both schemas.

pick

Select specific properties:

const user = w.object({
  id: w.string(),
  name: w.string(),
  email: w.string(),
});

const nameOnly = user.pick("name");
// { name: string }

const nameAndEmail = user.pick("name", "email");
// { name: string; email: string }

omit

Remove specific properties:

const safe = user.omit("password");
// All fields except password

partial

Make all properties optional:

const partialUser = user.partial();
// { id?: string; name?: string; email?: string }

required

Make all properties required:

const fullUser = partialUser.required();
// { id: string; name: string; email: string }

Type Inference

All composition operations preserve type inference:

import type { Infer } from "wellformed-ts";

const schema = w.object({
  name: w.string(),
  age: w.integer().optional(),
}).extend({
  email: w.string().email(),
});

type T = Infer<typeof schema>;
// { name: string; age?: number; email: string }

Cross-Field Rules

Object rules run after all properties have been transformed and validated. This means a rule reads normalized values:

const schema = w.object({
  ssn: w.string().digitsOnly().optional(),
  country: w.string().upper(),
})
  .when("country").equals("US").require("ssn");

If the input contains { country: "us" }, the rule sees country as "US".

On this page