Rules Overview
Every rule helper returns a Rule<F, C> object. Rules are plain values — they can be composed, stored, and combined with anyOf().
| Rule | Purpose |
|---|---|
requires() | Field stays disabled until dependencies are satisfied and available |
enabledWhen() | Field enabled only when a predicate returns true |
fairWhen() | Field’s current value is appropriate only when a predicate returns true |
disables() | Active source disables target fields |
oneOf() | Only one branch of fields is active at a time |
anyOf() | OR logic — pass if any inner rule passes |
check() | Bridge validators into rules with preserved field metadata |
Try each one interactively on the Quick Start page.
Custom Reasons
Section titled “Custom Reasons”All rule helpers that accept options.reason support either a static string or a function.
enabledWhen('companyName', (_values, conditions) => conditions.plan === 'business', { reason: (_values, conditions) => `Plan "${conditions.plan}" cannot edit company details`,})Dynamic reasons are useful when the UI should explain a specific plan tier, feature flag, or external gate.
Typing Conditions
Section titled “Typing Conditions”Rule factories have their own generic parameters, so TypeScript can’t always infer your conditions type from the umpire() call. When a predicate receives conditions, it may be typed as Record<string, unknown> instead of your specific type.
Two ways to fix this:
field<V>() for per-field type capture
Section titled “field<V>() for per-field type capture”When a rule like fairWhen needs a typed value parameter, use a named field<V>() builder. It captures the value type for that field and flows it through to the predicate — no annotation needed.
const motherboard = field<string>('motherboard')
fairWhen(motherboard, (mb, values) => { // ^^ string, not unknown return socketFor(mb) === socketFor(values.cpu ?? '')})This is narrower than createRules() — it types one field at a time rather than the whole schema. Use field<V>() when you want typed predicates inline; use createRules() when many rules share the same field and condition types.
Annotate the predicate
Section titled “Annotate the predicate”Type conditions directly in the callback. This works well for a handful of rules.
type Conditions = { plan: 'personal' | 'business' }
enabledWhen('companyName', (_v, c: Conditions) => c.plan === 'business', { reason: 'business plan required',})createRules() for many rules
Section titled “createRules() for many rules”When you have many rules sharing the same field and condition types, createRules() returns typed versions of all rule factories. Type once, use everywhere.
import { createRules, umpire } from '@umpire/core'
type Conditions = { plan: 'personal' | 'business' isAdmin: boolean}
const fields = { companyName: {}, companySize: {}, discountOverride: {},}
const { enabledWhen, requires } = createRules<typeof fields, Conditions>()
// c.plan and c.isAdmin are fully typed in every predicateconst ump = umpire({ fields, rules: [ enabledWhen('companyName', (_v, c) => c.plan === 'business', { reason: 'business plan required', }), enabledWhen('discountOverride', (_v, c) => c.isAdmin, { reason: 'admin only', }), requires('companySize', 'companyName'), ],})createRules() is purely a type-level convenience — zero runtime overhead. The returned functions are the same rule factories with narrowed generics.