Skip to content

Compose.partial and Compose.required Compile-time + Runtime

Validation modes: Validation modes reference

partial and required are inverse operations for adjusting the required-ness of all properties. Both return new schema objects - inputs are never mutated.


Compose.partial

Declaration. Creates a new schema by removing the required array entirely. All properties become optional. TypeScript infers a type with all properties optional - the equivalent of Partial<T>. The $id is replaced with newId.

Use this when you need a PATCH-body schema where any combination of fields may be provided. Also useful for form state where all fields start empty and become required only on submit.

Don't use this when you only want specific fields to be optional (use pick to select and then omit required from only those). Don't use it when you want to remove specific fields entirely (use omit).

Examples

Example 1: PATCH customer endpoint

/**
 * Compose.partial / required — Example 1: PATCH and strict-create schemas
 * Demonstrates: partial removes required, required makes all fields required
 *
 * Derived schemas register onto the canonical bookstore via
 * `jt.set()`. Inputs use the Bastian Balthazar Bux fixture
 * shared with every other doc example.
 */

import { Compose } from '../../../src/index.js';
import {
  aboxFixtures, createBookstoreDocRegistry,
  CustomerSchema
} from '../bookstore/index.js';

// createBookstoreDocRegistry seeds a permissive copy of the bookstore — docs examples extend
// it with ad-hoc demo schemas; strict-graph checking is intentionally off here.
const jt = createBookstoreDocRegistry();

const PatchCustomerSchema = Compose.partial(
  CustomerSchema,
  'https://bookstore.example/PatchCustomer'
);

const StrictCustomerSchema = Compose.required(
  CustomerSchema,
  'https://bookstore.example/StrictCustomer'
);

const jt2 = jt.set(PatchCustomerSchema).set(StrictCustomerSchema);

// PatchCustomer accepts a partial body — name alone is enough.
const patchErrors = jt2.validate(PatchCustomerSchema.$id, { 'name': aboxFixtures.customer.name });

console.assert(patchErrors.length === 0);
console.log('PatchCustomer accepts name-only:', patchErrors.length === 0, '| required stripped by partial');

// StrictCustomer requires every field, including addresses.
const strictErrors = jt2.validate(StrictCustomerSchema.$id, {
  'customerId': aboxFixtures.customer.customerId,
  'email': aboxFixtures.customer.email,
  'name': aboxFixtures.customer.name
  // addresses missing — required by StrictCustomer
});

console.assert(strictErrors.length > 0);
console.log('StrictCustomer rejects missing addresses:', strictErrors.length, 'error(s)');

// Full Bastian fixture passes StrictCustomer.
const strictOk = jt2.validate(
  StrictCustomerSchema.$id,
  aboxFixtures.customer
);

console.assert(strictOk.length === 0);
console.log('StrictCustomer accepts full fixture:', strictOk.length === 0);
Output
Press Execute to run this example against the real library.

Example 2: Form initial state for a Review

/**
 * Compose.partial — Example 2: Draft Review form initial state
 *
 * `Compose.partial` strips the `required` array from the canonical
 * ReviewSchema so a draft form can validate at any intermediate point
 * — even with zero fields filled in.
 */

import { Compose } from '../../../src/index.js';
import {
  createBookstoreDocRegistry,
  ReviewSchema
} from '../bookstore/index.js';

// createBookstoreDocRegistry seeds a permissive copy of the bookstore — docs examples extend
// it with ad-hoc demo schemas; strict-graph checking is intentionally off here.
const jt = createBookstoreDocRegistry();

const DraftReviewSchema = Compose.partial(
  ReviewSchema,
  'https://bookstore.example/DraftReview'
);

const jt2 = jt.set(DraftReviewSchema);

// An empty draft passes — every field is optional.
const empty = jt2.validate(DraftReviewSchema.$id, {});

console.assert(empty.ok);
console.log('DraftReview accepts empty body:', empty.ok, '| all fields optional after partial');

// A partial draft also passes.
const draftPartial = jt2.validate(DraftReviewSchema.$id, { 'rating': 4 });

