Rules operators
Regle provides tools to combine and operate on different rules. It includes the following built-in operators, available in @regle/rules:
andorxornotapplyIfassignIfpipe
These operators work with any rules you provide, but combining rules with incompatible input types may lead to errors.
TIP
All operators are compatible with wrappers
and
The and operator combines multiple rules and validates successfully only if all provided rules are valid.
import { useRegle } from '@regle/core';
import { and, startsWith, endsWith, withMessage } from '@regle/rules';
const { r$ } = useRegle(
{ regex: '' },
{
regex: {
myError: withMessage(
and(startsWith('^'), endsWith('$')),
({ $params: [start, end] }) =>
`Regex should start with "${start}" and end with "${end}"`
),
},
}
);Result:
or
The or operator validates successfully if at least one of the provided rules is valid.
import { useRegle } from '@regle/core';
import { or, startsWith, endsWith, withMessage } from '@regle/rules';
const { r$ } = useRegle(
{ regex: '' },
{
regex: {
myError: withMessage(
or(startsWith('^'), endsWith('$')),
({ $params: [start, end] }) =>
`Field should start with "${start}" or end with "${end}"`
),
},
}
);Result:
xor
The xor operator (exclusive or) validates successfully if exactly one of the provided rules is valid. It fails when none or more than one rule passes.
import { useRegle } from '@regle/core';
import { xor, contains, withMessage } from '@regle/rules';
const { r$ } = useRegle(
{ code: '' },
{
code: {
myError: withMessage(
xor(contains('A'), contains('B')),
({ $params: [charA, charB] }) =>
`Field should contain either "${charA}" or "${charB}", but not both`
),
},
}
);Result:
not
The not operator passes when the provided rule fails and fails when the rule passes. It can be combined with other rules.
import { useRegle } from '@regle/core';
import { not, required, sameAs, withMessage } from '@regle/rules';
import { ref } from 'vue';
const form = ref({ oldPassword: '', newPassword: '' });
const { r$ } = useRegle(form, {
oldPassword: {
required,
},
newPassword: {
notEqual: withMessage(
not(sameAs(() => form.value.oldPassword)),
'Your confirm new password must not be the same as your old password'
),
},
});Result:
applyIf
The applyIf operator is similar to requiredIf, but it can be used with any rule. It simplifies conditional rule declarations.
import { minLength, applyIf } from '@regle/rules';
const condition = ref(false);
const { r$ } = useRegle({name: ''}, {
name: {
minLength: applyIf(condition, minLength(6))
},
});assignIf
The assignIf is a shorthand for conditional destructuring assignment. It allows to apply multiple rules to a field conditionally.
import { required, email, minLength, assignIf } from '@regle/rules';
const condition = ref(false);
const { r$ } = useRegle(ref({ name: '', email: '' }), {
name: assignIf(condition, { required, minLength: minLength(4) }),
email: { email },
});pipe
The pipe operator chains multiple rules together sequentially. Each rule only runs if all previous rules have passed. This is useful when you want to validate in a specific order and avoid running expensive validations (like async checks) until simpler ones pass.
import { useRegle } from '@regle/core';
import { pipe, required, minLength, email } from '@regle/rules';
const { r$ } = useRegle(
{ email: '' },
{
email: pipe(required, minLength(5), email),
}
);
// minLength only runs if required passes
// email only runs if both required and minLength passResult:
When to use pipe vs and
- Use
andwhen all rules should run simultaneously and you want all errors at once - Use
pipewhen rules should run sequentially and later rules depend on earlier ones passing (e.g., don't check email format until the field has enough characters)
The pipe operator also works with async rules. Subsequent rules will wait for async validators to complete before running:
import { pipe, required, withAsync } from '@regle/rules';
const checkEmailAvailable = withAsync(async (value) => {
const response = await fetch(`/api/check-email?email=${value}`);
return response.ok;
});
const { r$ } = useRegle(
{ email: '' },
{
email: pipe(required, email, checkEmailAvailable),
}
);
// checkEmailAvailable only runs after required and email passDebouncing async validators
When using pipe with async validators, you can configure a debounce delay to prevent too many API calls while the user is typing. Use the array syntax with an options object as the second argument:
import { pipe, required, email, withAsync } from '@regle/rules';
const checkEmailAvailable = withAsync(async (value) => {
const response = await fetch(`/api/check-email?email=${value}`);
return response.ok;
});
const { r$ } = useRegle(
{ email: '' },
{
email: pipe(
[required, email, checkEmailAvailable],
{ debounce: 300 } // Wait 300ms after last input before running async validators
),
}
);The debounce option only affects async validators in the pipe. Synchronous validators (required, email in the example above) run immediately, while the async validator (checkEmailAvailable) will be debounced.
Default debounce
When using pipe with async validators, the default debounce delay is 200ms. You can override this by passing a custom debounce value in the options.

