Conditional Validation
Require or constrain a field only when another field has a certain value — e.g. a REST endpoint's URL is required only when the type is rest. This uses JSON Schema's if/then/else (via allOf), evaluated by the same validator the runtime uses. It's an advanced capability; a first node rarely needs it.
NRG uses AJV for schema validation, which supports JSON Schema's if/then conditional keywords. This lets you create dependent validation rules — where a field's constraints change based on another field's value. Validation errors are shown inline in the auto-generated form.
TypeBox natively supports if, then, else, and allOf — pass them in the defineSchema options object (the same options where you may optionally set a stable $id). For a single condition, use if/then directly. For multiple conditions, use allOf with an array of if/then objects.
You can also use ajv-errors errorMessage to provide custom, user-friendly error messages instead of the default AJV output.
Single condition
import { defineSchema, SchemaType } from "@bonsae/nrg/schema";
const ConfigsSchema = defineSchema(
{
authType: SchemaType.String({
default: "none",
enum: ["none", "bearer"],
}),
token: SchemaType.String({ default: "", format: "password" }),
},
{
$id: "my-node:configs",
if: SchemaType.Object({ authType: SchemaType.Literal("bearer") }),
then: SchemaType.Object({ token: SchemaType.String({ minLength: 1 }) }),
errorMessage: {
properties: {
token: "Token is required for bearer auth",
},
},
},
);Multiple conditions
Use allOf to combine multiple independent if/then rules:
import { defineSchema, SchemaType } from "@bonsae/nrg/schema";
const ConfigsSchema = defineSchema(
{
method: SchemaType.String({
default: "GET",
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
}),
url: SchemaType.String({ default: "", minLength: 1 }),
body: SchemaType.String({ default: "" }),
authType: SchemaType.String({
default: "none",
enum: ["none", "basic", "bearer"],
}),
username: SchemaType.String({ default: "" }),
password: SchemaType.String({ default: "", format: "password" }),
token: SchemaType.String({ default: "", format: "password" }),
retries: SchemaType.Number({ default: 0, minimum: 0, maximum: 10 }),
retryDelay: SchemaType.Number({ default: 1000, minimum: 100 }),
},
{
$id: "http-request:configs",
allOf: [
// If method is POST/PUT/PATCH, body must not be empty
{
if: SchemaType.Object({
method: SchemaType.String({ enum: ["POST", "PUT", "PATCH"] }),
}),
then: SchemaType.Object({
body: SchemaType.String({ minLength: 1 }),
}),
errorMessage: {
properties: {
body: "Body is required for ${/method} requests",
},
},
},
// If authType is "basic", username and password are required
{
if: SchemaType.Object({
authType: SchemaType.Literal("basic"),
}),
then: SchemaType.Object({
username: SchemaType.String({ minLength: 1 }),
password: SchemaType.String({ minLength: 1 }),
}),
errorMessage: {
properties: {
username: "Username is required for basic auth",
password: "Password is required for basic auth",
},
},
},
// If authType is "bearer", token is required
{
if: SchemaType.Object({
authType: SchemaType.Literal("bearer"),
}),
then: SchemaType.Object({
token: SchemaType.String({ minLength: 1 }),
}),
errorMessage: {
properties: {
token: "Token is required for bearer auth",
},
},
},
// If retries > 0, retryDelay must be at least 100
{
if: SchemaType.Object({
retries: SchemaType.Number({ exclusiveMinimum: 0 }),
}),
then: SchemaType.Object({
retryDelay: SchemaType.Number({ minimum: 100 }),
}),
errorMessage: {
properties: {
retryDelay: "Retry delay is required when retries > 0",
},
},
},
],
},
);
export { ConfigsSchema };How it works
if— matches when the specified properties meet the condition (e.g., method is POST). UseSchemaType.Object()to define the condition schemathen— applies additional constraints when theifmatches. UseSchemaType.Object()to define the constraint schemaelse— applies constraints when theifdoes not match (optional)allOf— allows multiple independent conditions on the same schemaerrorMessage— custom error text shown in the form (from ajv-errors). You can interpolate values using JSON Pointer syntax like${/method}
Custom error messages
Without errorMessage, AJV shows generic messages like "must NOT have fewer than 1 characters". Use errorMessage.properties to replace these with context-specific messages that help users understand what to fix.
Good to know
- TypeScript types are not affected —
if/thenconditions are enforced at runtime by AJV.Infer<typeof Schema>reflects the full shape of the schema with all fields present. This is expected — conditional requirements are a runtime concern, and your code should handle all fields regardless. - All fields are always rendered — the auto-generated form displays every property in the schema. Conditional rules control when validation errors appear, not field visibility. For dynamic show/hide behavior, use a custom form component — it only takes a few lines of Vue.
- Need more advanced validation? — JSON Schema covers most validation patterns (
if/then,pattern,minLength,minimum,enum, etc.). For anything beyond that (async checks, complex cross-field logic), a custom form component gives you full control — you can combine schema-driven validation with your own computed validation errors.