console.assert(draftPartial.ok);
console.log('DraftReview accepts rating-only draft:', draftPartial.ok);
Output
Press Execute to run this example against the real library.

Comparison

ts
Compose.partial(CustomerSchema, 'https://bookstore.example/PatchCustomer')
ts
CustomerSchema.partial()
ts
import * as v from 'valibot';
v.partial(CustomerSchema)
ts
import * as t from 'io-ts';
// io-ts has t.partial which makes every property optional:
const PatchCustomer = t.partial(CustomerCodec.props);
ts
import { Type } from '@sinclair/typebox';
Type.Partial(CustomerSchema)
ts
// Manual  - copy schema, remove required:
const { required: _, ...PatchCustomer } = CustomerSchema;
PatchCustomer.$id = 'https://bookstore.example/PatchCustomer';
py
# Create a PATCH model manually with Optional fields:
class PatchCustomer(BaseModel):
    name: str | None = None
    email: str | None = None
    # Or use model_fields_set to track which fields were provided.
ts
// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.
  • required - inverse: make all fields required
  • pick - subset of fields, combined with partial for partial sub-schemas
  • extend - add fields before making partial

Compose.required

Declaration. Creates a new schema where every declared property in properties is listed in required. The resulting required array is Object.keys(schema.properties). TypeScript infers a type with all properties required - the equivalent of Required<T>. The $id is replaced with newId.

Use this when you need a strict create-body schema that demands all fields, even those that have defaults. Useful for internal service calls where missing defaults should be caught, or for admin APIs that require full objects.

Don't use this when you only want specific fields to be required (build a combined schema with intersection instead).

Examples

Example 1: Strict book creation requiring all fields

BookSchema declares inStock with a default (so it is effectively optional in the base schema). A strict create schema requires every declared property explicitly.

/**
 * Compose.required — Example 1: Strict book creation
 *
 * `BookSchema.inStock` carries a default of `true`, so it is
 * effectively optional in the base. `Compose.required` produces a
 * schema where every declared property must be present at validation
 * — useful for admin endpoints that want to catch missing defaults.
 */

import { Compose } from '../../../src/index.js';
import {
  BookSchema,
  createBookstoreDocRegistry
} from '../bookstore/index.js';

// createBookstoreDocRegistry seeds a permissive copy of the bookstore — docs examples extend
// it with ad-hoc demo schemas; strict-graph checking is intentionally off here.
const jt = createBookstoreDocRegistry();

const CreateBookSchema = Compose.required(
  BookSchema,
  'https://bookstore.example/CreateBook'
);

const jt2 = jt.set(CreateBookSchema);

// Missing inStock — fails because Compose.required promotes it.
const missingInStock = jt2.validate(CreateBookSchema.$id, {
  'authors': ['Michael Ende'],
  'isbn': '9783522128001',
  'price': {
    'amount': 14.99,
    'currency': 'EUR'
  },
  'printStatus': 'outOfPrint',
  'title': 'Die unendliche Geschichte'
});

console.assert(!missingInStock.ok);
console.log('CreateBook rejects missing inStock:', !missingInStock.ok, missingInStock.length, 'error(s) | Compose.required promotes all declared properties');
Output
Press Execute to run this example against the real library.

Comparison

ts
Compose.required(BookSchema, 'https://bookstore.example/CreateBook')
ts
BookSchema.required()
ts
import * as v from 'valibot';
v.required(BookSchema)
ts
import * as t from 'io-ts';
// io-ts treats every t.type field as required already. To force a partial
// codec back to fully required, rebuild with t.type:
const CreateBook = t.type(BookCodec.props);
ts
import { Type } from '@sinclair/typebox';
Type.Required(BookSchema)
ts
// Manual  - set required = all property keys:
const CreateBook = {
  ...BookSchema,
  $id: 'https://bookstore.example/CreateBook',
  required: Object.keys(BookSchema.properties),
};
py
# Pydantic fields without defaults are already required.
# To make defaulted fields required, remove the defaults or use a validator.
ts
// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.
  • partial - inverse: make all fields optional
  • intersection - when some fields must be required from a second schema
  • extend - add fields before making required

See also

Released under the MIT License.