Skip to main content
Version: Next

JSON Schema

Refer to the TypeScript reference page for information about the types and interfaces referenced below.

getQueryJsonSchema​

function getQueryJsonSchema(
fields: FlexibleOptionListProp<FullField> | BaseOptionMap<FullField>,
options?: GetQueryJsonSchemaOptions
): JSONSchema;

getQueryJsonSchema generates a JSON Schema that describes valid queries for a fields config. You can use it for:

  • LLM structured output (OpenAI response_format, the AI SDK's generateObject, etc.)
  • LLM tool input schemas (Anthropic input_schema, MCP inputSchema)
  • Validating query objects with a generic JSON Schema validator like Ajv

It's available from @react-querybuilder/core and react-querybuilder, or from the /getQueryJsonSchema subpath of either package.

import type { Field } from '@react-querybuilder/core';
import { getQueryJsonSchema } from '@react-querybuilder/core';
// or: import { getQueryJsonSchema } from '@react-querybuilder/core/getQueryJsonSchema';

const fields: Field[] = [
{ name: 'firstName', label: 'First name', operators: ['=', 'beginsWith'] },
{ name: 'age', label: 'Age', inputType: 'number' },
{
name: 'color',
label: 'Color',
valueEditorType: 'select',
values: [
{ name: 'red', label: 'Red' },
{ name: 'blue', label: 'Blue' },
],
},
];

const schema = getQueryJsonSchema(fields, { strict: true });

Schema shape​

By default, the schema describes a RuleGroupType (set queryType: 'ic' for RuleGroupTypeIC). Groups and rules are defined in $defs and reference each other recursively:

{
"$defs": {
"group": {
"type": "object",
"properties": {
"combinator": { "type": "string", "enum": ["and", "or"] },
"rules": {
"type": "array",
"items": { "anyOf": [{ "$ref": "#/$defs/rule" }, { "$ref": "#/$defs/group" }] }
},
"not": { "type": "boolean" }
},
"required": ["combinator", "rules"]
},
"rule": { "anyOf": ["...one branch per field and operator class..."] }
},
"$ref": "#/$defs/group"
}

In the default 'full' mode, each field's rule schema is split into branches by operator class (unary, binary, between, in). Each branch restricts field to the field's name and operator to the matching operators (field.operators, falling back to the operators option). The value type comes from the field:

Field configvalue schema
values (any editor except text/textarea)enum of value names (option groups flattened)
valueEditorType: 'multiselect'Array of the above
valueEditorType: 'checkbox' or 'switch'boolean
inputType: 'number'number
inputType: 'date', 'datetime-local', etc.string, with the expected format in description
between/notBetween operatorsArray of exactly 2 items (minItems/maxItems)
in/notIn operatorsArray of items
null/notNull (and other unary operators)Ignored (optional)
valueSource: 'field' (when allowed for a field)enum of comparable field names (see comparator)
Anything elsestring

Field labels are added as descriptions.

Options​

OptionTypeDefaultDescription
queryType'standard' | 'ic''standard'RuleGroupType or RuleGroupTypeIC
mode'full' | 'loose''full''loose' only restricts field and operator names. Use it when the full schema gets too big (roughly 50+ fields).
strictbooleanfalseProvider compatibility: additionalProperties: false everywhere, all properties required (optional ones become nullable), and the root is an object.
maxDepth'ref' | number'ref''ref' uses recursive $refs. A number inlines that many levels of nested groups with no $ref/$defs, for consumers that don't support recursion.
includeIdsbooleanfalseInclude id and path properties. Off by default so LLMs don't invent ids.
allowFieldValueSourcebooleanDerived from fieldstrue allows valueSource: 'field' for every field, false for none. By default, each field's valueSources decide.
includeNotbooleantrueInclude not on groups
combinatorsOptionListdefaultCombinatorsAllowed combinators
operatorsOptionListdefaultOperatorsOperators for fields without their own operators
descriptionsbooleantrueAdd field labels as descriptions
listsAsArraysbooleantrueList values (between, in, multiselect) as arrays. false expects comma-separated strings. Should match formatQuery's listsAsArrays.
includeSchemaDeclarationbooleanfalseAdd "$schema" (draft-07). Off by default because some providers reject it.

Compatibility​

The schema only uses keywords that mean the same thing in JSON Schema draft-07 and 2020-12: type, properties, required, enum, anyOf, items (single schema), minItems/maxItems, additionalProperties, description, $ref, and $defs. It never uses oneOf, allOf, not, const, format, pattern, or tuple keywords, since provider support for those is inconsistent.

ConsumerNotes
OpenAI structured outputsRequires strict: true. Recursion via $ref is supported.
Anthropic tools$ref/$defs supported. Root must be an object.
GeminiUse a numeric maxDepth if $ref is rejected.
AI SDK jsonSchema()Passed through to the provider
MCP inputSchemaRoot must be an object, so use strict or maxDepth.
Ajv (draft-07 and 2020)All modes
tip

When in doubt, use strict: true. It works with every consumer above.

Usage with LLMs​

These examples are illustrative. Check each provider's current docs for details.

AI SDK

import { generateObject, jsonSchema } from 'ai';
import { getQueryJsonSchema } from '@react-querybuilder/core';

const { object: query } = await generateObject({
model,
schema: jsonSchema<RuleGroupType>(getQueryJsonSchema(fields, { strict: true })),
prompt: 'Musicians over 30 named Steve',
});

OpenAI (structured outputs)

const response = await openai.chat.completions.create({
model,
messages,
response_format: {
type: 'json_schema',
json_schema: {
name: 'query',
strict: true,
schema: getQueryJsonSchema(fields, { strict: true }),
},
},
});

Anthropic (tool input)

const message = await anthropic.messages.create({
model,
max_tokens: 1024,
messages,
tools: [
{
name: 'set_filter',
description: 'Set the query builder filter',
input_schema: getQueryJsonSchema(fields, { strict: true }),
},
],
});

In strict mode, optional properties like not and valueSource are nullable instead of omitted, so the output may contain nulls. Formatting functions treat those the same as missing properties.

Validate the result with diagnostics​

The schema is necessary but not sufficient. Some constraints can't be expressed with the compatible keyword subset:

  • Independent combinator alternation (rule/group, combinator, rule/group, ...). The IC schema only allows rules, groups, and combinator strings in any order.
  • With listsAsArrays: false, the number of comma-separated values for between/in.
  • In 'loose' mode, which operators and values are valid for each field.

Always check LLM output (and any other untrusted query) with the diagnostics export format before using it:

import { formatQuery } from '@react-querybuilder/core';

const { diagnostics } = formatQuery(query, { format: 'diagnostics', fields });
const problems = diagnostics.filter(d => d.code !== 'UNREFERENCED_FIELD');
if (problems.length > 0) {
// Reject, or send `problems` back to the LLM to fix
}

When fields is provided, diagnostics reports unknown fields (UNDEFINED_FIELD), operators not in field.operators (INVALID_OPERATOR), wrong between/in value counts (INVALID_VALUE_ARITY), values missing from field.values (VALUE_NOT_IN_OPTIONS), and values that don't match the field's type (VALUE_TYPE_MISMATCH). It reports broken IC alternation (INVALID_STRUCTURE) with or without fields.

caution

A schema-valid query is not automatically safe to run. See Quote field names and always whitelist fields, operators, and combinators on the server.