TypeScript-first JSON rules engine with intuitive syntax and detailed error messages
npm install @inixiative/json-rules

A powerful, type-safe JSON-based rules engine for TypeScript/JavaScript applications. Define complex validation and business logic rules using simple JSON structures.
``bash`
npm install @inixiative/json-rulesor
yarn add @inixiative/json-rulesor
bun add @inixiative/json-rules
- 🎯 Type-safe: Full TypeScript support with strict type checking
- 🔧 Flexible: 22 standard operators, 8 array operators, and 8 date operators
- 🌳 Composable: Nest rules with logical operators (all/any) and conditional logic (if-then-else)
- 📊 Array validation: Rich array validation with element-wise conditions
- 📅 Date handling: Comprehensive date comparison with timezone support
- 🔍 Path-based access: Reference values from anywhere in your data structure
- 💬 Custom errors: Every rule supports custom error messages
`bash`
npm install json-rulesor
yarn add json-rulesor
bun add json-rules
`typescript
import { check, Operator } from 'json-rules';
// Simple rule
const rule = {
field: 'age',
operator: Operator.greaterThanEqual,
value: 18,
error: 'Must be 18 or older'
};
const result = check(rule, { age: 21 }); // returns true
const result2 = check(rule, { age: 16 }); // returns "Must be 18 or older"
`
#### Comparison
- equal - Exact equality checknotEqual
- - Not equal checklessThan
- - Less than comparisonlessThanEqual
- - Less than or equalgreaterThan
- - Greater than comparisongreaterThanEqual
- - Greater than or equal
#### Range
- between - Value within range (inclusive)notBetween
- - Value outside range
#### Membership
- in - Value in arraynotIn
- - Value not in arraycontains
- - Array/string contains valuenotContains
- - Array/string doesn't contain value
#### String
- startsWith - String starts with valueendsWith
- - String ends with value
#### Pattern
- match - Regex pattern matchnotMatch
- - Regex pattern doesn't match
#### Existence
- isEmpty - Check if value is empty (null, undefined, "", [], {})notEmpty
- - Check if value is not emptyexists
- - Field exists (not undefined)notExists
- - Field doesn't exist (undefined)
- all - All elements match conditionany
- - At least one element matchesnone
- - No elements matchatLeast
- - At least X elements matchatMost
- - At most X elements matchexactly
- - Exactly X elements matchempty
- - Array is emptynotEmpty
- - Array has elements
- before - Date is before comparison dateafter
- - Date is after comparison dateonOrBefore
- - Date is on or beforeonOrAfter
- - Date is on or afterbetween
- - Date is between two datesnotBetween
- - Date is outside rangedayIn
- - Day of week is in listdayNotIn
- - Day of week is not in list
#### Timezone Handling
Date comparisons are timezone-aware:
1. When condition value has no timezone (e.g., '2025-01-20'), it's interpreted in the field's timezone:`
typescript`
// Field: Jan 20 10:00 AM Sydney time
{ eventDate: '2025-01-20T10:00:00+11:00' }
// Condition: on or after Jan 20 (interpreted as Jan 20 in Sydney)
{ dateOperator: 'onOrAfter', value: '2025-01-20' } // ✓ passes
2. When condition value has timezone (e.g., '2025-01-20T00:00:00Z'), it's used as-is:`
typescript`
// Condition: after midnight UTC specifically
{ dateOperator: 'after', value: '2025-01-20T00:00:00Z' }
3. Fields without timezone are treated as local time (UTC offset 0)
`typescript`
{
field: 'status',
operator: Operator.equal,
value: 'active'
}
`typescript
// All conditions must pass (AND)
{
all: [
{ field: 'age', operator: Operator.greaterThanEqual, value: 18 },
{ field: 'hasLicense', operator: Operator.equal, value: true }
]
}
// At least one must pass (OR)
{
any: [
{ field: 'role', operator: Operator.equal, value: 'admin' },
{ field: 'isOwner', operator: Operator.equal, value: true }
]
}
`
`typescript`
{
if: { field: 'type', operator: Operator.equal, value: 'premium' },
then: { field: 'discount', operator: Operator.greaterThan, value: 0 },
else: { field: 'discount', operator: Operator.equal, value: 0 }
}
`typescript`
{
field: 'orders',
arrayOperator: ArrayOperator.all,
condition: {
field: 'total',
operator: Operator.lessThan,
value: 1000
}
}
`typescript`
{
field: 'expiryDate',
dateOperator: DateOperator.after,
value: '2024-12-31'
}
Compare fields against each other using paths:
`typescript`
{
field: 'confirmPassword',
operator: Operator.equal,
path: 'password' // Compare against another field
}
Use $. prefix to reference the current array element:
`typescript`
{
field: 'items',
arrayOperator: ArrayOperator.all,
condition: {
field: 'price',
operator: Operator.lessThan,
path: '$.maxPrice' // Reference field on current array element
}
}
Every rule supports custom error messages:
`typescript`
{
field: 'email',
operator: Operator.match,
value: /^[^@]+@[^@]+\.[^@]+$/,
error: 'Please enter a valid email address'
}
`typescript
const rule = {
all: [
// User must be active
{ field: 'status', operator: Operator.equal, value: 'active' },
// Age requirement
{ field: 'age', operator: Operator.between, value: [18, 65] },
// Must have at least one verified email
{
field: 'emails',
arrayOperator: ArrayOperator.any,
condition: { field: 'verified', operator: Operator.equal, value: true }
},
// Conditional premium features
{
if: { field: 'subscription', operator: Operator.equal, value: 'premium' },
then: {
field: 'features',
arrayOperator: ArrayOperator.all,
condition: { field: 'enabled', operator: Operator.equal, value: true }
}
}
]
};
const userData = {
status: 'active',
age: 25,
emails: [
{ address: 'user@example.com', verified: true },
{ address: 'alt@example.com', verified: false }
],
subscription: 'premium',
features: [
{ name: 'advanced', enabled: true },
{ name: 'analytics', enabled: true }
]
};
const result = check(rule, userData); // returns true
`
The main validation function.
- condition: The rule to evaluate
- data: The data to validate against
- context: Optional context (defaults to data)
- Returns: true if validation passes, error string if it fails
`typescript
type Condition = Rule | ArrayRule | DateRule | All | Any | IfThenElse | boolean;
type Rule = {
field: string;
operator: Operator;
value?: any;
path?: string;
error?: string;
};
type ArrayRule = {
field: string;
arrayOperator: ArrayOperator;
condition?: Condition;
count?: number;
error?: string;
};
type DateRule = {
field: string;
dateOperator: DateOperator;
value?: any;
path?: string;
error?: string;
};
`
The engine throws errors for:
- Invalid array fields when using array operators
- Missing required parameters (e.g., count for atLeast)
- Invalid dates in date comparisons
- Primitive arrays with array operators (use contains or in` instead)
MIT