` around the "Shift up"/"Shift down" buttons)
* `.undoRedoActions` (the wrapper `
` around the ["undo"/"redo" buttons](/docs/tips/undo-redo.md))
* `.rule-hasSubQuery` (applied to rules that render a subquery)
* `.queryBuilder-loading` (applied to rules/groups while an [async promise is pending](/docs/tips/async-option-lists.md))
* `.dndDragging` (applied to "preview" element while dragging)
* `.dndOver` (applied to "hovered over" element while dragging)
* `.dndCopy` (applied to "hovered over" element while dragging and the ["copy" modifier key is pressed](/docs/dnd.md#cloning-and-grouping))
* `.dndGroup` (applied to "hovered over" element while dragging and the ["group" modifier key is pressed](/docs/dnd.md#cloning-and-grouping))
* `.dndPreviewPosition` (applied to elements at the preview position during [update-while-dragging](/docs/dnd.md#updatewhiledragging))
* `.dndHidden` (applied to hide elements during [update-while-dragging](/docs/dnd.md#updatewhiledragging))
A full list of standard classnames is [below](#standard-classnames).
tip
Disable application of all standard classnames with the [`suppressStandardClassnames`](/docs/components/querybuilder.md#suppressstandardclassnames) prop.
.ruleGroup-combinators (and)\[ ].ruleGroup-notToggle.ruleGroup-addRule.ruleGroup-addGroup.ruleGroup-lock
.undoRedoActions-undo.undoRedoActions-redo
˄˅
.rule-fields (f1).rule-operators (=).rule-value.rule-cloneRule.rule-lock.rule-remove
˄˅
.ruleGroup-combinators (and)\[ ].ruleGroup-notToggle.ruleGroup-addRule.ruleGroup-addGroup.ruleGroup-cloneGroup.ruleGroup-lock.ruleGroup-remove
˄˅
.rule-fields (f1).rule-operators (=)This rule is valid.rule-cloneRule.rule-lock.rule-remove
˄˅
.rule-fields (f2).rule-operators (=)This rule is invalid.rule-cloneRule.rule-lock.rule-remove
˄˅
.ruleGroup-combinators (and).ruleGroup-notToggle.ruleGroup-addRule.ruleGroup-addGroup.ruleGroup-cloneGroup.ruleGroup-lock.ruleGroup-remove
˄˅
.rule-fields (f1).rule-operators (=).rule-cloneRule.rule-lock.rule-remove
˄˅
.rule-valueSource (f3).rule-operators (=)valuef1.rule-cloneRule.rule-lock.rule-remove
˄˅
Value list (fb1)betweenfield.rule-value-list-item (fb2).rule-value-list-item (fb2).rule-cloneRule.rule-lock.rule-remove
## Standard classnames[](#standard-classnames "Direct link to Standard classnames")
```
export const standardClassnames = {
queryBuilder: 'queryBuilder',
ruleGroup: 'ruleGroup',
header: 'ruleGroup-header',
body: 'ruleGroup-body',
combinators: 'ruleGroup-combinators',
addRule: 'ruleGroup-addRule',
addGroup: 'ruleGroup-addGroup',
cloneRule: 'rule-cloneRule',
cloneGroup: 'ruleGroup-cloneGroup',
removeGroup: 'ruleGroup-remove',
notToggle: 'ruleGroup-notToggle',
rule: 'rule',
fields: 'rule-fields',
matchMode: 'rule-matchMode',
matchThreshold: 'rule-matchThreshold',
operators: 'rule-operators',
value: 'rule-value',
removeRule: 'rule-remove',
betweenRules: 'betweenRules',
valid: 'queryBuilder-valid',
invalid: 'queryBuilder-invalid',
shiftActions: 'shiftActions',
undoRedoActions: 'undoRedoActions',
undoAction: 'undoRedoActions-undo',
redoAction: 'undoRedoActions-redo',
dndDragging: 'dndDragging',
dndOver: 'dndOver',
dndCopy: 'dndCopy',
dndGroup: 'dndGroup',
dndDropNotAllowed: 'dndDropNotAllowed',
dndPreviewPosition: 'dndPreviewPosition',
dndHidden: 'dndHidden',
dragHandle: 'queryBuilder-dragHandle',
disabled: 'queryBuilder-disabled',
muted: 'queryBuilder-muted',
lockRule: 'rule-lock',
lockGroup: 'ruleGroup-lock',
muteRule: 'rule-mute',
muteGroup: 'ruleGroup-mute',
valueSource: 'rule-valueSource',
valueListItem: 'rule-value-list-item',
branches: 'queryBuilder-branches',
justified: 'queryBuilder-justified',
responsive: 'queryBuilder-responsive',
hasSubQuery: 'rule-hasSubQuery',
loading: 'queryBuilder-loading',
valueDateTimeRelative: 'rule-value-dateTimeRelative',
} as const;
```
> *Source: [/packages/core/src/defaults.ts#L326-L374](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/defaults.ts#L326-L374)*
---
# Styling overview
React Query Builder has a [flexible structure with specific classes](/docs/styling/classnames.md) assigned to each element in the component hierarchy, enabling wide-ranging styling possibilities.
tip
Check out the [customization showcase](/docs/tips/showcase.md) for styling tips.
The default stylesheet is available in both CSS and SCSS formats, allowing you to override default values without replicating the entire rule set.
For layout-only styles (structural properties like `flex-direction`, `gap`, alignment—excluding decorative styles), import `query-builder-layout.css` (or `.scss`).
* CSS
* SCSS
```
@import 'react-querybuilder/dist/query-builder.css';
/* OR, for layout only: */
@import 'react-querybuilder/dist/query-builder-layout.css';
```
```
@use 'react-querybuilder/dist/query-builder.scss';
// OR, for layout only:
@use 'react-querybuilder/dist/query-builder-layout.scss';
```
## CSS variables[](#css-variables "Direct link to CSS variables")
Customize the default stylesheet by overriding CSS or SCSS variables.
info
CSS variables were introduced in **v8.3.0** and [SCSS variables](#scss-variables) have been available since **v4.0.0**. Both methods will continue to be supported.
Default variables:
* CSS
* SCSS
```
:root {
--rqb-spacing: 0.5rem;
--rqb-border-width: 1px;
--rqb-base-color: #004bb8;
--rqb-background-color: #004bb833;
--rqb-border-color: #8081a2;
--rqb-border-style: solid;
--rqb-border-radius: 0.25rem;
}
```
```
$rqb-spacing: 0.5rem;
$rqb-border-width: 1px;
$rqb-base-color: #004bb8;
$rqb-background-color: #004bb833;
$rqb-border-color: #8081a2;
$rqb-border-style: solid;
$rqb-border-radius: 0.25rem;
```
Example:
* CSS
* SCSS
```
@import 'react-querybuilder/dist/query-builder.css';
:root {
--rqb-spacing: 0.8rem; /* a little roomier than the default 0.5rem */
--rqb-background-color: #ccc3; /* gray, semi-transparent background */
}
```
```
@use 'react-querybuilder/dist/query-builder' with (
$rqb-spacing: 0.8rem, /* a little roomier than the default 0.5rem */
$rqb-background-color: #ccc3 /* gray, semi-transparent background */
);
```
### SCSS variables[](#scss-variables "Direct link to SCSS variables")
SCSS variables begin with `$` instead of `--`, but otherwise have the same name as their CSS counterpart (e.g., `$rqb-spacing` corresponds to `--rqb-spacing`).
SCSS allows you to customize the CSS variable prefix (`rqb-` by default) by setting `$rqb-var-prefix`.
```
@use 'react-querybuilder/dist/query-builder' with (
$rqb-var-prefix: myprefix-,
$rqb-spacing: 0.8rem
);
:root {
--myprefix-background-color: #ccc3;
}
```
## Branch lines[](#branch-lines "Direct link to Branch lines")
To add branch lines to the left side of rule groups, add the `queryBuilder-branches` class using the [`controlClassnames` prop](/docs/components/querybuilder.md#controlclassnames) or to any ancestor element.
```
```
https\://example.com
AND (and)+ Rule+ Group
First name (firstName)begins with (beginsWith)Stev⨯
OR (or)+ Rule+ Group⨯
Last name (lastName)=Vai⨯
Last name (lastName)=Vaughan⨯
The branch lines are colored red in the example above to stand out, but by default use the same color, width, and style as group borders. These variables can be overridden to customize branch lines:
* CSS
* SCSS
```
:root {
--rqb-branch-indent: var(--rqb-spacing);
--rqb-branch-width: var(--rqb-border-width);
--rqb-branch-color: var(--rqb-border-color);
--rqb-branch-radius: var(--rqb-border-radius);
--rqb-branch-style: var(--rqb-border-style);
}
```
```
$rqb-branch-indent: $rqb-spacing;
$rqb-branch-width: $rqb-border-width;
$rqb-branch-color: $rqb-border-color;
$rqb-branch-radius: $rqb-border-radius;
$rqb-branch-style: $rqb-border-style;
```
## Justified layout[](#justified-layout "Direct link to Justified layout")
For a "justified" layout, add the `queryBuilder-justified` class using the [`controlClassnames` prop](/docs/components/querybuilder.md#controlclassnames) or to any ancestor element.
Justified layout styles augment the default styles by pushing "+ Rule", "+ Group", clone, lock, and remove buttons to the right edge of their containers.
```
```
https\://example.com
AND (and)+ Rule+ Group
First name (firstName)begins with (beginsWith)Stev⨯
OR (or)+ Rule+ Group⨯
Last name (lastName)=Vai⨯
Last name (lastName)=Vaughan⨯
## Responsive layout[](#responsive-layout "Direct link to Responsive layout")
For a layout that adapts to narrow containers (e.g. mobile), add the `queryBuilder-responsive` class using the [`controlClassnames` prop](/docs/components/querybuilder.md#controlclassnames) or to any ancestor element.
Responsive layout styles augment the default styles so that rule and group header rows wrap onto multiple lines when they run out of horizontal space instead of overflowing. No media or container queries are involved, so the layout reflows based on the actual available width—even for deeply nested groups.
These styles are intentionally minimal and only lightly opinionated; think of them as a convenient starting point for responsive behavior rather than a canonical or prescribed approach. Override or replace them freely to suit your design.
```
```
https\://example.com
AND (and)+ Rule+ Group
First name (firstName)begins with (beginsWith)Stev⨯
OR (or)+ Rule+ Group⨯
Last name (lastName)=Vai⨯
Last name (lastName)=Vaughan⨯
## Drag-and-drop[](#drag-and-drop "Direct link to Drag-and-drop")
When [drag-and-drop is enabled](/docs/dnd.md), these variables control styles for dragged and hovered elements:
* CSS
* SCSS
```
:root {
--rqb-dnd-drop-indicator-color: rebeccapurple;
--rqb-dnd-drop-indicator-copy-color: #669933;
--rqb-dnd-drop-indicator-style: dashed;
--rqb-dnd-drop-indicator-width: 2px;
}
```
```
$rqb-dnd-drop-indicator-color: rebeccapurple;
$rqb-dnd-drop-indicator-copy-color: #669933;
$rqb-dnd-drop-indicator-style: dashed;
$rqb-dnd-drop-indicator-width: 2px;
// Deprecated variable names (still work)
// $rqb-dnd-hover-border-bottom-color: rebeccapurple;
// $rqb-dnd-hover-copy-border-bottom-color: #669933;
// $rqb-dnd-hover-border-bottom-style: dashed;
// $rqb-dnd-hover-border-bottom-width: 2px;
```
You can also assign styles to these classes:
* `dndDragging`: Assigned to the clone of the dragged element (the "ghost" that follows the mouse cursor)
* `dndOver`: Assigned to a drop target when the cursor hovers over it
* `dndCopy`: Assigned to a drop target when the cursor hovers over it while the "copy" modifier key is pressed (`Alt` on Windows/Linux, `⌥ Option` on macOS; see [Drag-and-drop § Cloning and grouping](/docs/dnd.md#cloning-and-grouping))
* `dndGroup`: Assigned to a drop target when the cursor hovers over it while the "group" modifier key is pressed (`Ctrl`; see [Drag-and-drop § Cloning and grouping](/docs/dnd.md#cloning-and-grouping))
---
# Adding and removing query properties
## Adding properties[](#adding-properties "Direct link to Adding properties")
To enhance a query object with additional properties, iterate through the `rules` array recursively. The [`transformQuery`](/docs/utils/misc.md#transformquery) utility function simplifies this process.
The example below (inspired by [issue #226](https://github.com/react-querybuilder/react-querybuilder/issues/226)) demonstrates adding the `inputType` property from the `fields` array to each rule in the query object.
```
import type { Field, RuleGroupType, RuleType } from 'react-querybuilder';
import { transformQuery } from 'react-querybuilder';
const fields: Field[] = [
{ name: 'description', label: 'Description', inputType: 'string' },
{ name: 'price', label: 'Price', inputType: 'number' },
];
const ruleProcessor: RuleProcessor = (r, { fieldData }): RuleType & { inputType?: string } => ({
...r,
inputType: fieldData?.inputType,
});
const result = transformQuery(query, { ruleProcessor });
```
Manual recursion
This example (taken directly from [issue #226](https://github.com/react-querybuilder/react-querybuilder/issues/226)) shows how to use manual recursion to achieve the same result as the `transformQuery` example above.
```
import type { Field, RuleGroupType, RuleType } from 'react-querybuilder';
const fields: Field[] = [
{ name: 'description', label: 'Description', inputType: 'string' },
{ name: 'price', label: 'Price', inputType: 'number' },
];
const processRule = (r: RuleType): RuleType & { inputType?: string } => ({
...r,
inputType: fields.find(f => f.name === r.field)?.inputType,
});
const processGroup = (rg: RuleGroupType): RuleGroupType => ({
...rg,
rules: rg.rules.map(r => {
if ('field' in r) {
return processRule(r);
}
return processGroup(r);
}),
});
const result = processGroup(query);
```
## Removing properties[](#removing-properties "Direct link to Removing properties")
To create a JSON string from a query object containing only specific properties, use the `replacer` parameter (second argument) of the [`JSON.stringify` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
```
const query: RuleGroupType = {
id: 'root',
combinator: 'and',
rules: [
{
field: 'firstName',
operator: '=',
value: 'Steve',
},
],
};
// This omits all properties except those specified in the replacer array:
console.log(JSON.stringify(query, ['rules', 'field', 'operator', 'value']));
// '{"rules":[{"field":"firstName","operator":"=","value":"Steve"}]}'
```
Alternatively, the `formatQuery` function provides a convenient method to generate a JSON string containing all query properties except `id` and `path`:
```
console.log(formatQuery(query, 'json_without_ids'));
// '{"combinator":"and","rules":[{"field":"firstName","operator":"=","value":"Steve"}]}'
```
---
# Arbitrary updates
Sometimes standard component actions don't meet specific requirements. React Query Builder provides tools to extend or replace the default behavior with custom implementations.
## Multiple action elements[](#multiple-action-elements "Direct link to Multiple action elements")
The default `addRuleAction` component always creates rules using the default configuration. However, you might need to provide multiple buttons that add rules with different pre-selected fields based on which button is clicked.
This example demonstrates a custom component that renders two buttons in place of the standard `addRuleAction`. Each button corresponds to a specific field. The click handler uses `props.schema.getQuery()` to retrieve the current query and `props.schema.dispatchQuery()` to update the query with the appropriate field selection.
## Custom query properties[](#custom-query-properties "Direct link to Custom query properties")
While the standard query structure works well, it may not contain all the information you need. You might require additional properties in the query hierarchy and want to manage these properties through custom controls within the query builder. This example adds a `caseSensitive` property to each rule, using a checkbox alongside the default value editor to toggle this setting.
This example also demonstrates how the `caseSensitive` property integrates with a custom [rule processor](/docs/utils/export.md#rule-processor) for `formatQuery`. When `caseSensitive` is false, the processor wraps the field with SQL's `LOWER` function and converts the value to lowercase before passing it to the default rule processor.
The example displays both the generated SQL query and the raw query object below the query builder interface.
> *Related: [Adding and removing query properties](/docs/tips/adding-removing-query-properties.md)*
---
# Async option list loading
To load option lists asynchronously for a value selector or editor, use the `useAsyncOptionList` hook imported from `"react-querybuilder/async"`.
This opt-in feature enables dynamic loading of options based on rule/group context, with intelligent caching for performance optimization.
tip
For more information about option list props, see [Working with option lists](/docs/tips/option-lists.md).
## Basic usage[](#basic-usage "Direct link to Basic usage")
1. Create a component that accepts `ValueSelectorProps` or `ValueEditorProps`.
2. Pass the props directly to `useAsyncOptionList` along with the async configuration options.
3. After any custom logic, pass the object returned from `useAsyncOptionList` as the props to a standard selector/editor component.
4. Assign the component in the [`controlElements` prop](/docs/components/querybuilder-controlelements.md).
```
import { type UseAsyncOptionListParams, useAsyncOptionList } from 'react-querybuilder/async';
const useAsyncOptionListParams: UseAsyncOptionListParams
= {
getCacheKey: 'field',
loadOptionList: async (value, { ruleOrGroup }) => {
const response = await fetch(`/api/operators?field=${ruleOrGroup.field}`);
return response.json();
},
};
// Step 1
const AsyncOperatorSelector = (props: ValueSelectorProps) => {
// Step 2
const asyncProps = useAsyncOptionList(props, useAsyncOptionListParams);
// Step 3
return ;
};
const App = () => (
);
```
tip
While you can explicitly render any selector or editor component...
```
// For example:
return ;
// or
return ;
```
...rendering the configured value selector/editor makes your component more versatile as it will automatically adapt to configuration changes at the context and query builder level.
```
return ;
// or
return ;
```
This method can also help avoid some issues with certain [compatibility packages](/docs/compat.md).
## Configuration options[](#configuration-options "Direct link to Configuration options")
### `loadOptionList`[](#loadoptionlist "Direct link to loadoptionlist")
Function that returns a `Promise` for the [option list](/docs/tips/option-lists.md). This function is called when a valid cached list is unavailable. It should ultimately call your API, if and when necessary.
* As with option list-style props on [`QueryBuilder`](/docs/components/querybuilder.md), the resolved value from `loadOptionList` can be `string[]`, `Option[]`, or `OptionGroup[]`.
* The resolved list will be processed through the `prepareOptionList` function, guaranteeing each option is a `FullOption` with `name`, `value`, and `label` properties.
* The processed list will be `options` in the returned object if a `ValueSelectorProps` object is passed in, or `values` if `ValueEditorProps` is passed in.
**Example:**
```
const loadFieldOptions = async (value, { ruleOrGroup }) => {
// Current selector value is available
console.log('Current value:', value);
// Rule or group context is available
if (ruleOrGroup?.field === 'user') {
return await fetch('/api/user-fields').then(r => r.json());
}
return await fetch('/api/default-fields').then(r => r.json());
};
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { loadOptionList: loadFieldOptions });
return ;
};
```
### `getCacheKey`[](#getcachekey "Direct link to getcachekey")
Controls cache key generation. Can be a string, array of strings, or a function returning a string.
#### Cache by property name (string)[](#cache-by-property-name-string "Direct link to Cache by property name (string)")
```
// Cache by field value only
const getCacheKey = 'field';
// Or cache by operator value only
const getCacheKey = 'operator';
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { getCacheKey, loadOptionList });
return ;
};
```
#### Cache by multiple property names (array of strings)[](#cache-by-multiple-property-names-array-of-strings "Direct link to Cache by multiple property names (array of strings)")
```
// Cache by combination of field and operator
const getCacheKey = ['field', 'operator'];
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { getCacheKey, loadOptionList });
return ;
};
```
#### Cache by custom function[](#cache-by-custom-function "Direct link to Cache by custom function")
```
// `getCacheKey` receives the entire props object as its only parameter
const getCacheKey = (props: ValueSelectorProps) => {
const {
rule,
ruleGroup,
schema: { qbId },
} = props;
// Using `qbId` will cache each query builder separately
return `${qbid}-${rule?.field}-${rule?.operator}-${ruleGroup?.id}`;
};
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { getCacheKey, loadOptionList });
return ;
};
```
### `cacheTTL`[](#cachettl "Direct link to cachettl")
Cache time-to-live in milliseconds. Defaults to `1_800_000` (30 minutes).
```
// 30 minutes (default)
const cacheTTL = 1_800_000;
// 5 minutes: m s ms
const cacheTTL = 5 * 60 * 1000;
// Disable caching (cache will be populated but immediately outdated)
const cacheTTL = 0;
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { cacheTTL, loadOptionList });
return ;
};
```
## Loading states[](#loading-states "Direct link to Loading states")
`useAsyncOptionList` adds the "queryBuilder-loading" class while the promise from `loadOptionList` is pending (if [`suppressStandardClassnames`](/docs/components/querybuilder.md#suppressstandardclassnames) is not `true`). No styles are applied by the default stylesheet for this class.
To add custom classes during pending `loadOptionList` promises, use `controlClassnames#loading` or override the `className` prop on the rendered value selector.
In this example, `my-async-loading-class` will be added to the specific component `ValueSelectorAsync` when loading, and `common-async-loading-class` will be added to *all* "loading" selectors.
```
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, { ...otherParams, isLoading });
return (
);
};
const App = () => (
);
```
To force a "loading" state, set the `isLoading` parameter to `true`:
```
const ValueSelectorAsync = (props: ValueSelectorProps) => {
// Assume this hook determines whether to force a "loading" state and returns a `boolean`:
const isLoading = useIsLoading(props);
const asyncProps = useAsyncOptionList(props, { ...otherParams, isLoading });
return ;
};
```
## Real-world examples[](#real-world-examples "Direct link to Real-world examples")
### Dependent values[](#dependent-values "Direct link to Dependent values")
Load options in the value editor that depend on the selected field and operator. The value editor must
```
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, {
loadOptionList: async (value, { ruleOrGroup }) => {
const { field, operator } = ruleOrGroup as RuleType;
return myValuesAPI({ field, operator });
},
getCacheKey: ['field', 'operator'],
});
return ;
};
// Assign the async value selector as `selectorComponent` to an otherwise
// "pass-through" value editor component.
const ValueEditorAsync = (props: ValueEditorProps) => (
);
// Assign the custom value editor in `controlElements`
const App = () => ;
```
### Dependent operators[](#dependent-operators "Direct link to Dependent operators")
Load operators that depend on the selected field type:
```
const ValueSelectorAsync = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, {
loadOptionList: async (value, { ruleOrGroup }) => {
const fieldType = props.fieldData.datatype; // custom field property
return getOperatorsForType(fieldType);
},
getCacheKey: props => `operators-${props.fieldData.datatype}`,
});
return ;
};
```
### Auto-complete value editor[](#auto-complete-value-editor "Direct link to Auto-complete value editor")
Create an auto-complete component by including the current value in the cache key:
```
const AutoCompleteValueSelector = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, {
loadOptionList: async (value, { ruleOrGroup }) => {
if (!value || value.length < 2) return [];
return fetch(`/api/autocomplete?q=${value}&field=${ruleOrGroup?.field}`).then(r => r.json());
},
getCacheKey: props => `autocomplete-${props.rule?.field}-${props.value}`,
});
// Rendering of the input and option list is left to this component
// (see below for example usage of third-party auto-complete components)
return ;
};
// Use the autocomplete selector as the selector for the value editor
const ValueEditorWithAutocomplete = (props: ValueEditorProps) => (
);
// Assign the new value editor
const App = () => ;
```
Some of the [compatibility packages](/docs/compat.md) provide themed auto-complete components that integrate well with `useAsyncOptionList`.
* MUI/Material
* Mantine
* Ant Design
```
import { Autocomplete, TextField } from '@mui/material';
export const ValueEditorAutocompleteAsync = (props: ValueEditorProps) => {
const { value, handleOnChange, values } = useAsyncOptionList(props, {
getCacheKey,
loadOptionList,
});
return (
handleOnChange(v)}
disabled={props.disabled}
renderInput={params => (
)}
/>
);
};
```
```
import { Autocomplete } from '@mantine/core';
export const ValueEditorAutocompleteAsync = (props: ValueEditorProps) => {
const { value, handleOnChange, values } = useAsyncOptionList(props, {
getCacheKey,
loadOptionList,
});
return (
);
};
```
```
import { AutoComplete } from 'antd';
export const ValueEditorAutocompleteAsync = (props: ValueEditorProps) => {
const { value, handleOnChange, values } = useAsyncOptionList(props, {
getCacheKey,
loadOptionList,
});
return (
);
};
```
Mock loader setup
This code can be used to mock an API call for the compatibility examples above.
```
// prettier-ignore
const words = [ "React", "Angular", "Vue", "Svelte", "Next.js", "Nuxt.js", "Gatsby", "TypeScript", "JavaScript", "Python", "Java", "C#", "Go", "Rust", "Node.js", "Express", "Fastify", "Koa", "Hapi", "NestJS", "MongoDB", "PostgreSQL", "MySQL", "Redis", "SQLite", "Docker", "Kubernetes", "AWS", "Azure", "GCP"];
// Simulate async data loading
const loadOptionList = async (value: string | undefined): Promise => {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 500));
// Filter based on input value if provided
if (value && value.length > 0) {
return words.filter(word => word.toLowerCase().includes(value.toLowerCase()));
}
// Otherwise return no results
return [];
};
const getCacheKey = ({ value }: ValueEditorProps) => value;
```
## Error handling[](#error-handling "Direct link to Error handling")
Async loading errors can be managed within your `loadOptionList` function or by checking the `errors` property on the object returned from `useAsyncOptionList`, which will contain an error message when the promise is rejected.
Internal error handling:
```
const loadOptionList = async (value, { ruleOrGroup }) => {
try {
const response = await fetch('/api/options');
if (!response.ok) throw new Error('Failed to load options');
return response.json();
} catch (error) {
// Log the error and return fallback options
console.error('Failed to load options:', error);
return [{ name: 'error', value: 'error', label: 'Error loading options' }];
}
};
```
Promise rejection detection:
```
const AsyncOperatorSelector = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, useAsyncOptionListParams);
// If `errors` is truthy, the promise was rejected
if (asyncProps.errors) {
const fallbackOptions = [{ name: 'error', value: 'error', label: 'Error loading options' }];
return ;
}
return ;
};
```
## Best practices[](#best-practices "Direct link to Best practices")
### Cache key design[](#cache-key-design "Direct link to Cache key design")
* **Don't include the selector's own value** unless building auto-complete
* **Use specific, meaningful keys** to avoid cache conflicts
* **Consider rule/group hierarchy** for context-dependent options
```
// ❌ Bad: includes own value (unless auto-complete)
getCacheKey: props => `${props.rule?.field}-${props.value}`;
// ✅ Good: context-dependent without own value
getCacheKey: props => `operators-${props.rule?.field}`;
```
### Performance optimization[](#performance-optimization "Direct link to Performance optimization")
* **Set appropriate cache TTL** based on data freshness requirements
* **Use specific cache keys** to maximize cache hits
* **Consider debouncing** for auto-complete scenarios
### Error resilience[](#error-resilience "Direct link to Error resilience")
* **Provide fallback options** when loading fails
* **Handle network timeouts** gracefully
* **Show meaningful error states** to users
---
# Common mistakes
## Custom component as closure[](#custom-component-as-closure "Direct link to Custom component as closure")
Avoid defining custom components for React Query Builder [inside the body of another function component](https://react.dev/learn/your-first-component#nesting-and-organizing-components). Here's what not to do:
App.jsx
```
const App = () => {
// Other stuff ...
const CustomValueEditor = props => {
// Custom logic here ...
return ;
};
return (
setQuery(q)}
controlElements={{
valueEditor: CustomValueEditor,
}}
/>
);
};
```
This pattern causes issues like input fields losing focus after each keystroke because `CustomValueEditor` is recreated on every `App` render. Instead, declare custom components outside the parent function:
App.jsx
```
const CustomValueEditor = props => {
// Custom logic here ...
return ;
};
const App = () => {
// Other stuff ...
return (
setQuery(q)}
controlElements={{
valueEditor: CustomValueEditor,
}}
/>
);
};
```
Another common mistake involves creating wrapper arrow functions in JSX. In this example, `CustomValueEditor` is correctly defined outside `App`, but assigning an arrow function that renders it (instead of the component itself) creates a new function component on every render:
App.jsx
```
const CustomValueEditor = props => {
// Custom logic here ...
return ;
};
const App = () => {
// Other stuff ...
return (
setQuery(q)}
controlElements={{
// Don't do this:
valueEditor: props => ,
// Do this instead:
// valueEditor: CustomValueEditor,
}}
/>
);
};
```
---
# Comparison with other libraries
The query builder library most often compared to React Query Builder (RQB) is [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB). Both render an interactive UI for building filter criteria, and both can export those criteria to several query languages, but they take meaningfully different approaches.
This page summarizes the differences as of RQB v8 and RAQB v6.6.15 (May 2025). If you're moving an existing RAQB implementation to RQB, see [Migrating from react-awesome-query-builder](/docs/tips/migrate-from-raqb.md).
## Philosophy[](#philosophy "Direct link to Philosophy")
| | React Query Builder | react-awesome-query-builder |
| --------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Query storage | Plain JSON ([`RuleGroupType`](/docs/typescript.md#rules-and-groups)), serializable as-is | [immutable.js](https://immutable-js.com/) tree; plain JSON via `Utils.getTree()` |
| Configuration | Discrete props (`fields`, `operators`, `combinators`, `controlElements`, etc.) | One monolithic `Config` object (types, widgets, operators, funcs, settings) |
| Packaging | Composable packages — core, React, UI styles, and optional feature packages | Core package plus UI-specific packages |
| Non-React usage | `@react-querybuilder/core` has no React dependency (server-safe) | Core depends on React |
RQB's plain-JSON query format means you can store queries in a database, send them over the wire, diff them, and hand-edit them without any conversion step. RQB's discrete props mean you only configure (and only bundle) the parts you use.
## Query model[](#query-model "Direct link to Query model")
| Aspect | React Query Builder | react-awesome-query-builder |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Group children | `rules` | `children1` (array, or keyed object when `children1AsArray: false`) |
| Combinators | `combinator` on the group, *or* [independent combinators](/docs/components/querybuilder.md#independent-combinators) interleaved between rules | `conjunction` on the group only |
| Negation | `not` on groups | `not` on groups |
| Rule value | Scalar or array, depending on the operator | Always an array (one entry per operand) |
| Subqueries | [Match modes](/docs/tips/subqueries.md) — `all`, `some`, `none`, `atLeast`, `atMost`, `exactly` (with threshold) | `!group` fields with `some`/`all`/`none` and count operators |
| Disabled rules | `disabled` | `isLocked` |
| Muted rules | [`muted`](/docs/utils/export.md#muted-rules-and-groups) (kept in the query, excluded from exports) | — |
| Ternary/CASE | — | `switch_group` / `case_group` |
| Value sources | `value`, `field`, [expressions](/docs/expr.md), [parameters](/docs/tips/parameter-manager.md) | `value`, `field`, `func` |
| Left-hand side | Expressions supported on the LHS as well as the RHS | `fieldSrc: "func"` |
## Formats[](#formats "Direct link to Formats")
RQB's [`formatQuery`](/docs/utils/export.md) supports considerably more export targets, and its [parsers](/docs/utils/import.md) support more import sources.
| Direction | React Query Builder | react-awesome-query-builder |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Export | JSON, SQL (plain/parameterized/named), MongoDB, CEL, JsonLogic, SpEL, ElasticSearch, JSONata, LDAP, natural language, Drizzle, TanStack DB, Prisma, Sequelize, Cypher, GraphQL, SPARQL, Gremlin, and more | SQL, MongoDB, JsonLogic, SpEL, ElasticSearch (partial), query string |
| Import | SQL, CEL, JsonLogic, MongoDB, SpEL, JSONata, Cypher, GraphQL, SPARQL, Gremlin, **RAQB** | JsonLogic, SpEL, SQL (separate package) |
## Ecosystem[](#ecosystem "Direct link to Ecosystem")
| Aspect | React Query Builder | react-awesome-query-builder |
| -------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| UI style packs | Ant Design, Bootstrap, Bulma, Chakra UI, Fluent UI, Mantine, MUI/Material, PrimeReact, Tremor | Ant Design, MUI, Material (v4), Bootstrap, Fluent UI |
| React Native | `@react-querybuilder/native` | — |
| Drag-and-drop | [`@react-querybuilder/dnd`](/docs/dnd.md) (opt-in) | Built in |
| Date/time | [`@react-querybuilder/datetime`](/docs/datetime.md) (relative dates, per-dialect formatting) | Built in (widgets and functions) |
| Expressions | [`@react-querybuilder/expr`](/docs/expr.md) | Built in (config `funcs`) |
| Rules engine | [`@react-querybuilder/rules-engine`](/docs/rules-engine.md) | — |
| Accessibility | ARIA attributes, keyboard navigation, `data-testid` hooks | Not documented |
| Internationalization | `translations` prop accepting strings or JSX | i18next-based locale packs |
| Validation | Per-field `validator` plus [`defaultValidator`](/docs/utils/validation.md) | `sanitizeTree` / `validateTree` / `isValidTree` |
| Styling | Unstyled by default with CSS custom properties; official style packs | Bundled SCSS per UI package |
## Feature-by-feature notes[](#feature-by-feature-notes "Direct link to Feature-by-feature notes")
Most RAQB concepts have a direct RQB counterpart. These are the exceptions:
* **Ternary (`switch_group`)** — RAQB can build CASE/WHEN-style expressions. RQB has no equivalent in the query builder itself; conditional logic is generally modeled with [`@react-querybuilder/rules-engine`](/docs/rules-engine.md) instead.
* **`proximity` operator** — RAQB's full-text proximity operator (and its `operatorOptions`) has no RQB equivalent.
* **`is_empty` / `is_not_empty`** — RQB expresses these as `=` and `!=` against an empty string.
* **Locking** — RQB models this with the `disabled` property rather than a separate `locked` property, but the behavior and UI are equivalent. The [`showLockButtons`](/docs/components/querybuilder.md#showlockbuttons) prop displays "Lock rule"/"Lock group" toggle buttons, which set `disabled` on the rule or group. As in RAQB, a locked group disables its header elements and all descendants. RQB has no equivalent to RAQB's `canDeleteLocked` setting.
* **Date/time functions** — RAQB models `NOW`, `TODAY`, `RELATIVE_DATETIME`, etc. as functions. RQB treats the same concept as a *value*: [`@react-querybuilder/datetime`](/docs/datetime.md) relative date/time values, which each rule processor serializes symbolically (e.g. `current_timestamp - interval '7 days'`).
* **Cardinality operators** — RQB's match modes are a superset of RAQB's `!group` count operators, except that RQB has no strict-inequality modes (RAQB's `less`/`greater` counts).
---
# Custom bind variables
tip
The specific method described below is not necessary in version 7 or later. The [`numberedParams` option](/docs/utils/export.md#numbered-parameters) will achieve the same result.
Different SQL database systems have varying requirements for bind variable placeholders. Some use a simple `?` character (the default format from `formatQuery(query, 'parameterized')`), while others require placeholders starting with `$` followed by a unique identifier or number.
The ["parameterized\_named" export format](/docs/utils/export.md#named-parameters) with the [`paramPrefix` option](/docs/utils/export.md#parameter-prefix) typically handles named placeholders. However, if the default parameter names (e.g., `:fieldName_1`) don't meet your requirements, you can use the "parameterized" format and replace the `?` placeholders with custom names.
This code generates a SQL string with numbered bind variable placeholders from "$1" to "$n", where n equals the number of bind variables (matching the number of elements in the `params` array):
```
let i = 0;
const fq = formatQuery(query, 'parameterized');
const fqWithNumberedParams = {
...fq,
sql: fq.sql.replaceAll('?', () => `$${++i}`),
};
```
If `formatQuery(query, "parameterized")` returns this object:
```
{
"sql": "(firstName = ? and lastName = ?)",
"params": ["Steve", "Vai"]
}
```
The code above transforms it into:
```
{
"sql": "(firstName = $1 and lastName = $2)",
"params": ["Steve", "Vai"]
}
```
---
# Custom components with fallbacks
Sometimes a default component nearly meets your needs, but requires minor behavioral modifications. Rather than recreating the entire component, you can implement custom behavior and fall back to the default component when appropriate. This approach keeps your implementation current with standard features while maintaining customization flexibility.
Consider a scenario where you need a custom date picker for specific fields, while using the standard value editor for others. The default `ValueEditor` lacks date picker functionality, requiring a custom solution.
Instead of duplicating the default `ValueEditor` code, you can leverage its existing functionality by spreading the same props (` `) and returning it when your custom behavior doesn't apply.
This example creates a custom value editor using the [`react-datepicker`](https://reactdatepicker.com/) library. We'll start by configuring the `fields` array with standard `Field` objects, adding a custom `datatype` attribute to date fields that signals when to display the date picker.
```
// fields.ts
import { Field } from 'react-querybuilder';
export const fields: Field[] = [
{
name: 'name',
label: 'Name',
operators: [
{ name: '=', label: 'is' },
{ name: 'beginsWith', label: 'begins with' },
],
},
{
name: 'dateOfBirth',
label: 'Date of Birth',
operators: [{ name: '=', label: 'is' }],
datatype: 'date',
},
{
name: 'dateRange',
label: 'Date Range',
operators: [{ name: 'between', label: 'is between' }],
datatype: 'dateRange',
},
];
```
The custom value editor displays different interfaces based on the field's `datatype`:
* `"date"`: Standard date picker
* `"dateRange"`: Date range picker
* Other values or `undefined`: Falls back to the default `ValueEditor`
We use the [`date-fns`](https://date-fns.org/) library for date parsing and formatting. Storing dates as strings (rather than `Date` objects) keeps the query object serializable for `JSON.stringify`. Date ranges are stored as comma-separated string pairs.
```
// CustomValueEditor.tsx
import { format, parse } from 'date-fns';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { ValueEditor, ValueEditorProps } from 'react-querybuilder';
const dateFormat = 'yyyy-MM-dd';
export const CustomValueEditor = (props: ValueEditorProps) => {
if (props.fieldData.datatype === 'date') {
return (
props.handleOnChange(d ? format(d, dateFormat) : null)}
/>
);
} else if (props.fieldData.datatype === 'dateRange') {
const [startDate, endDate] = props.value.split(',');
return (
{
const [s, e] = update;
props.handleOnChange(
[!s ? '' : format(s, dateFormat), !e ? '' : format(e, dateFormat)].join(',')
);
}}
/>
);
}
return ;
};
```
tip
If you're using one of the [compatibility packages](/docs/compat.md), you probably want to fall back to the value editor from that package instead of `ValueEditor` from the main package. For example, when using `@react-querybuilder/antd`, fall back to `AntDValueEditor`:
```
-import { ValueEditor, ValueEditorProps } from 'react-querybuilder';
+import { AntDValueEditor } from '@react-querybuilder/antd';
+import { ValueEditorProps } from 'react-querybuilder';
```
```
- return ;
+ return ;
```
Configure the `QueryBuilder` component to use the custom value editor through the `controlElements` prop:
```
// App.tsx
import { useState } from 'react';
import { CustomValueEditor } from './CustomValueEditor';
import { fields } from './fields';
export default function App() {
const [query, setQuery] = useState({ combinator: 'and', rules: [] });
return (
);
}
```
The interactive demo below shows how each field type behaves: the "Name" field displays a text input, "Date of Birth" shows a standard date picker, and "Date Range" presents a date range picker.
note
Other examples of the "fallback" technique can be seen in the [Limit rule groups](/docs/tips/limit-groups.md#conditionally-allow-new-groups) page and [these](https://stackoverflow.com/questions/68447510/react-query-builder-question-is-there-a-way-to-disable-a-field-option-when-addi/69443288#69443288) [two](https://stackoverflow.com/questions/61768845/progamatically-show-hide-operator-rule-and-group-button-in-react-querybuilder/69443467#69443467) StackOverflow answers.
---
# External controls
React Query Builder exports the same [query tools](/docs/utils/query-management.md#query-tools) used internally for managing query updates. You can use these functions outside the ` ` component for greater UI design flexibility while maintaining full query management capabilities.
Both examples below hide the default add/remove buttons and implement external controls above the query builder. The query methods (`add`, `remove`, `update`, and `move`) are called from event handlers, each returning a new query that replaces the current one.
## Uncontrolled component[](#uncontrolled-component "Direct link to Uncontrolled component")
Give the query builder an explicit [`qbId`](/docs/components/querybuilder.md#qbid) and the external controls can address it by that identifier, reading and updating its query without your component owning the state.
* [`useQueryBuilderSelector`](/docs/utils/hooks.md#usequerybuilderselector) with [`getQuerySelectorById`](/docs/utils/hooks.md#usequerybuilderselector) reads the current query and re-renders the controls whenever it changes—including changes made from within the query builder itself.
* [`getDispatchQueryById`](/docs/utils/hooks.md#getdispatchquerybyid) applies a new query exactly as a user edit would, so `onQueryChange` still fires if you supply it.
Both need access to the internal store, which is why `QueryBuilderStateProvider` wraps the pair. The query builder renders its own provider internally, so this only extends that same store to components *outside* it.
tip
`getDispatchQueryById` returns `undefined` when no query builder with the given `qbId` is mounted, hence the optional call. Calling it from event handlers (rather than capturing it during render) means the controls always dispatch to the currently mounted query builder.
## Controlled component[](#controlled-component "Direct link to Controlled component")
When your component already owns the query, the same query tools apply—call them from event handlers and assign the result to your state variable to keep everything synchronized.
---
# Limit rule groups
Some implementations require a flat, rule-only structure, or need to restrict group creation to specific conditions (such as limiting depth to one level). Several approaches can address these requirements.
## Prevent creation of new groups[](#prevent-creation-of-new-groups "Direct link to Prevent creation of new groups")
The simplest approach is hiding the "+ Group" button entirely. Set the `addGroupAction` component to `null` (or `() => null`) to prevent group creation:
```
```
Alternatively, hide the button using CSS with the default `ruleGroup-addGroup` class:
```
.ruleGroup-addGroup {
display: none;
}
```
Both approaches produce this result:
https\://example.com
AND (and)+ Rule
## Conditionally allow new groups[](#conditionally-allow-new-groups "Direct link to Conditionally allow new groups")
To allow groups only at the top level (hiding the button in sub-groups), conditionally render the `ActionElement` based on the `level` prop:
```
(props.level === 0 ? : null),
}}
/>
```
The CSS equivalent uses descendant selectors:
```
.ruleGroup .ruleGroup .ruleGroup-addGroup {
display: none;
}
```
Both methods produce this result:
https\://example.com
AND (and)+ Rule+ Group
AND (and)+ Rule⨯
AND (and)+ Rule⨯
## Other methods[](#other-methods "Direct link to Other methods")
You can also prevent group addition by returning `false` from the `onAddGroup` callback.
caution
This approach alone may confuse users since the "+ Group" button appears clickable but produces no result. Consider combining this method with user interface cues or messaging to clarify the behavior.
```
false}
/>
```
---
# Managing fields
The [`fields`](/docs/components/querybuilder.md#fields) array forms the foundation of React Query Builder configuration, defining which data fields users can include in a query.
tip
For more information about option list props like `fields`, see [Working with option lists](/docs/tips/option-lists.md).
## Updating `fields` at runtime[](#updating-fields-at-runtime "Direct link to updating-fields-at-runtime")
The `fields` prop is fully reactive — when it changes, the query builder re-normalizes the field list and updates all selectors automatically. This means you can let users add, remove, or edit fields at runtime with standard React state:
```
import { useState } from 'react';
import { QueryBuilder } from 'react-querybuilder';
import type { Field, RuleGroupType } from 'react-querybuilder';
const initialFields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
export function App() {
const [fields, setFields] = useState(initialFields);
const [query, setQuery] = useState({ combinator: 'and', rules: [] });
const addField = () =>
setFields(prev => [...prev, { name: 'email', label: 'Email', inputType: 'email' }]);
const removeField = (name: string) => setFields(prev => prev.filter(f => f.name !== name));
return (
<>
Add Email Field
removeField('email')}>Remove Email Field
>
);
}
```
Existing rules that reference unchanged field names remain valid when the field list is updated. If you remove a field that is already referenced by a rule, the rule will still render but display the now-missing field name in the selector.
tip
If your fields array is computed inline or derived from other state, wrap it in `useMemo` to avoid unnecessary re-normalization on every render.
## Generating `fields` dynamically[](#generating-fields-dynamically "Direct link to generating-fields-dynamically")
Field arrays typically correspond to database table columns. You can dynamically generate the `fields` array by querying your database's information schema.
The following examples demonstrate database-specific queries to extract field information. Each platform has unique syntax and data type handling, resulting in different query structures. Key patterns include:
* Only `label` and at least one of `name` or `value` are required in the `fields` prop; other properties are optional.
* `label` typically uses the same value as `name` and `value`, but consider using more user-friendly captions from other sources.
* `datatype` (used by the [date/time package](/docs/datetime.md), though not an official `Field` property) copies the column's declared type directly.
* `inputType` gets normalized to HTML5 input types, or `null` when no reliable mapping exists.
* These examples cast `defaultValue` as text; consider more sophisticated type conversions for your specific needs. `defaultValue` will be `null` when no default is configured.
> **Note:** `inputType: null` and `defaultValue: null` behave differently than `undefined` or missing properties. Consider removing `null` values from the query results or substituting empty strings (`""`) as needed.
### Relational databases[](#relational-databases "Direct link to Relational databases")
#### PostgreSQL[](#postgresql "Direct link to PostgreSQL")
```
SELECT json_agg(
json_build_object(
'name', column_name,
'value', column_name,
'label', column_name,
'datatype', data_type || CASE WHEN data_type LIKE '%char%' THEN '(' || character_maximum_length || ')' END,
'defaultValue', column_default::text,
'inputType', CASE
WHEN data_type LIKE '%char%' OR data_type = 'text' THEN 'text'
WHEN data_type IN ('integer', 'bigint', 'smallint', 'decimal', 'numeric', 'real', 'double precision') THEN 'number'
WHEN data_type = 'date' THEN 'date'
WHEN data_type LIKE 'timestamp%' THEN 'datetime-local'
WHEN data_type LIKE 'time%' THEN 'time'
END
) ORDER BY ordinal_position
) AS fields
FROM information_schema.columns
WHERE table_name = 'my_table';
```
#### MySQL[](#mysql "Direct link to MySQL")
```
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'name', COLUMN_NAME,
'value', COLUMN_NAME,
'label', COLUMN_NAME,
'datatype', DATA_TYPE,
'defaultValue', CAST(column_default AS CHAR),
'inputType', CASE
WHEN DATA_TYPE LIKE '%char%' OR DATA_TYPE = 'text' THEN 'text'
WHEN DATA_TYPE IN ('int', 'bigint', 'smallint', 'decimal', 'dec', 'fixed', 'numeric', 'float', 'double', 'double precision') THEN 'number'
WHEN DATA_TYPE = 'date' THEN 'date'
WHEN DATA_TYPE = 'time' THEN 'time'
WHEN DATA_TYPE IN ('datetime', 'timestamp') THEN 'datetime-local'
END
)
) AS fields
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'my_table';
```
#### SQLite[](#sqlite "Direct link to SQLite")
```
SELECT json_group_array(
json_object(
'name', name,
'value', name,
'label', name,
'datatype', type,
'defaultValue', dflt_value,
'inputType', CASE
WHEN UPPER(type) LIKE '%INT%' OR UPPER(type) LIKE '%REAL%' OR UPPER(type) LIKE '%FLOA%' OR UPPER(type) LIKE '%DOUB%' THEN 'number'
END,
'affinity', CASE -- See https://sqlite.org/datatype3.html#type_affinity
WHEN UPPER(type) LIKE '%INT%' THEN 'INTEGER'
WHEN UPPER(type) LIKE '%CHAR%' OR UPPER(type) LIKE '%CLOB%' OR UPPER(type) LIKE '%TEXT%' THEN 'TEXT'
WHEN UPPER(type) LIKE '%BLOB%' OR type IS NULL THEN 'BLOB'
WHEN UPPER(type) LIKE '%REAL%' OR UPPER(type) LIKE '%FLOA%' OR UPPER(type) LIKE '%DOUB%' THEN 'REAL'
ELSE 'NUMERIC'
END
)
) fields
FROM pragma_table_info('my_table')
ORDER BY cid;
```
#### SQL Server[](#sql-server "Direct link to SQL Server")
```
SELECT (
SELECT
COLUMN_NAME AS [name],
COLUMN_NAME AS [value],
COLUMN_NAME AS [label],
CAST(COLUMN_DEFAULT AS CHAR) AS [defaultValue],
CASE WHEN DATA_TYPE LIKE '%CHAR%' THEN CONCAT(DATA_TYPE, '(', CAST(ROUND(CHARACTER_MAXIMUM_LENGTH, 0) AS int), ')') ELSE DATA_TYPE END AS [datatype],
CASE
WHEN DATA_TYPE IN ('char', 'varchar', 'text', 'nchar', 'nvarchar', 'ntext') THEN 'text'
WHEN DATA_TYPE IN ('tinyint', 'smallint', 'int', 'bigint', 'bit', 'decimal', 'numeric', 'money', 'smallmoney', 'float', 'real') THEN 'number'
WHEN DATA_TYPE = 'date' THEN 'date'
WHEN DATA_TYPE = 'time' THEN 'time'
WHEN DATA_TYPE LIKE '%datetime%' THEN 'datetime-local'
END AS [inputType]
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME='my_table'
ORDER BY ORDINAL_POSITION
FOR JSON AUTO
) AS [fields];
```
#### Oracle[](#oracle "Direct link to Oracle")
```
SELECT json_arrayagg(
json_object(
'name' VALUE column_name,
'value' VALUE column_name,
'label' VALUE column_name,
'datatype' VALUE data_type || CASE WHEN data_type LIKE '%CHAR%' THEN '(' || data_length || ')' END,
-- 'defaultValue' is omitted in this example because ALL_TAB_COLS.DATA_DEFAULT
-- is type LONG which is difficult to convert to text without custom functions.
'inputType' VALUE CASE
WHEN data_type LIKE '%CHAR%' THEN 'text'
WHEN data_type IN ('NUMBER', 'NUMERIC', 'FLOAT', 'DECIMAL', 'DEC', 'INTEGER', 'INT', 'SMALLINT') THEN 'number'
WHEN data_type = 'DATE' THEN 'date'
WHEN data_type = 'TIMESTAMP' THEN 'datetime-local'
END
) ORDER BY column_id
) fields
FROM all_tab_cols
WHERE table_name = 'my_table';
```
### MongoDB[](#mongodb "Direct link to MongoDB")
*Coming soon*
### ElasticSearch[](#elasticsearch "Direct link to ElasticSearch")
*Coming soon*
---
# Managing operators
Many React Query Builder implementations need to customize operators based on the selected field type. For example, date fields might use "before" as a more intuitive label than the default `"<"` operator. Number fields could display "less than" for the `<` operator, while text fields might exclude comparison operators entirely.
tip
For more information about option list props like `operators`, see [Working with option lists](/docs/tips/option-lists.md).
## Field `operators` property[](#field-operators-property "Direct link to field-operators-property")
One approach uses the field's `operators` property to specify which operators appear when users select a particular field. However, this method requires defining complete operator lists for each field, potentially creating duplication across fields with similar data types.
## `getOperators` prop[](#getoperators-prop "Direct link to getoperators-prop")
While the field `operators` property works well for individual field customizations, the `getOperators` function prop centralizes all operator logic in one location.
`getOperators` receives two arguments: the field identifier and a meta object containing the complete field definition. Using this information, you can reference the `fields` array (or other data sources) to return appropriate operator lists with custom names and labels.
This example demonstrates custom field properties for operator management. Each field includes a custom `datatype` property that determines operator selection and labeling. The `defaultOperators` export provides base functionality where needed. Note that when present, a field's `operators` property overrides `getOperators` results—observe how "Favorite Movie" shows only the "is" operator despite having `datatype: "text"`.
---
# Maximizing rendering performance
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
TL;DR
Each prop passed to `QueryBuilder` should have a stable reference or be memoized.
Starting with version 7, all `QueryBuilder` props, components, and derived values use aggressive memoization through `React.memo`, `useMemo`, `useCallback`, and immutability tools like [`immer`](https://immerjs.github.io/immer/). These optimizations significantly improve rendering performance for complex queries, particularly when using certain style libraries. To benefit from these optimizations, every prop passed to `QueryBuilder` (except `query`, when used) must maintain a stable reference or be properly memoized. We recommend using `QueryBuilder` as an uncontrolled component (`defaultQuery` instead of `query`) for optimal performance.
## Avoiding common pitfalls[](#avoiding-common-pitfalls "Direct link to Avoiding common pitfalls")
Prevent unstable references by defining static props (objects, arrays, functions) outside the component render function. This typically applies to the `fields` array and `onQueryChange` callback. For props that must be created within the component, use `useMemo` or `useCallback` for memoization. Most importantly, avoid defining non-primitive props inline within JSX.
* ✓ DO define variables that will remain unchanged outside the component if possible.
* ✓ DO memoize objects, arrays, and other values that must be created and/or calculated within the component with `useMemo`.
* ✓ DO memoize functions that must be created within the component with `useCallback`.
* ⚠ DO NOT define objects, arrays, or functions inline in the JSX prop declarations.
* This includes subcomponents—see [Custom component as closure](/docs/tips/common-mistakes.md#custom-component-as-closure).
* Inline assignment of primitives like strings, numbers, and booleans is usually not a problem.
## Exceptions[](#exceptions "Direct link to Exceptions")
Certain props use more granular memoization. `QueryBuilder` internally memoizes individual properties of objects passed to these props:
* `controlClassnames`
* `controlElements`
* `translations` (even nested properties are memoized individually for `translations`)
## Examples[](#examples "Direct link to Examples")
### "Bad" example[](#bad-example "Direct link to \"Bad\" example")
These patterns negatively impact `QueryBuilder` performance:
```
function App() {
const { t } = useTranslation(); // (<-- third-party i18n library)
// ⚠ Even though this `useState` call only sets the initial `query` value once, the object
// itself is still created on every render. This doesn't affect the stability of the reference,
// but it's probably a good idea to define the object outside the component anyway.
const [query, setQuery] = useState({ combinator: 'and', rules: [] });
// ❌ This function is not memoized and will get recreated on each render.
const getOperators = (field: Field) => t(defaultOperators);
return (
setQuery(q)}
//
// ❌ Inline definition of an array that doesn't change over time.
fields={[
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
]}
//
// This function is not defined inline in the JSX, but it does not have a stable
// reference since it's recreated on each render (see its declaration above).
getOperators={getOperators}
//
controlElements={{
// ❌ Component function is defined inline and will be recreated during each render.
// This can also cause bugs like "input loses focus after each keystroke."
actionElement: props => {props.label} ,
}}
/>
);
}
```
### "Good" example[](#good-example "Direct link to \"Good\" example")
Use these patterns to optimize `QueryBuilder` performance:
✓
```
// ✅ Fields array that never changes defined outside the component.
const fields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
// ✅ Custom subcomponent defined outside the main component render function.
const MyActionElement = (props: ActionProps) => (
{props.label}
);
// ✅ Default query, which is only access once, defined outside the component.
const defaultQuery: RuleGroupType = { combinator: 'and', rules: [] };
function App() {
const { t } = useTranslation(); // (<-- third-party i18n library)
// ✅ `useState` parameter (the initial value of `query`) defined outside the component.
const [query, setQuery] = useState(defaultQuery);
// ✅ Function defined inside the component memoized with `useCallback`. Since `t`
// _probably_ has a stable reference, this function will rarely, if ever, be recreated.
const getOperators = useCallback((field: Field) => t(defaultOperators), [t]);
return (
);
}
```
---
# Migrating from react-awesome-query-builder
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
This guide covers moving an existing [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB) implementation to React Query Builder (RQB). For a broader feature-level overview, see [Comparison with other libraries](/docs/tips/comparison.md).
Two utilities do most of the work:
* `parseRAQB` converts a saved RAQB query tree to an RQB query object.
* `parseRAQBFields` converts an RAQB `Config` (or just its `fields`) to an RQB `fields` array.
Both are exported from [`@react-querybuilder/migrate-raqb`](https://github.com/react-querybuilder/migrate-raqb), a standalone package outside the main React Query Builder repository. Since migration is generally a one-time task, these utilities are packaged separately to keep them out of your production bundle.
* npm
* Bun
* Yarn
* pnpm
```
npm i @react-querybuilder/migrate-raqb
```
```
bun add @react-querybuilder/migrate-raqb
```
```
yarn add @react-querybuilder/migrate-raqb
```
```
pnpm add @react-querybuilder/migrate-raqb
```
```
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';
```
The package has no runtime dependencies—only a peer dependency on `@react-querybuilder/core` (v8.21.2 or later). It adds no dependency on RAQB or immutable.js.
info
`@react-querybuilder/migrate-raqb` requires React Query Builder v8. If you're migrating from RAQB, migrate straight to [the latest version](/docs/next/tips/migrate-from-raqb)—there's no reason to land on v7 first. Run the conversion under v8, save the resulting queries, and the output is plain JSON that works anywhere.
tip
Once your queries are converted and saved in RQB format, uninstall `@react-querybuilder/migrate-raqb`. Nothing in your application needs it at runtime.
## Concept mapping[](#concept-mapping "Direct link to Concept mapping")
| RAQB | React Query Builder |
| ----------------------------------- | ------------------------------------------------------------------------------------------ |
| `Config` | Discrete props: `fields`, `operators`, `combinators`, `controlElements` |
| `Config.fields` | [`fields`](/docs/tips/managing-fields.md) |
| `Query` + `Builder` render prop | A single [` `](/docs/components/querybuilder.md) element |
| immutable tree + `Utils.getTree()` | Plain JSON query object, used directly |
| `group` node | Rule group (an object with a `rules` array) |
| `properties.conjunction` | `combinator` |
| `properties.not` | `not` |
| `rule` node | Rule (`{ field, operator, value }`) |
| `properties.valueSrc` | `valueSource` |
| `properties.isLocked` | `disabled` (see [`showLockButtons`](/docs/components/querybuilder.md#showlockbuttons)) |
| `rule_group` (`!group` field) | Rule with a [match mode](/docs/tips/subqueries.md) and a nested group as its value |
| `switch_group` / `case_group` | No equivalent — see [Unsupported constructs](#unsupported-constructs) |
| Widgets | [`valueEditorType` / `inputType`](/docs/tips/managing-fields.md) or a custom `valueEditor` |
| `funcs` | [`@react-querybuilder/expr`](/docs/expr.md) |
| `sqlFormat`, `mongodbFormat`, etc. | [`formatQuery`](/docs/utils/export.md) |
| `loadFromJsonLogic`, `loadFromSpel` | [`parseJsonLogic`, `parseSpEL`](/docs/utils/import.md) |
## Rendering the component[](#rendering-the-component "Direct link to Rendering the component")
RAQB splits rendering across ``, a `renderBuilder` callback, and ``, and keeps the query in an immutable.js tree that must be converted before it can be saved. RQB renders a single element and stores the query as plain JSON.
Before (RAQB)
```
import { useCallback, useState } from 'react';
import type {
BuilderProps,
Config,
ImmutableTree,
JsonGroup,
} from '@react-awesome-query-builder/ui';
import { BasicConfig, Builder, Query, Utils as QbUtils } from '@react-awesome-query-builder/ui';
import '@react-awesome-query-builder/ui/css/styles.css';
const config: Config = {
...BasicConfig,
fields: {
price: { label: 'Price', type: 'number' },
color: {
label: 'Color',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'yellow', title: 'Yellow' },
{ value: 'green', title: 'Green' },
],
},
},
},
settings: {
...BasicConfig.settings,
showNot: true,
showLock: true,
maxNesting: 3,
addRuleLabel: 'New rule',
},
};
const initialTree: JsonGroup = { id: QbUtils.uuid(), type: 'group' };
export const App = () => {
const [tree, setTree] = useState(() => QbUtils.loadTree(initialTree));
const onChange = useCallback((immutableTree: ImmutableTree) => {
setTree(immutableTree);
// Convert before saving — the state itself is an immutable.js tree
console.log(QbUtils.getTree(immutableTree));
}, []);
const renderBuilder = useCallback(
(props: BuilderProps) => (
),
[]
);
return (
<>
{QbUtils.sqlFormat(tree, config)}
>
);
};
```
After (RQB)
```
import { useState } from 'react';
import type { Field, RuleGroupType } from 'react-querybuilder';
import { formatQuery, QueryBuilder } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';
const fields: Field[] = [
{ name: 'price', label: 'Price', inputType: 'number' },
{
name: 'color',
label: 'Color',
valueEditorType: 'select',
values: [
{ name: 'yellow', label: 'Yellow' },
{ name: 'green', label: 'Green' },
],
},
];
const initialQuery: RuleGroupType = { combinator: 'and', rules: [] };
export const App = () => {
// Already plain JSON — save it as-is
const [query, setQuery] = useState(initialQuery);
return (
<>
{formatQuery(query, 'sql')}
>
);
};
```
Notable differences:
* Configuration is spread across discrete props instead of one `Config` object, so there's no base config to spread (no `BasicConfig`) and nothing to keep in state alongside the query.
* `onQueryChange` receives the query object directly, so it can be passed straight to a `useState` setter and saved without conversion.
* There is no `renderBuilder` indirection or required wrapper markup. Use [`controlClassnames`](/docs/components/querybuilder.md#controlclassnames) and [`controlElements`](/docs/components/querybuilder.md#controlelements) to customize markup and styling.
* Export functions take the query object, not a tree plus a config.
### Settings[](#settings "Direct link to Settings")
RAQB's `config.settings` correspond to individual ` ` props:
| RAQB setting | RQB prop |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `showNot` | [`showNotToggle`](/docs/components/querybuilder.md#shownottoggle) |
| `showLock` | [`showLockButtons`](/docs/components/querybuilder.md#showlockbuttons) |
| `canReorder` / `canRegroup` | [`enableDragAndDrop`](/docs/components/querybuilder.md#enabledraganddrop) (requires [`@react-querybuilder/dnd`](/docs/dnd.md)) |
| `maxNesting` | [`maxLevels`](/docs/components/querybuilder.md#maxlevels) |
| `maxNumberOfRules` | Return `false` from [`onAddRule`](/docs/components/querybuilder.md#onaddrule) |
| `immutableGroupsMode` and friends | [`disabled`](/docs/components/querybuilder.md#disabled) (all or by path) |
| `clearValueOnChangeField` / `clearValueOnChangeOp` | [`resetOnFieldChange`](/docs/components/querybuilder.md#resetonfieldchange) / [`resetOnOperatorChange`](/docs/components/querybuilder.md#resetonoperatorchange) |
| `setOpOnChangeField` | [`getDefaultOperator`](/docs/components/querybuilder.md#getdefaultoperator), [`autoSelectOperator`](/docs/components/querybuilder.md#autoselectoperator) |
| `defaultField` / `defaultOperator` | [`getDefaultField`](/docs/components/querybuilder.md#getdefaultfield) / [`getDefaultOperator`](/docs/components/querybuilder.md#getdefaultoperator) |
| `defaultConjunction` | First entry of [`combinators`](/docs/components/querybuilder.md#combinators), or the `combinator` of your initial query |
| `addRuleLabel`, `valuePlaceholder`, and other labels | [`translations`](/docs/components/querybuilder.md#translations) |
| `fieldSeparator` | Handled at conversion time by `parseRAQBFields` (field names are flattened) |
## Converting the config[](#converting-the-config "Direct link to Converting the config")
`parseRAQBFields` accepts either a full RAQB `Config` object or just the `fields` map, and returns an RQB `fields` array.
```
import { parseRAQBFields } from '@react-querybuilder/migrate-raqb';
const fields = parseRAQBFields({
price: {
label: 'Price',
type: 'number',
fieldSettings: { min: 0, max: 100 },
},
color: {
label: 'Color',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'yellow', title: 'Yellow' },
{ value: 'green', title: 'Green' },
],
},
},
});
```
Result
```
[
{ "name": "price", "label": "Price", "inputType": "number" },
{
"name": "color",
"label": "Color",
"valueEditorType": "select",
"values": [
{ "name": "yellow", "label": "Yellow" },
{ "name": "green", "label": "Green" }
]
}
]
```
Translation details:
* Nested `!struct` fields are flattened to dot-separated names, matching the field paths stored in RAQB query trees. The separator comes from `settings.fieldSeparator` (default `"."`), or the `fieldSeparator` option.
* `!group` fields become fields with `matchModes` and `subproperties`, which is RQB's [subquery](/docs/tips/subqueries.md) representation.
* `type` maps to `inputType` (`number`, `date`, `time`, `datetime-local`, etc.) or `valueEditorType` (`select`, `multiselect`, `checkbox`).
* `fieldSettings.listValues` and `fieldSettings.treeValues` map to `values`. Tree values are flattened.
* `operators`, `defaultOperator`, `excludeOperators`, `valueSources`, and `defaultValue` are translated to their RQB equivalents.
* Fields marked `hideForSelect` are omitted unless you pass `includeHidden: true`.
* Export-time settings (`fieldName`, `tableName`, `jsonLogicVar`, `isSpelVariable`) and `fieldSettings.validateValue` are not translated.
### `parseRAQBFields` options[](#parseraqbfields-options "Direct link to parseraqbfields-options")
* `fieldSeparator` (`string`): Character used to join nested field names.
* `operatorMap` (`Record`): Additional or overriding operator name mappings. Should match the `operatorMap` passed to `parseRAQB`.
* `includeHidden` (`boolean`): Include fields marked `hideForSelect`. Defaults to `false`.
* `onUnsupported` (`(info: RAQBUnsupportedInfo) => void`): Called for each construct that could not be fully translated.
## Converting queries[](#converting-queries "Direct link to Converting queries")
`parseRAQB` accepts RAQB's **plain-JSON** tree — the output of `Utils.getTree(immutableTree)` — or a JSON string of the same. Immutable.js trees are not accepted; passing one throws an error explaining the fix.
```
// In your RAQB app, save the plain JSON form:
const jsonTree = QbUtils.getTree(immutableTree);
```
```
import { parseRAQB } from '@react-querybuilder/migrate-raqb';
const query = parseRAQB(jsonTree);
```
Example
```
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: { field: 'price', operator: 'greater', value: [10], valueSrc: ['value'] },
},
{
type: 'rule',
properties: { field: 'color', operator: 'select_any_in', value: [['yellow', 'green']] },
},
],
});
```
Result
```
{
"combinator": "and",
"rules": [
{ "field": "price", "operator": ">", "value": 10 },
{ "field": "color", "operator": "in", "value": "yellow,green" }
]
}
```
Both `children1` shapes are supported: the default array form and the keyed-object form produced when `children1AsArray` is `false`.
### Operator mapping[](#operator-mapping "Direct link to Operator mapping")
| RAQB | RQB |
| -------------------------------------------------------------- | --------------------------------------------------------------- |
| `equal` / `not_equal` | `=` / `!=` |
| `less` / `less_or_equal` | `<` / `<=` |
| `greater` / `greater_or_equal` | `>` / `>=` |
| `like` / `not_like` | `contains` / `doesNotContain` |
| `starts_with` / `ends_with` | `beginsWith` / `endsWith` |
| `between` / `not_between` | `between` / `notBetween` |
| `is_null` / `is_not_null` | `null` / `notNull` |
| `is_empty` / `is_not_empty` | `=` / `!=` with an empty string |
| `select_equals` / `select_not_equals` | `=` / `!=` |
| `select_any_in` / `select_not_any_in` | `in` / `notIn` |
| `multiselect_equals` / `multiselect_not_equals` | `=` / `!=` (array value kept) |
| `multiselect_contains` / `multiselect_not_contains` | `contains` / `doesNotContain` |
| `some` / `all` / `none` | Match modes `some` / `all` / `none` |
| `!group` counts `equal` / `greater_or_equal` / `less_or_equal` | Match modes `exactly` / `atLeast` / `atMost` with a `threshold` |
Add to or override this map with the `operatorMap` option.
### `parseRAQB` options[](#parseraqb-options "Direct link to parseraqb-options")
Beyond the standard [import configuration](/docs/utils/import.md#configuration) options (`fields`, `listsAsArrays`, `generateIDs`, `independentCombinators`, etc.), `parseRAQB` accepts:
* `operatorMap` (`Record`): Additional or overriding RAQB-to-RQB operator mappings, merged over the defaults.
* `functionMap` (`Record`): Additional or overriding RAQB-to-`expr` function name mappings, merged over the defaults.
* `funcArgOrder` (`Record`): Explicit argument order per RAQB function name. By default, argument order follows the key order of the serialized function object.
* `relativeDateTimes` (`boolean`): Convert RAQB's built-in date/time functions to relative date/time values instead of expressions. Defaults to `true`. See [Date and time functions](#date-and-time-functions).
* `onUnsupported` (`(info: RAQBUnsupportedInfo) => void`): Called for each construct that could not be converted.
As with other parsers, passing `fields` restricts the output to rules whose fields exist in the list.
## Functions and expressions[](#functions-and-expressions "Direct link to Functions and expressions")
RAQB's `valueSrc: "func"` operands convert to RQB [expressions](/docs/expr.md). Right-hand side functions become `valueSource: "expression"` with an expression object as the value; a function on the *left* side (`fieldSrc: "func"`) becomes the rule's `lhs` property.
Example
```
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'name',
operator: 'equal',
valueSrc: ['func'],
value: [{ func: 'LOWER', args: { str: { value: 'Steve', valueSrc: 'value' } } }],
},
},
],
});
```
Result
```
{
"combinator": "and",
"rules": [
{
"field": "name",
"operator": "=",
"valueSource": "expression",
"value": { "kind": "func", "fn": "lower", "args": [{ "kind": "value", "value": "Steve" }] }
}
]
}
```
caution
Queries containing expressions require [`@react-querybuilder/expr`](/docs/expr.md) to render, validate, or serialize. Wrap your query builder in `QueryBuilderExpressions` and register the export processor as described in the [expressions documentation](/docs/expr.md). Without that package, expression values will not display or export correctly.
RAQB's `LOWER` and `UPPER` map to `expr`'s `lower` and `upper`, and `LINEAR_REGRESSION` is expanded to the equivalent arithmetic expression. Every other RAQB function—apart from the date/time functions described below—is passed through under its original name and reported to `onUnsupported`. To render or export those, either register matching function metadata with `@react-querybuilder/expr` or map them to existing functions with `functionMap`.
## Date and time functions[](#date-and-time-functions "Direct link to Date and time functions")
RAQB's built-in date/time functions don't really compute anything—they describe a date relative to "now". RQB represents that concept as a *value* rather than an expression, so `parseRAQB` converts them to [`@react-querybuilder/datetime`](/docs/datetime.md)'s relative date/time value shape (`{ mode: "relative", anchor, offset, unit }`), which every date/time rule processor serializes symbolically (e.g. `current_timestamp - interval '7 days'`).
| RAQB | RQB value |
| ----------------------------------------- | -------------------------------------------------- |
| `NOW()` | `{ anchor: 'now', offset: 0 }` |
| `TODAY()` / `START_OF_TODAY()` | `{ anchor: 'startOfDay', offset: 0 }` |
| `TRUNCATE_DATETIME(NOW(), dim)` | `{ anchor: 'startOf', offset: 0 }` |
| `RELATIVE_DATE(TIME)(date, op, val, dim)` | `{ anchor: , offset: ±val, unit: dim }` |
Example
```
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'created',
operator: 'between',
valueSrc: ['func', 'func'],
value: [
{
func: 'RELATIVE_DATETIME',
args: { op: { value: 'minus' }, val: { value: 7 }, dim: { value: 'day' } },
},
{ func: 'NOW', args: {} },
],
},
},
],
});
```
Result
```
{
"combinator": "and",
"rules": [
{
"field": "created",
"operator": "between",
"value": [
{ "mode": "relative", "anchor": "now", "offset": -7, "unit": "day" },
{ "mode": "relative", "anchor": "now", "offset": 0, "unit": "day" }
]
}
]
}
```
With `@react-querybuilder/datetime`'s SQL rule processor, that query exports as `"created" between current_timestamp - interval '7 days' and current_timestamp`.
A few RAQB calls fall outside what relative values can express, and are converted to expressions instead (and reported to `onUnsupported`):
* A `"second"` dimension. RQB's smallest relative unit is `minute`.
* Truncation to `hour`, `minute`, or `second`. RQB truncates only to day, week, month, or year.
* Truncation applied *after* an offset, e.g. `TRUNCATE_DATETIME(RELATIVE_DATETIME(NOW(), 'minus', 3, 'day'), 'month')`. RQB applies the anchor first and the offset second, so only the reverse nesting is representable.
Pass `relativeDateTimes: false` to convert date/time functions to expressions like any other function.
## Unsupported constructs[](#unsupported-constructs "Direct link to Unsupported constructs")
`parseRAQB` never throws on unrecognized content (aside from immutable.js input). Anything it can't convert is skipped, and a partial query is always returned. Use `onUnsupported` to log or surface what was dropped.
```
const unsupported: RAQBUnsupportedInfo[] = [];
const query = parseRAQB(jsonTree, {
onUnsupported: info => unsupported.push(info),
});
```
Reported constructs include:
* **`switch_group` / `case_group`** (RAQB's ternary mode). RQB has no equivalent; consider [`@react-querybuilder/rules-engine`](/docs/rules-engine.md) for conditional logic.
* **`proximity`** and its `operatorOptions`, which have no RQB counterpart.
* **`!group` strict-inequality count operators** (`less`, `greater`), since RQB match modes cover only `exactly`, `atLeast`, and `atMost`.
* **Functions with no `@react-querybuilder/expr` equivalent**, which pass through by name.
## Putting it together[](#putting-it-together "Direct link to Putting it together")
```
import { QueryBuilder } from 'react-querybuilder';
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';
import raqbConfig from './raqbConfig';
import savedTree from './savedQuery.json';
const fields = parseRAQBFields(raqbConfig);
const defaultQuery = parseRAQB(savedTree, { fields });
export const App = () => ;
```
## Converting back to RAQB[](#converting-back-to-raqb "Direct link to Converting back to RAQB")
The reverse conversion is useful for a phased migration where both query builders must read the same stored queries. `@react-querybuilder/migrate-raqb` exports `formatRAQB` for this purpose. (There is no `"raqb"` `formatQuery` export format; `formatRAQB` wraps `formatQuery`'s `ruleGroupProcessor` option, which takes precedence over `format`.)
```
import { formatRAQB } from '@react-querybuilder/migrate-raqb';
import { Utils } from '@react-awesome-query-builder/core';
const jsonTree = formatRAQB(query, { fields });
const immutableTree = Utils.checkTree(Utils.loadTree(jsonTree), raqbConfig);
```
Pass `fields` for the most faithful output—several RAQB operators collapse to a single RQB operator, and the field's `valueEditorType` is used to disambiguate them.
`formatRAQBFields`, the inverse of `parseRAQBFields`, is exported from the same package:
```
import { formatRAQBFields } from '@react-querybuilder/migrate-raqb';
const raqbConfig = { ...BasicConfig, fields: formatRAQBFields(fields) };
```
`formatRAQB` accepts all [`formatQuery` options](/docs/utils/export.md) except `format`, `ruleGroupProcessor`, and `fallbackExpression`, plus `fallbackTree` (the tree returned when the query is empty or fails validation) and:
| Option | Default | Description |
| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `raqbOperatorMap` | `{}` | Additional/overriding RQB-to-RAQB operator mappings, keyed by RQB operator name. |
| `raqbFunctionMap` | `{}` | Additional/overriding expression-function-to-RAQB function name mappings. |
| `raqbFuncArgOrder` | `{}` | Argument names per RAQB function name. Defaults cover RAQB's built-ins; others get `arg0`, `arg1`, ... |
| `raqbFieldSeparator` | `"."` | Separator used to qualify sub-query rule fields with their parent `!group` field name. |
| `raqbRelativeDateTimes` | `true` | Convert relative date/time values to RAQB's built-in date/time functions. |
| `raqbValueTypes` | `false` | Emit `valueType` entries inferred from each field's `inputType`/`valueEditorType`. |
The mapping is the inverse of the tables above, with these caveats:
* RQB's `doesNotBeginWith` and `doesNotEndWith` have no RAQB default equivalent (there is no `not_starts_with`/`not_ends_with`); rules using them are omitted unless you supply a `raqbOperatorMap` entry.
* The `"parameter"` value source has no RAQB counterpart, so those rules are omitted.
* `in`/`notIn` map to `select_any_in`/`select_not_any_in`, which RAQB only allows for `select` and `multiselect` fields. On a plain text field RAQB's `checkTree` will reject the rule.
* Field-to-field comparisons are more restricted in RAQB: its `field` widget for the `text` type supports only `equal`, `not_equal`, and `proximity`, so `contains`/`beginsWith`/`endsWith`/`<`/`>` against another field are rejected.
* RAQB clamps inverted ranges, so a `between` rule with `preserveValueOrder` and out-of-order bounds (e.g. `[100, 0]`) becomes `[100, 100]` once loaded.
* `bigint` values are narrowed to `number`, since RAQB trees must be JSON-serializable.
* `endOf*` relative date/time anchors are emitted as plain values, since RAQB's built-in functions only truncate to the *start* of a period.
* RAQB defines no `XOR` conjunction. `xor` combinators are emitted as `"XOR"` rather than silently degraded, so RAQB's `checkTree` will flag them unless a matching custom conjunction is configured.
* RAQB's several operators that collapse to one RQB operator (`equal`/`select_equals`/`multiselect_equals` → `=`) are disambiguated using each field's `valueEditorType`, so pass `fields` for the most faithful output.
* When `validator` results invalidate the entire query, `formatRAQB` returns `fallbackTree` (`raqbFallback`, an empty `AND` group, by default). If you call `formatQuery` directly with `defaultRuleGroupProcessorRAQB`, pass `fallbackExpression: raqbFallback as unknown as string`—`formatQuery` short-circuits *before* the rule group processor runs, and its own default fallback is a SQL string.
To combine RAQB output with other `formatQuery` behavior, or to supply a custom `ruleProcessor`, use `defaultRuleGroupProcessorRAQB` directly. RAQB options then move into `context`:
```
import { formatQuery } from 'react-querybuilder';
import { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';
const jsonTree = formatQuery(query, {
ruleGroupProcessor: defaultRuleGroupProcessorRAQB,
fields,
context: { raqbValueTypes: true },
fallbackExpression: raqbFallback as unknown as string,
});
```
RAQB's last stable release was 6.6.15 in May 2025; these utilities exist to give RAQB users a straightforward path to React Query Builder.
---
# Working with option lists
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
Option list props in React Query Builder—such as [`fields`](/docs/components/querybuilder.md#fields), [`combinators`](/docs/components/querybuilder.md#combinators), and [`operators`](/docs/components/querybuilder.md#operators)—use the [`OptionList`](/docs/typescript.md#option-lists) type. `OptionList` is a union type supporting two array formats: `Option[]` and `OptionGroup[]`. While this design provides flexibility, it introduces complexity when consuming option list props in custom subcomponents.
This guide offers strategies for managing the inherent ambiguity in option list TypeScript types.
info
* `Option` objects have `label`, `name`, and `value` properties, all of which extend `string`.
* `OptionGroup` objects have a `label` property along with an `options` property which is an array of `Option` objects.
This mirrors the `children` prop structure of `` elements, which can contain either `` elements directly or ` ` elements with nested `` lists.
## An incorrect assumption[](#an-incorrect-assumption "Direct link to An incorrect assumption")
You may have found this page after seeing a TypeScript error message similar to this:
```
Property 'name' does not exist on type 'FullOption | OptionGroup>'.
Property 'name' does not exist on type 'OptionGroup>'. ts(2339)
```
This typically occurs when treating option list elements as guaranteed `Option` types. For example, mapping through the list to access the `name` property:
```
const ListAllOptionNames = (props: ValueSelectorProps) => {
return {props.options.map(opt => opt.name).join(', ')}
;
// ^^^^ error
};
```
While it seems logical that an option list passed to `QueryBuilder` as `Option[]` would remain `Option[]` in subcomponents, React Query Builder's type generics only infer the option types within the list, not the list structure itself.
tip
All options in subcomponent props are guaranteed to include both `name` and `value` properties, even if the original `QueryBuilder` prop omitted one of them.
As an example, consider this `fields` array:
```
const fields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
```
When this array is assigned to the [`fields` prop](/docs/components/querybuilder.md#fields), the `fieldSelector` component will receive the same array but with each option object augmented with a `value` property equivalent to the original `name`:
```
const MyFieldSelector = (props: FieldSelectorProps) => {
console.log(props.options); // =>
// [
// { name: 'firstName', value: 'firstName', label: 'First Name' },
// { name: 'lastName', value: 'lastName', label: 'Last Name' }],
// ]
return ;
};
const App = () => (
);
```
**If `name` and `value` differ for a given option, `value` takes precedence.**
## Workarounds[](#workarounds "Direct link to Workarounds")
Several approaches can handle this ambiguity. One option is casting option list props to `Option[]` using the `as` keyword. While this may work if your option list is definitely `Option[]`, the `as` keyword only suppresses TypeScript errors without affecting runtime behavior. This essentially misleads the TypeScript compiler and prevents proper type checking, making it an unrecommended approach.
```
const MyComponent(props: ValueSelectorProps) => {
return {(props.options as Option[]).map(opt => opt.name).join(', ')}
;
// ^^^^^^^^^^^ avoids TypeScript error; may have issues during execution
};
```
info
All [default option lists](/docs/utils/misc.md#defaults) (`defaultCombinators`, `defaultOperators`, etc.) are type `Option[]`.
A better solution uses the [`isOptionGroupArray`](#isoptiongrouparray) type guard to determine the option list type. This approach avoids the deception of type casting while enabling different behaviors for `Option[]` versus `OptionGroup[]` arrays.
```
const MyComponent(props: ValueSelectorProps) => {
if (isOptionGroupArray(props.options)) {
return {props.options.flatMap(og => og.options).map(opt => opt.name).join(', ')}
;
}
return {(props.options).map(opt => opt.name).join(', ')}
;
};
```
## Utilities[](#utilities "Direct link to Utilities")
Several utility functions simplify working with option list props without requiring type guards or `as` casting.
### `getOption`[](#getoption "Direct link to getoption")
```
function getOption(arr: OptionList, identifier: string): Option;
```
Retrieves the complete option object from an option list using the given identifier (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats.
#### Examples[](#examples "Direct link to Examples")
```
getOption(
[
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
],
'lastName'
);
// => { name: 'lastName', label: 'Last Name' }
getOption(
[
{ label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },
{ label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },
],
'lastName'
);
// => { name: 'lastName', label: 'Last Name' }
```
### `getFirstOption`[](#getfirstoption "Direct link to getfirstoption")
```
function getFirstOption(arr: OptionList): Option;
```
Returns the identifier value (`name` or `value`) of the first `Option` in the list, supporting both `Option[]` and `OptionGroup[]` formats.
`QueryBuilder` uses this function to establish default values for option lists in new rules and groups when no other default determination method is available.
#### Examples[](#examples-1 "Direct link to Examples")
```
getFirstOption([
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
]);
// => 'firstName'
getFirstOption([
{ label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },
{ label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },
]);
// => 'firstName'
```
### `toOptions`[](#tooptions "Direct link to tooptions")
```
function toOptions(arr: OptionList): ReactElement;
```
Creates `` elements for `Option` arrays or ` ` elements for `OptionGroup` arrays. Designed for use as the `children` prop of `` elements, as implemented in [`ValueSelector`](/docs/components/valueselector.md).
info
Some of the [compatibility packages](/docs/compat.md) implement their own `toOptions` method that generates "option" elements appropriate for their respective style library.
#### Usage[](#usage "Direct link to Usage")
```
const MyComponent(props: ValueSelectorProps) => {
return (
props.handleOnChange(e.target.value)}>
{toOptions(props.options)}
)
}
```
Examples
#### `Option[]` example[](#option-example "Direct link to option-example")
```
toOptions([
{ value: 'firstName', label: 'First Name' },
{ value: 'lastName', label: 'Last Name' },
]);
// yields (approximately):
[
First Name
,
Last Name
,
];
```
#### `OptionGroup[]` example[](#optiongroup-example "Direct link to optiongroup-example")
```
toOptions([
{ label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },
{ label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },
]);
// yields (approximately):
[
First Name
,
Last Name
,
];
```
### `toFlatOptionArray`[](#toflatoptionarray "Direct link to toflatoptionarray")
```
function toFlatOptionArray(arr: any): boolean;
```
Converts `OptionGroup` arrays to flattened `Option` arrays using [`Array.prototype.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) when grouped structures are unsuitable, while leaving `Option` arrays unchanged. The result is deduplicated using [`uniqByIdentifier`](#uniqbyidentifier).
### `isOptionGroupArray`[](#isoptiongrouparray "Direct link to isoptiongrouparray")
```
function isOptionGroupArray(arr: any): boolean;
```
A type guard that distinguishes between the two array types in `OptionList`. When this returns `true`, the array is `OptionGroup[]`, meaning actual options are nested within each group's `options` property.
Examples
```
isOptionGroupArray([
{ value: 'firstName', label: 'First Name' },
{ value: 'lastName', label: 'Last Name' },
]);
// => false
isOptionGroupArray([
{ label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },
{ label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },
]);
// => true
```
### `uniqOptList`[](#uniqoptlist "Direct link to uniqoptlist")
```
function uniqOptList(arr: any): boolean;
```
Removes duplicate options from an `OptionList` by comparing identifier properties (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats.
### `uniqByIdentifier`[](#uniqbyidentifier "Direct link to uniqbyidentifier")
```
function uniqByIdentifier(arr: any): boolean;
```
Removes duplicate options from an `Option` array by comparing identifier properties (`name` or `value`).
## String option lists[](#string-option-lists "Direct link to String option lists")
Option list props also accept `string[]`. Each string becomes an option with `name`, `value`, and `label` all set to that string.
```
// These are equivalent:
```
Strings can be mixed with option objects:
```
```
This shorthand is convenient for simple option lists but lacks the flexibility of full option objects, such as custom labels.
---
# External parameter manager
This example wires an external "parameter manager" component to a query builder. The manager owns a user-supplied list of parameters—each with a **name** (like `p1`), an optional **label** (like `Param 1`), and an optional **value**. Its state is passed as the [`getParameters`](/docs/components/querybuilder.md#getparameters) prop, so rules whose [`valueSource`](/docs/components/valueeditor.md#the-parameter-value-source) is `"parameter"` present the managed names in their value editors.
The rendered SQL comes from the [`"parameterized_named"`](/docs/utils/export.md#named-parameters) `formatQuery` export, which registers each referenced parameter as a `params` key with a `null` placeholder. The example merges the manager's values over those placeholders, leaving `null` for parameters without a supplied value.
tip
Parameter names are stored **without** a prefix (e.g. `p1`, not `:p1`); `formatQuery` adds the dialect-appropriate prefix. The merge below strips any prefix from the `params` keys before matching them to the manager's entries.
---
# Path concepts
While the `id` property uniquely identifies rules and groups, it doesn't indicate their position within the query hierarchy. React Query Builder uses a "path" concept to locate and update query objects based on their structural position.
The `path` property is an integer array that uniquely identifies each rule and group's location within a query. The root query group has a `path` of `[]`, while all nested rules and groups have paths reflecting their position within their ancestor groups' `rules` arrays.
Each object's `path` equals its parent group's `path` plus its index within the parent's `rules` array: `path = [...parentPath, index]`.
This example query shows the `path` for each rule and group with explanatory comments:
```
// [] (the root group)
const query: RuleGroupType = {
combinator: 'and',
rules: [
// [0] (the first, aka zeroth, element in the root rules array)
{ field: 'f1', operator: '=', value: 'v1' },
// [1] (the second element in the root rules array is a sub-group)
{
combinator: 'or',
rules: [
// [1, 0] (the first element within the rules array
// of the group occupying the second position
// in the root rules array)
{ field: 'f2', operator: '=', value: 'v2' },
// [1, 1] (the second element within the rules array
// of the group occupying the second position
// in the root rules array)
{ field: 'f3', operator: '=', value: 'v3' },
],
},
],
};
```
The first rule has `path` `[0]` (index 0 in the root `rules` array). The sub-group has `path` `[1]` (index 1 in the root array). Child rules within that group have paths starting with `1` (their parent's path) followed by their own indices.
## Finding a `path`[](#finding-a-path "Direct link to finding-a-path")
The [`findPath`](/docs/utils/misc.md#findpath) function locates specific rules or groups for examination or updates. Using the query above:
```
findPath([1, 0], query);
```
Returns:
```
{ "field": "f2", "operator": "=", "value": "v2" }
```
## Example[](#example "Direct link to Example")
While most scenarios don't require direct `path` interaction, it's useful when custom components need to access other query parts.
For example, if a custom value editor needs sibling rule values, you can retrieve the full query object using the [`useQueryBuilderQuery`](/docs/utils/hooks.md#usequerybuilderquery) hook (which connects to React Query Builder's Redux store), then find sibling rules using `getParentPath` and `findPath`.
info
Before version 7, custom components only received props for their specific rule or group. Additional data (like the root query) required the [`context` prop](/docs/components/querybuilder.md#context).
The `context` prop remains available, but query retrieval no longer requires it.
---
# Customization showcase
These examples showcase React Query Builder's extensive customization capabilities. Submit a pull request to share your interesting or unusual implementations!
### Justified layout[](#justified-layout "Direct link to Justified layout")
These CSS rules push "clone", "lock", or "remove" buttons to the right edge, creating a justified appearance. [The demo has an option to enable this technique](/demo#justifiedLayout=true).
tip
These styles are now part of the default stylesheet.
Apply them by adding `queryBuilder-justified` to the `className` prop on your ` ` component or an ancestor element.
CSS
```
.queryBuilder .ruleGroup-addGroup + button.ruleGroup-cloneGroup,
.queryBuilder .ruleGroup-addGroup + button.ruleGroup-lock,
.queryBuilder .ruleGroup-addGroup + button.ruleGroup-remove,
.queryBuilder .rule-operators + button.rule-cloneRule,
.queryBuilder .rule-operators + button.rule-lock,
.queryBuilder .rule-operators + button.rule-remove,
.queryBuilder .rule-value + button.rule-cloneRule,
.queryBuilder .rule-value + button.rule-lock,
.queryBuilder .rule-value + button.rule-remove {
margin-left: auto !important;
}
```
https\://example.com
AND (and)+ Rule+ Group🔓
First name (firstName)begins with (beginsWith)Stev⧉🔓⨯
OR (or)+ Rule+ Group⧉🔓⨯
Last name (lastName)=Vai⧉🔓⨯
Last name (lastName)=Vaughan⧉🔓⨯
Last name (lastName)=Martin⧉🔓⨯
First name (firstName)is null (null)⧉🔓⨯
### Inline combinator selectors[](#inline-combinator-selectors "Direct link to Inline combinator selectors")
Positions combinator selectors to the right of their preceding rules or groups.
note
These examples use [independent combinators](/docs/components/querybuilder.md#independent-combinators), but the same styles work with [`showCombinatorsBetweenRules`](/docs/components/querybuilder.md#showcombinatorsbetweenrules).
CSS
```
.ruleGroup-body {
/* Override the default flex layout */
display: grid !important;
/* Allow the left-hand column (the rule/subgroup) to expand as needed */
/* Collapse the right-hand column (the combinator) to the width of the content */
grid-template-columns: auto min-content;
/* Keep the combinator aligned with the bottom of the rule/subgroup */
align-items: end;
}
```
https\://example.com
\+ Rule+ Group
First name (firstName)begins with (beginsWith)Stev⨯
AND (and)
\+ Rule+ Group⨯
Last name (lastName)=Vai⨯
OR (or)
Last name (lastName)=Vaughan⨯
OR (or)
Last name (lastName)=Martin⨯
OR (or)
First name (firstName)is null (null)⨯
Alternatively, position combinators to the left of their following rules or groups:
CSS
```
.ruleGroup-body {
/* Override the default flex layout */
display: grid !important;
/* Allow the right-hand column (the rule/subgroup) to expand as needed */
/* Collapse the left-hand column (the combinator) to the width of the content */
grid-template-columns: min-content auto;
/* Keep the combinator aligned with the top of the rule/subgroup */
align-items: start;
}
/* Indent the first rule/subgroup since it has no preceding combinator */
.ruleGroup-body > .rule:first-child:not(:only-child),
.ruleGroup-body > .ruleGroup:first-child:not(:only-child) {
grid-column-start: 2;
}
```
https\://example.com
\+ Rule+ Group
First name (firstName)begins with (beginsWith)Stev⨯
AND (and)
\+ Rule+ Group⨯
Last name (lastName)=Vai⨯
OR (or)
Last name (lastName)=Vaughan⨯
OR (or)
Last name (lastName)=Martin⨯
OR (or)
First name (firstName)is null (null)⨯
## Disjunctive normal form[](#disjunctive-normal-form "Direct link to Disjunctive normal form")
This example implements [disjunctive normal form (DNF)](https://en.wikipedia.org/wiki/Disjunctive_normal_form) by restricting root groups to "or", subgroups to "and", and limiting nesting to one level. Additional customizations include:
**CSS customizations:**
* Swap header and body order within groups
* Hide top-level "add rule" button (prevents rules in root group)
* Hide subgroup "add group" buttons (prevents deep nesting)
* Display subgroups horizontally
* Stack rule elements vertically (accommodates horizontal layout)
* Hide "remove group" buttons (would appear oddly positioned)
**Component/prop customizations:**
* Display combinators as static text (DNF is always "OR of ANDs")
* Remove groups when their last rule is deleted
* Auto-add default rules to new groups (enables immediate removal)
tip
This example demonstrates techniques detailed in the [arbitrary updates guide](/docs/tips/arbitrary-updates.md) and [hooks documentation](/docs/utils/hooks.md#usequerybuilderquery).
> *You may want to hide the left-hand sidebar (click `<<` at the bottom) to have a wider view of this example.*
---
# Subqueries and nested data
Modern applications frequently handle complex, nested data structures such as object arrays or hierarchical JSON. React Query Builder's **subquery** feature enables sophisticated queries against these nested structures through intuitive match modes like "all," "some," or "none."
https\://example.com
AND (and)+ Rule+ Group
Nested String Array (nestedStringArray)at most (atMost)2
AND (and)+ Rule+ Group
⨯
containsabc⨯
Nested Number Array (nestedNumberArray)Several (some)
AND (and)+ Rule+ Group
⨯
between1214⨯
Nested Object Array (nestedObjectArray)all
AND (and)+ Rule+ Group
⨯
First Name (firstName)begins with (beginsWith)S⨯
Last Name (lastName)does not end with (doesNotEndWith)s⨯
## Configuring subqueries[](#configuring-subqueries "Direct link to Configuring subqueries")
Enable subqueries for a field by adding a `matchModes` property to its field definition. This property determines available match modes and their labels. You can also control subquery behavior globally using the `getMatchModes` and `getSubQueryBuilderProps` props.
### Match modes configuration[](#match-modes-configuration "Direct link to Match modes configuration")
The `matchModes` property accepts several formats:
* **`true`** - Enables all available match modes with default labels
* **`MatchMode[]`** - Array of match mode names (e.g., `['all', 'some', 'none']`)
* **`Option[]`** - Array of objects with custom labels (e.g., `[{ name: 'all', label: 'Every' }]`)
```
const fields: Field[] = [
{
name: 'nestedStringArray',
label: 'Nested String Array',
// Enable all match modes with default labels
matchModes: true,
},
{
name: 'nestedNumberArray',
label: 'Nested Number Array',
// Enable specific match modes with custom labels
matchModes: [
{ name: 'all', label: 'Every' },
{ name: 'none', label: 'Not one' },
{ name: 'some', label: 'Several' },
],
},
{
name: 'nestedObjectArray',
label: 'Nested Object Array',
// Enable specific match modes with default labels
matchModes: ['all', 'none', 'some'],
// Define properties of objects in the nested array
subproperties: [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
],
},
];
```
### Dynamic match mode configuration[](#dynamic-match-mode-configuration "Direct link to Dynamic match mode configuration")
Configure match modes dynamically using the `getMatchModes` prop at the query builder level. This function executes for each field, enabling conditional match mode configuration based on field properties:
```
const getMatchModes = (field: string, misc: { fieldData: Field }) => {
// Return true to enable all match modes for any field
if (field === 'flexibleArray') return true;
// Return specific match modes based on field type
if (misc.fieldData.datatype === 'array') {
return ['all', 'some', 'none'];
}
// Return false or undefined to disable subqueries for this field
return false;
};
;
```
### Customizing subquery builder props[](#customizing-subquery-builder-props "Direct link to Customizing subquery builder props")
The `getSubQueryBuilderProps` prop customizes individual subquery builder configurations. Use this to provide different field sets, operators, or other settings for nested queries:
```
const getSubQueryBuilderProps = (field: string, misc: { fieldData: Field }) => {
// Return props that should override the parent query builder's configuration
if (field === 'nestedObjectArray') {
return {
fields: misc.fieldData.subproperties || [],
operators: [
{ name: '=', label: 'equals' },
{ name: 'contains', label: 'contains' },
{ name: 'beginsWith', label: 'begins with' },
],
// Disable certain features for subqueries
showCloneButtons: false,
showLockButtons: false,
};
}
// For primitive arrays, don't show field selector
if (field === 'nestedStringArray') {
return {
fields: [{ name: '', label: '' }],
autoSelectField: true,
};
}
return {};
};
;
```
**Note:** Props like `query`, `onQueryChange`, and `enableDragAndDrop` are automatically managed and cannot be overridden for subquery builders.
### Available match modes[](#available-match-modes "Direct link to Available match modes")
React Query Builder supports six match modes:
| Mode | Type | Description | Requires threshold |
| --------- | --------- | --------------------------------------------------- | ------------------ |
| `all` | Unary | Every item in the array matches the subquery | No |
| `some` | Unary | At least one item in the array matches the subquery | No |
| `none` | Unary | No items in the array match the subquery | No |
| `atLeast` | Threshold | At least N items match the subquery | Yes |
| `atMost` | Threshold | At most N items match the subquery | Yes |
| `exactly` | Threshold | Exactly N items match the subquery | Yes |
## Working with object properties[](#working-with-object-properties "Direct link to Working with object properties")
For object arrays (not primitive arrays), use the `subproperties` configuration to specify which object properties are available in subqueries. This functions identically to the main `fields` prop.
When `subproperties` is undefined, subquery rules don't render a field selector, and the `field` property should remain an empty string.
## Query structure[](#query-structure "Direct link to Query structure")
Subqueries store as nested `RuleGroupType` objects in the rule's `value` property. The `match` property contains the mode and optional threshold:
```
const exampleQuery: RuleGroupType = {
combinator: 'and',
rules: [
{
field: 'nestedStringArray',
operator: '=', // Ignored when match is present
match: { mode: 'atMost', threshold: 2 },
value: {
combinator: 'and',
rules: [{ field: '', operator: 'contains', value: 'abc' }],
},
},
{
field: 'nestedObjectArray',
operator: '=',
match: { mode: 'all' },
value: {
combinator: 'and',
rules: [
{ field: 'firstName', operator: 'beginsWith', value: 'S' },
{ field: 'lastName', operator: 'doesNotEndWith', value: 's' },
],
},
},
],
};
```
info
When a rule has a valid `match` property, the `operator` property is ignored. The match mode determines subquery evaluation logic.
## Export format support[](#export-format-support "Direct link to Export format support")
Export format support for subqueries varies by implementation:
| Support level | Formats |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Full | "jsonlogic"[1](#user-content-fn-1), "jsonata", "cel", "spel", "natural\_language", ~~"mongodb"~~, "mongodb\_query" |
| Partial | "sql"[2](#user-content-fn-2), "parameterized"[2](#user-content-fn-2), "drizzle"[2](#user-content-fn-2), "elasticsearch"[3](#user-content-fn-3) |
| None | "parameterized\_named"[4](#user-content-fn-4), "prisma", "sequelize", "tanstack\_db", "ldap" |
caution
In unsupported formats, rules with valid `match` properties are treated as invalid and ignored during export.
## Footnotes[](#footnote-label "Direct link to Footnotes")
1. JsonLogic was the original inspiration for this feature. [↩](#user-content-fnref-1)
2. SQL-based formats only support primitive value arrays when `preset` is "postgresql". Other RDBMS platforms lack support for nested tables or have overly complex implementations. [↩](#user-content-fnref-2) [↩2](#user-content-fnref-2-2) [↩3](#user-content-fnref-2-3)
3. ElasticSearch nested queries only support `"some"` and `"none"` match modes. [↩](#user-content-fnref-3)
4. PostgreSQL does not support named parameters [↩](#user-content-fnref-4)
---
# Undo / redo
React Query Builder can record a history of query changes and step backward and forward through it. History is opt-in and lives in a separate entry point, so applications that don't use it pay nothing for it.
* npm
* Bun
* Yarn
* pnpm
```
npm install react-querybuilder
```
```
bun add react-querybuilder
```
```
yarn add react-querybuilder
```
```
pnpm add react-querybuilder
```
```
import { QueryBuilder } from 'react-querybuilder';
import { QueryBuilderHistory } from 'react-querybuilder/history';
const App = () => (
);
```
That's the whole setup. `QueryBuilderHistory` records changes for descendant query builders and defaults their [`showUndoRedo`](/docs/components/querybuilder.md#showundoredo) prop to `true`, which displays undo/redo controls at the end of the outermost group's header. Pass `showUndoRedo={false}` to a particular `QueryBuilder`, or to `QueryBuilderHistory` itself, to opt out while still recording history.
tip
Unlike most undo/redo recipes, this works in [uncontrolled mode](/docs/components/querybuilder.md#defaultquery)—you don't need to lift the query into your own state.
## How it works[](#how-it-works "Direct link to How it works")
Every query change—no matter which button, editor, or drag-and-drop interaction caused it—flows through a single action in React Query Builder's internal Redux store. The history recorder listens for that action, so recording requires no instrumentation of individual controls.
Because query updates use [Immer](https://immerjs.github.io/immer/), snapshots share structure with one another: an undo entry is a reference to a previous query object, not a deep copy, so keeping many of them is inexpensive. Snapshots also retain rule `id`s, which means undo preserves React keys and drag-and-drop state.
## Options[](#options "Direct link to Options")
Both options can be set on `QueryBuilderHistory`:
```
```
### `maxHistory`[](#maxhistory "Direct link to maxhistory")
`number` (default `50`)
Maximum number of undo steps to retain. Older entries are discarded.
### `coalesceMs`[](#coalescems "Direct link to coalescems")
`number` (default `500`)
Consecutive changes to the same property of the same rule within this many milliseconds are merged into a single undo step. Typing `hello` into a value editor therefore produces one undo entry that restores the original value, rather than five entries that remove one character at a time.
Set to `0` to record every change separately.
Structural changes—adding, removing, moving, or reordering rules and groups—never merge with each other, so each one is always its own undo step.
note
Coalescing relies on the structural sharing that Immer guarantees: nodes that didn't change keep their identity. If your application rebuilds the query wholesale between changes—deep-cloning it, round-tripping it through JSON, or re-parsing it from a string—then no two consecutive queries share any identity and every change becomes its own history entry. Undo/redo still works correctly; it's just less granular.
## Custom controls[](#custom-controls "Direct link to Custom controls")
`useQueryBuilderHistory` gives you the same controls that power the default buttons. It takes a query builder's [`qbId`](/docs/components/querybuilder.md#qbid) and can be used anywhere—including *outside* the query builder's component tree, which is what makes external toolbars possible.
```
import { QueryBuilder } from 'react-querybuilder';
import { QueryBuilderHistory, useQueryBuilderHistory } from 'react-querybuilder/history';
const Toolbar = ({ qbId }: { qbId: string }) => {
const { undo, redo, clear, canUndo, canRedo, past, future } = useQueryBuilderHistory(qbId);
return (
Undo
Redo
Clear history
{past.length} undo / {future.length} redo
);
};
const App = () => (
);
```
Note the explicit `qbId` on both components. Without it, the query builder generates an identifier internally that outside code has no way to discover.
### `useQueryBuilderHistory`[](#usequerybuilderhistory "Direct link to usequerybuilderhistory")
```
useQueryBuilderHistory(qbId: string, options?: { maxHistory?: number; coalesceMs?: number })
```
| Property | Type | Description |
| --------- | -------------------- | ------------------------------------------------------------- |
| `undo` | `() => void` | Restores the previous query. No-op when `canUndo` is `false`. |
| `redo` | `() => void` | Restores the most recently undone query. |
| `clear` | `() => void` | Discards all history without changing the current query. |
| `canUndo` | `boolean` | Whether there is anything to undo. |
| `canRedo` | `boolean` | Whether there is anything to redo. |
| `past` | `RuleGroupTypeAny[]` | Previous queries, oldest first. |
| `future` | `RuleGroupTypeAny[]` | Undone queries, newest first. |
Options passed here override those from the nearest `QueryBuilderHistory` ancestor.
info
Rendering this hook is what opts a query builder in to history recording. A query builder that neither uses the hook nor renders undo/redo controls retains no history at all, which is what keeps the feature free for everyone else.
## Keyboard shortcuts[](#keyboard-shortcuts "Direct link to Keyboard shortcuts")
Keyboard shortcuts aren't built in, because a library-level listener would hijack the browser's native text undo inside the query builder's own inputs and could conflict with your application's shortcuts. Wiring them up yourself is straightforward:
```
import { useEffect } from 'react';
import { useQueryBuilderHistory } from 'react-querybuilder/history';
const useUndoRedoShortcuts = (qbId: string) => {
const { undo, redo } = useQueryBuilderHistory(qbId);
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'z') return;
// Leave text inputs alone so native undo keeps working while editing a value
const target = event.target as HTMLElement | null;
if (target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA') return;
event.preventDefault();
if (event.shiftKey) {
redo();
} else {
undo();
}
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [redo, undo]);
};
```
## Multiple query builders[](#multiple-query-builders "Direct link to Multiple query builders")
Each query builder has its own independent history, keyed by `qbId`. Undoing in one has no effect on any other.
## History lifetime[](#history-lifetime "Direct link to History lifetime")
History belongs to the query builder, not to whichever component displays it. Several consumers can share one `qbId`—the built-in undo/redo buttons alongside an external toolbar, for example—and one of them unmounting leaves the history intact for the rest.
When the last query builder using a given `qbId` unmounts, both its query and its history are discarded. To preserve them—so that remounting a query builder with the same `qbId` resumes where it left off—use [`preserveQueryStateOnUnmount`](/docs/components/querybuilder.md#preservequerystateonunmount), which retains the history along with the query.
## Alternative: controlled mode[](#alternative-controlled-mode "Direct link to Alternative: controlled mode")
If your application already keeps the query in its own state, you can implement undo/redo without this entry point by maintaining `past`/`present`/`future` stacks and snapshotting the previous query on each `onQueryChange`. Because query update functions return the *same* object reference when an operation is a no-op, you can skip recording those by comparing references.
This approach requires [controlled mode](/docs/components/querybuilder.md#query) and doesn't benefit from coalescing, so `QueryBuilderHistory` is generally the better option unless you have lifted the query into your own state for other reasons.
---
# TypeScript reference
Here are the key [TypeScript](https://www.typescriptlang.org/) types and interfaces used throughout React Query Builder. Even if you're using JavaScript, this reference helps you understand the expected shape of props and function parameters.
note
Some definitions below have been simplified from their actual implementations for legibility and ease of comprehension.
The **[API documentation](/api)** is generated from source code and provides complete type definitions with repository links for detailed reference.
## Fields[](#fields "Direct link to Fields")
```
interface Field {
id?: string; // The field identifier (if not provided, `name` will be used)
name: string; // The field name (REQUIRED)
label: string; // The field label (REQUIRED)
operators?: OptionList[]; // Array of operators (if not provided, `getOperators()` will be used)
valueEditorType?: ValueEditorType; // Value editor type for this field (if not provided, `getValueEditorType()` will be used)
inputType?: string | null; // @type attribute for the rendered by ValueEditor, e.g. 'text', 'number', or 'date' (if not provided, `getInputType()` will be used)
values?: OptionList; // Array of value options, applicable when valueEditorType is 'select', 'radio', or 'multiselect' (if not provided, `getValues()` will be used)
defaultOperator?: string; // Default operator for this field (if not provided, `getDefaultOperator()` will be used)
defaultValue?: any; // Default value for this field (if not provided, `getDefaultValue()` will be used)
placeholder?: string; // Placeholder text for the value editor when this field is selected
validator?: RuleValidator; // Validation function for rules that specify this field
valueSources?: ValueSources | ((operator: string) => ValueSources); // List of allowed value sources (must contain "value", "field", or both)
comparator?: string | ((f: Field, operator: string) => boolean); // Determines which (other) fields to include in the list when the rule's valueSource is "field"
className?: Classname; // Assigned to rules where this field is selected
separator?: ReactNode; // Rendered between multiple value editors, e.g. when the operator is "between" or "notBetween"
}
```
Notes:
* `Field` extends the `Option` interface, described [below](#option-lists).
* More information on `valueEditorType` is available [below](#value-editor) and in the [`ValueEditor` component documentation](/docs/components/valueeditor.md).
## Rules and groups[](#rules-and-groups "Direct link to Rules and groups")
*For more information about the `Path` type, see [Path concepts](/docs/tips/path.md).*
```
type Path = number[];
type RuleType = {
path?: Path;
id?: string;
disabled?: boolean;
field: string;
operator: string;
value: any;
valueSource?: ValueSource;
};
type RuleGroupType = {
path?: Path;
id?: string;
disabled?: boolean;
combinator: string;
rules: (RuleType | RuleGroupType)[];
not?: boolean;
};
type RuleGroupTypeIC = {
path?: Path;
id?: string;
disabled?: boolean;
rules: (RuleType | RuleGroupTypeIC | string)[]; // see note below
not?: boolean;
};
type RuleGroupTypeAny = RuleGroupType | RuleGroupTypeIC;
type RuleOrGroupArray = RuleGroupType['rules'] | RuleGroupTypeIC['rules'];
```
info
`RuleGroupTypeIC` (see [independent combinators](/docs/components/querybuilder.md#independent-combinators)) is *greatly* simplified here for brevity. In reality, TypeScript enforces the following conditions:
* All even indexes in the `rules` array must be of type `RuleType` or `RuleGroupTypeIC`
* All odd indexes in the `rules` array must be of type `string`
* The array length must be zero or an odd number; therefore, the first and last elements of the `rules` array must be of type `RuleType` or `RuleGroupTypeIC`
For example, the following would be invalid because the first element in the `rules` array (the `0`th index, which should be `RuleType | RuleGroupTypeIC`) is a `string`, and the second element (the `1`st index, which should be a `string`) is a `RuleType`. Also, the length is an even number (2).
```
const ruleGroupInvalid: RuleGroupTypeIC = {
rules: ['and', { field: 'firstName', operator: '=', value: 'Steve' }],
};
```
We can resolve this by either removing the first element or inserting another rule before it:
```
const ruleGroupValid1: RuleGroupTypeIC = {
rules: [{ field: 'firstName', operator: '=', value: 'Steve' }],
};
// OR
const ruleGroupValid2: RuleGroupTypeIC = {
rules: [
{ field: 'lastName', operator: '=', value: 'Vai' },
'and',
{ field: 'firstName', operator: '=', value: 'Steve' },
],
};
```
## Export[](#export "Direct link to Export")
```
type ExportFormat =
| 'json'
| 'sql'
| 'json_without_ids'
| 'parameterized'
| 'parameterized_named'
| 'mongodb'
| 'mongodb_query'
| 'cel'
| 'jsonlogic'
| 'spel'
| 'elasticsearch'
| 'jsonata'
| 'natural_language'
| 'ldap'
| 'drizzle'
| 'tanstack_db'
| 'prisma'
| 'sequelize'
| 'cypher'
| 'gql'
| 'sparql'
| 'gremlin'
| 'diagnostics';
interface FormatQueryOptions {
format?: ExportFormat;
valueProcessor?: ValueProcessor;
ruleProcessor?: RuleProcessor;
quoteFieldNamesWith?: string | [string, string];
fieldIdentifierSeparator?: string;
quoteValuesWith?: string;
validator?: QueryValidator;
fields?: OptionList;
getOperators?: (
field: string,
misc: { fieldData: FullField }
) => FlexibleOptionList | null;
fallbackExpression?: string;
paramPrefix?: string;
paramsKeepPrefix?: boolean;
numberedParams?: boolean;
parseNumbers?: ParseNumberMethod;
placeholderFieldName?: string;
placeholderOperatorName?: string;
concatOperator?: string;
preset?: SQLPreset;
context?: Record;
}
type RuleProcessor = (rule: RuleType, options?: ValueProcessorOptions) => any;
type ValueProcessor = (field: string, operator: string, value: any) => string;
interface ValueProcessorOptions extends FormatQueryOptions {
escapeQuotes?: boolean;
fieldData?: Field;
fieldParamNames?: Record;
getNextNamedParam?: (field: string) => string;
wrapValueWith?: [string, string];
}
interface ParameterizedSQL {
sql: string;
params: any[];
}
interface ParameterizedNamedSQL {
sql: string;
params: { [p: string]: any };
}
type ParseNumberMethod = boolean | 'enhanced' | 'native' | 'strict';
```
## Import[](#import "Direct link to Import")
```
interface ParserCommonOptions {
fields?: OptionList[] | Record;
getValueSources?: (field: string, operator: string) => ValueSources;
listsAsArrays?: boolean;
independentCombinators?: boolean;
}
interface ParseSQLOptions extends ParserCommonOptions {
paramPrefix?: string;
params?: any[] | Record;
}
interface ParseCELOptions extends ParserCommonOptions {
customExpressionHandler?: (expr: CELExpression) => RuleType | RuleGroupType | null;
}
type ParseSpELOptions = ParserCommonOptions;
type ParseJsonLogicOptions = ParserCommonOptions;
interface ParseMongoDbOptions extends ParserCommonOptions {
preventOperatorNegation?: boolean;
additionalOperators?: Record<
string,
(operator: string, value: any, otherOptions: ParserCommonOptions) => RuleType | RuleGroupType
>;
}
```
## Validation[](#validation "Direct link to Validation")
```
interface ValidationResult {
valid: boolean;
reasons?: any[];
}
interface ValidationMap {
[id: string]: boolean | ValidationResult;
}
type QueryValidator = (query: RuleGroupTypeAny) => boolean | ValidationMap;
type RuleValidator = (rule: RuleType) => boolean | ValidationResult;
```
## Option lists[](#option-lists "Direct link to Option lists")
*As of version 7, options and lists can use `name` **or** `value` as the item identifier. Both `name` and `value` are passed down to subcomponents, so `name` is available even if `value` is used in props and vice versa. `name` is used in the documentation for brevity and backwards compatibility. [Click here for more information](/docs/tips/option-lists.md).*
```
interface Option {
name: string;
label: string;
[x: string]: any;
}
interface OptionGroup {
label: string;
options: Option[];
}
type OptionList = Option[] | OptionGroup[];
interface Combinator extends Option {
className?: Classname; // Assigned to groups where this combinator is selected
}
interface Operator extends Option {
arity?: number | 'unary' | 'binary' | 'ternary';
className?: Classname; // Assigned to rules where this operator is selected
}
```
## Value editor[](#value-editor "Direct link to Value editor")
See [`ValueEditor` component documentation here](/docs/components/valueeditor.md).
```
type ValueEditorType =
| 'text'
| 'select'
| 'checkbox'
| 'radio'
| 'textarea'
| 'multiselect'
| 'date'
| 'datetime-local'
| 'time'
| null;
type ValueSource = 'value' | 'field';
type ValueSources = ['value'] | ['value', 'field'] | ['field', 'value'] | ['field'];
```
## Miscellaneous[](#miscellaneous "Direct link to Miscellaneous")
```
interface Schema {
qbId: string;
fields: OptionList;
fieldMap: Record;
classNames: Classnames;
combinators: OptionList;
controls: Controls;
createRule(): RuleType;
createRuleGroup(): RuleGroupTypeAny;
dispatchQuery(query: RuleGroupTypeAny): void;
getQuery(): RuleGroupTypeAny | undefined;
getOperators(field: string): OptionList;
getValueEditorType(field: string, operator: string): ValueEditorType;
getValueEditorSeparator(field: string, operator: string): ReactNode;
getValueSources(field: string, operator: string): ValueSources;
getInputType(field: string, operator: string): string | null;
getValues(field: string, operator: string): OptionList;
getRuleClassname(rule: RuleType): Classname;
getRuleGroupClassname(ruleGroup: RuleGroupTypeAny): Classname;
showCombinatorsBetweenRules: boolean;
showNotToggle: boolean;
showShiftActions: boolean;
showCloneButtons: boolean;
showLockButtons: boolean;
autoSelectField: boolean;
autoSelectOperator: boolean;
addRuleToNewGroups: boolean;
enableDragAndDrop: boolean;
validationMap: ValidationMap;
independentCombinators: boolean;
listsAsArrays: boolean;
parseNumbers: ParseNumbersMethod;
disabledPaths: Path[];
}
interface QueryActions {
onGroupAdd(group: RuleGroupTypeAny, parentPath: Path, context?: any): void;
onGroupRemove(path: Path): void;
onPropChange(
prop: Exclude,
value: any,
path: Path
): void;
onRuleAdd(rule: RuleType, parentPath: Path, context?: any): void;
onRuleRemove(path: Path): void;
moveRule(oldPath: Path, newPath: Path, clone?: boolean): void;
}
```
---
# Export
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
Use the `formatQuery` function to export queries in various formats. The function has this signature:
```
function formatQuery(
query: RuleGroupTypeAny,
options?: ExportFormat | FormatQueryOptions
): string | ParameterizedSQL | ParameterizedNamedSQL | RQBJsonLogic | Record;
```
`formatQuery` converts query objects to these formats:
* Formatted `JSON.stringify` result
* Unformatted `JSON.stringify` result with all `id` and `path` properties removed
* SQL `WHERE` clause
* Parameterized with anonymous parameters
* Parameterized with named parameters
* ORM query objects for Drizzle, Prisma, Sequelize, and TanStack DB
* MongoDB query object
* ~~MongoDB query object as string~~ [*(deprecated)*](#mongodb)
* Common Expression Language (CEL)
* Spring Expression Language (SpEL)
* JsonLogic
* ElasticSearch
* JSONata
* LDAP
* Natural language
The following sections use this example `query`:
```
const query: RuleGroupType = {
id: 'root',
combinator: 'and',
not: false,
rules: [
{
id: 'rule1',
field: 'firstName',
operator: '=',
value: 'Steve',
},
{
id: 'rule2',
field: 'lastName',
operator: '=',
value: 'Vai',
},
],
};
```
tip
For best results, use [default combinators and operators](/docs/utils/misc.md#defaults) or map custom ones to defaults with [`transformQuery`](/docs/utils/misc.md#transformquery).
More information...
`formatQuery` accepts `RuleGroupTypeAny` queries but only guarantees correct processing of `DefaultRuleGroupTypeAny` queries.
All query `combinator` and `operator` properties must match [`defaultCombinators` or `defaultOperators`](/docs/utils/misc.md#defaults) names (case-insensitive). Use [`transformQuery`](/docs/utils/misc.md#transformquery) to map custom names to defaults before calling `formatQuery`.
For example, replacing the default "between" operator with `{ name: "b/w", label: "b/w" }` creates rules with `operator: "b/w"`. For this query:
```
{
"combinator": "and",
"rules": [{ "field": "someNumber", "operator": "b/w", "value": "12,14" }]
}
```
Transform it using `transformQuery` with `operatorMap`:
```
const newQuery = transformQuery(query, { operatorMap: { 'b/w': 'between' } });
/*
{
"combinator": "and",
"rules": [{ "field": "someNumber", "operator": "between", "value": "12,14" }]
}
*/
```
The `newQuery` is ready for `formatQuery`, including special "between" operator handling.
## Basic usage[](#basic-usage "Direct link to Basic usage")
### JSON[](#json "Direct link to JSON")
Export the internal query representation (from `onQueryChange` callback) as formatted JSON:
```
formatQuery(query);
// or
formatQuery(query, 'json');
```
Output is multi-line JSON with 2-space indentation:
```
`{
"id": "root",
"combinator": "and",
"not": false,
"rules": [
{
"id": "rule1",
"field": "firstName",
"value": "Steve",
"operator": "="
},
{
"id": "rule2",
"field": "lastName",
"value": "Vai",
"operator": "="
}
]
}`;
```
### JSON without IDs[](#json-without-ids "Direct link to JSON without IDs")
Export unformatted (single-line) JSON without `id` or `path` attributes using "json\_without\_ids". This format is useful for persistent storage:
```
formatQuery(query, 'json_without_ids');
```
Output (string):
```
{"combinator":"and","not":false,"rules":[{"field":"firstName","value":"Steve","operator":"="},{"field":"lastName","value":"Vai","operator":"="}]}
```
### SQL[](#sql "Direct link to SQL")
Export SQL `WHERE` clauses using the "sql" format. This format is compatible with major RDBMS engines, though some cases require [configuration](#configuration). See [presets](#presets) for compatibility details.
```
formatQuery(query, 'sql');
```
Output (string):
```
(firstName = 'Steve' and lastName = 'Vai')
```
#### Parameterized SQL[](#parameterized-sql "Direct link to Parameterized SQL")
Export SQL with bind variables instead of inline values using the "parameterized" format. This returns an object with `sql` and `params` properties:
```
formatQuery(query, 'parameterized');
```
Output (JSON object):
```
{
"sql": "(firstName = ? and lastName = ?)",
"params": ["Steve", "Vai"]
}
```
#### Named parameters[](#named-parameters "Direct link to Named parameters")
When anonymous parameters aren't suitable, use "parameterized\_named" to name parameters based on field names. This is similar to "parameterized" but `params` is an object instead of an array:
```
formatQuery(query, 'parameterized_named');
```
Output (JSON object):
```
{
"sql": "(firstName = :firstName_1 and lastName = :lastName_1)",
"params": {
"firstName_1": "Steve",
"lastName_1": "Vai"
}
}
```
See also: [`paramPrefix`](#parameter-prefix) and [generating parameter names](#generating-parameter-names).
### ORMs[](#orms "Direct link to ORMs")
#### Prisma ORM[](#prisma-orm "Direct link to Prisma ORM")
Generate objects for Prisma ORM `where` properties using the "prisma" format:
> *Note: Prisma does not support field-to-field comparisons, so rules with `valueSource: "field"` will always be invalid.*
```
const where = formatQuery(query, 'prisma');
console.log(where);
// { AND: [{ firstName: 'Steve' }, { lastName: 'Vai' }] }
const users = await prisma.users.findMany({ where });
```
#### Drizzle ORM[](#drizzle-orm "Direct link to Drizzle ORM")
##### Relational Queries API[](#relational-queries-api "Direct link to Relational Queries API")
Generate functions for Drizzle's [relational queries API](https://orm.drizzle.team/docs/rqb) `where` property:
```
const where = formatQuery(query, 'drizzle');
// typeof where === 'function'
// where.length === 2
const results = db.query.users.findMany({ where });
```
##### Query Builder API[](#query-builder-api "Direct link to Query Builder API")
For Drizzle's [query builder API](https://orm.drizzle.team/docs/select), pass table definition and operators to the `formatQuery`-generated function:
```
import { getOperators } from 'drizzle-orm';
const whereFn = formatQuery(query, 'drizzle');
const whereObj = whereFn(table, getOperators());
const query = db.select().from(table).where(whereObj);
```
tip
Query builder API objects work with other Drizzle operators, letting you add conditions not in the original query:
```
import { and, ne, getOperators } from 'drizzle-orm';
// Conditions from the React Query Builder query object:
const whereFn = formatQuery(query, 'drizzle');
const whereObj = whereFn(table, getOperators());
// All conditions from the original query object _and_ `id != 123`:
const augmentedWhere = and(whereObj, ne(table.id, 123));
const query = db.select().from(table).where(augmentedWhere);
```
`@react-querybuilder/drizzle` *(deprecated)*
The [`@react-querybuilder/drizzle`](https://npmjs.com/package/@react-querybuilder/drizzle) package previously provided `generateDrizzleRuleGroupProcessor` and `generateDrizzleRuleProcessor` for integration with Drizzle's [query builder API](https://orm.drizzle.team/docs/select). This package is now deprecated.
To achieve the same result, inline the following functions in your project:
```
import type { RuleGroupProcessor, RuleProcessor } from '@react-querybuilder/core';
import {
defaultRuleGroupProcessorDrizzle,
defaultRuleProcessorDrizzle,
} from '@react-querybuilder/core';
import type { Column, SQL, Table } from 'drizzle-orm';
import * as drizzleOperators from 'drizzle-orm';
import { getOperators } from 'drizzle-orm';
export const generateDrizzleRuleGroupProcessor =
(columns: Record | Table): RuleGroupProcessor =>
(ruleGroup, options) =>
defaultRuleGroupProcessorDrizzle(ruleGroup, options)(
columns as Record,
getOperators()
);
export const generateDrizzleRuleProcessor =
(table: Table | Record): RuleProcessor =>
(rule, options) =>
defaultRuleProcessorDrizzle(rule, { ...options, context: { table, drizzleOperators } });
```
Usage:
```
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { formatQuery } from 'react-querybuilder';
const db = drizzle(process.env.DB_FILE_NAME!);
const table = sqliteTable('musicians', {
firstName: text(),
lastName: text(),
});
const ruleGroupProcessor = generateDrizzleRuleGroupProcessor(table);
// Tip: `format` is not required when `ruleGroupProcessor` is provided
const where = formatQuery(query, { ruleGroupProcessor });
const query = db.select().from(table).where(where);
console.log(query.toSQL());
// {
// sql: 'select "firstName", "lastName" from "musicians" where ("musicians"."firstName" = ? and "musicians"."lastName" = ?)',
// params: ['Steve', 'Vai']
// }
console.log(query.all());
// [{ firstName: 'Steve', lastName: 'Vai' }]
```
#### Sequelize[](#sequelize "Direct link to Sequelize")
Generate objects for Sequelize `findAll` `where` properties using the "sequelize" format. Requirements:
* Sequelize uses `Symbol`s for operator keys, so they must be provided through the `context` option as `sequelizeOperators` (see example below).
* If any rules have `valueSource: "field"`, then the Sequelize `col` function must be provided as `sequelizeCol`.
* If any rules have `valueSource: "field"` and use one of the `doesNot*` operators, then the Sequelize `fn` function must be provided as `sequelizeFn`.
```
import { col, fn, Op } from 'sequelize';
const where = formatQuery(query, {
format: 'sequelize',
context: { sequelizeOperators: Op, sequelizeCol: col, sequelizeFn: fn },
});
const users = await Users.findAll({ where });
```
#### TanStack DB[](#tanstack-db "Direct link to TanStack DB")
Generate a `WhereCallback` for [TanStack DB](https://tanstack.com/db)'s `.where()` method using the "tanstack\_db" format. The processor does not import any executable code from `@tanstack/db` — operators are passed in through the `context` option.
Pass the full `@tanstack/db` module or individual operators as `tanStackDbOperators`:
```
import * as tsdb from '@tanstack/db';
const where = formatQuery(query, {
format: 'tanstack_db',
context: { tanStackDbOperators: tsdb },
});
const results = useLiveQuery(q => q.from({ users: usersCollection }).where(where));
```
Or with cherry-picked operators:
```
import { eq, gt, gte, lt, lte, like, inArray, isNull, not, and, or } from '@tanstack/db';
const where = formatQuery(query, {
format: 'tanstack_db',
context: {
tanStackDbOperators: { eq, gt, gte, lt, lte, like, inArray, isNull, not, and, or },
},
});
```
tip
TanStack DB does not expose `ne`, `between`, `notBetween`, `notInArray`, `notLike`, or `isNotNull` — these are composed automatically using `not(...)`. For example, `!=` becomes `not(eq(...))` and `between` becomes `and(gte(...), lte(...))`.
##### Joins (multi-collection queries)[](#joins-multi-collection-queries "Direct link to Joins (multi-collection queries)")
When querying across joined collections, fields from non-primary collections must use dotted notation (`"alias.fieldName"`) to target the correct ref. Bare (unprefixed) fields always resolve to the primary collection (the first key in the `refs` object).
```
import * as tsdb from '@tanstack/db';
const query = {
combinator: 'and',
rules: [
// Bare field → resolves to the primary collection (su)
{ field: 'firstName', operator: '=', value: 'Bruce' },
// Dotted field → resolves to the nicknames collection (nn)
{ field: 'nn.nickname', operator: 'contains', value: 'Dark' },
],
};
const where = formatQuery(query, {
format: 'tanstack_db',
context: { tanStackDbOperators: tsdb },
});
const results = useLiveQuery(q =>
q
.from({ su: superUsersCollection })
.leftJoin({ nn: nicknamesCollection }, refs => eq(refs.su.id, refs.nn.userId))
.where(where)
);
```
caution
Bare fields cannot be disambiguated across collections at export time because TanStack DB refs are proxies that accept any property name. Always use dotted notation for fields on joined (non-primary) collections.
### MongoDB[](#mongodb "Direct link to MongoDB")
Generate MongoDB queries as JSON objects or strings. Use the "mongodb\_query" format (recommended) for JSON objects. The "mongodb" format is the stringified version.
info
The "mongodb" format was deprecated when the "mongodb\_query" export format was introduced in version 8.1.0.
```
formatQuery(query, 'mongodb_query');
```
Output (JSON object):
```
{ "$and": [{ "firstName": "Steve" }, { "lastName": "Vai" }] }
```
### Common Expression Language[](#common-expression-language "Direct link to Common Expression Language")
For [Common Expression Language (CEL)](https://cel.dev) output, use the "cel" format.
```
formatQuery(query, 'cel');
```
Output (string):
```
firstName = "Steve" && lastName = "Vai"
```
### Spring Expression Language[](#spring-expression-language "Direct link to Spring Expression Language")
For [Spring Expression Language (SpEL)](https://docs.spring.io/spring-framework/reference/core/expressions.html) output, use the "spel" format.
```
formatQuery(query, 'spel');
```
Output (string):
```
firstName == 'Steve' and lastName == 'Vai'
```
### JsonLogic[](#jsonlogic "Direct link to JsonLogic")
Generate objects for JsonLogic `apply` function (see ):
```
formatQuery(query, 'jsonlogic');
```
Output (JSON object):
```
{ "and": [{ "==": [{ "var": "firstName" }, "Steve"] }, { "==": [{ "var": "lastName" }, "Vai"] }] }
```
tip
Register additional `startsWith` and `endsWith` operators from `react-querybuilder` before using JsonLogic's `apply()`. These aren't [standard JsonLogic operations](https://jsonlogic.com/operations.html) but correspond to "beginsWith" and "endsWith" operators.
Loop through `jsonLogicAdditionalOperators` entries for future-proof registration of any new custom operators:
```
import { add_operation, apply } from 'json-logic-js';
import { jsonLogicAdditionalOperators } from 'react-querybuilder';
for (const [op, func] of Object.entries(jsonLogicAdditionalOperators)) {
add_operation(op, func);
}
apply({ startsWith: [{ var: 'firstName' }, 'Stev'] }, data);
```
### ElasticSearch[](#elasticsearch "Direct link to ElasticSearch")
Generate objects for [ElasticSearch](https://www.elastic.co/) processing:
```
formatQuery(query, 'elasticsearch');
```
Output (JSON object):
```
{ "bool": { "must": [{ "term": { "firstName": "Steve" } }, { "term": { "lastName": "Vai" } }] } }
```
### JSONata[](#jsonata "Direct link to JSONata")
Generate [JSONata](https://jsonata.org/) filters using "jsonata" format. Use [`parseNumbers` option](#parse-numbers) for numeric values since JSONata doesn't auto-cast strings to numbers:
```
formatQuery(query, { format: 'jsonata', parseNumbers: true });
```
Output (string):
```
firstName = "Steve" and lastName = "Vai"
```
Handling date values in JSONata
React Query Builder lacks standard date detection, so use `datetimeRuleProcessorJSONata` from [`@react-querybuilder/datetime`](/docs/datetime.md#jsonata).
For more control, implement a custom rule processor (example below lacks error checking but provides a starting point):
```
const customRuleProcessor: RuleProcessor = (rule, options) => {
// `datatype` is a non-standard property of the field, used for this example only.
// Replace this condition with your own logic to determine if the value is a date.
if (options?.fieldData?.datatype === 'date') {
return `$toMillis(${rule.field}) ${rule.operator} $toMillis("${rule.value}")`;
}
return defaultRuleProcessorJSONata(rule, options);
};
```
### LDAP[](#ldap "Direct link to LDAP")
Generate [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) filters:
> *Note: LDAP filters do not support direct comparison between the values of two attributes within the same entry, so rules with `valueSource: "field"` will always be invalid.*
```
formatQuery(query, 'ldap');
```
Output (string):
```
(&(givenName=Steve)(sn=Vai))
```
### Natural language[](#natural-language "Direct link to Natural language")
Generate natural language queries using "natural\_language" format. Use `getOperators` and `fields` options to render labels instead of values. See [i18n options](#internationalization):
```
formatQuery(query, {
format: 'natural_language',
parseNumbers: true,
getOperators: () => defaultOperators,
fields: [
{ value: 'firstName', label: 'First Name' },
{ value: 'lastName', label: 'Last Name' },
{ value: 'age', label: 'Age' },
],
});
```
Output (string):
```
First Name is 'Steve', and Last Name is "Vai", and Age is between 26 and 52
```
### Cypher[](#cypher "Direct link to Cypher")
Generate [Cypher](https://neo4j.com/docs/cypher-manual/) `WHERE` clause conditions using the "cypher" format. This format is also available as "gql" since [GQL](https://www.iso.org/standard/76120.html) uses the same expression syntax.
```
formatQuery(query, 'cypher');
// or
formatQuery(query, 'gql');
```
Output (string):
```
n.firstName = 'Steve' AND n.lastName = 'Vai'
```
### SPARQL[](#sparql "Direct link to SPARQL")
Generate [SPARQL](https://www.w3.org/TR/sparql11-query/) `FILTER` expressions using the "sparql" format:
```
formatQuery(query, 'sparql');
```
Output (string):
```
?firstName = "Steve" && ?lastName = "Vai"
```
### Gremlin[](#gremlin "Direct link to Gremlin")
Generate [Apache TinkerPop Gremlin](https://tinkerpop.apache.org/) `.has()` steps using the "gremlin" format:
```
formatQuery(query, 'gremlin');
```
Output (string):
```
.has('firstName', 'Steve').has('lastName', 'Vai')
```
### react-awesome-query-builder[](#react-awesome-query-builder "Direct link to react-awesome-query-builder")
Generate a [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB) query tree in its plain-JSON form, suitable for RAQB's `Utils.loadTree()`.
This is *not* a built-in export format. Since most projects need it exactly once, it lives in the separate [`@react-querybuilder/migrate-raqb`](https://github.com/react-querybuilder/migrate-raqb) package, which exports a `formatRAQB` function.
* npm
* Bun
* Yarn
* pnpm
```
npm i @react-querybuilder/migrate-raqb
```
```
bun add @react-querybuilder/migrate-raqb
```
```
yarn add @react-querybuilder/migrate-raqb
```
```
pnpm add @react-querybuilder/migrate-raqb
```
```
import { Utils } from '@react-awesome-query-builder/core';
import { formatRAQB } from '@react-querybuilder/migrate-raqb';
const jsonTree = formatRAQB(query, { fields });
const immutableTree = Utils.checkTree(Utils.loadTree(jsonTree), config);
```
This is the inverse of `parseRAQB`. See [Migrating from react-awesome-query-builder](/docs/tips/migrate-from-raqb.md) for the full concept mapping.
Output (object):
```
{
"type": "group",
"properties": { "conjunction": "AND", "not": false },
"children1": [
{
"type": "rule",
"properties": {
"field": "firstName",
"operator": "equal",
"value": ["Steve"],
"valueSrc": ["value"]
}
},
{
"type": "rule",
"properties": {
"field": "lastName",
"operator": "equal",
"value": ["Vai"],
"valueSrc": ["value"]
}
}
]
}
```
A companion function, `formatRAQBFields`, converts an RQB `fields` array to the `fields` section of an RAQB `Config`:
```
import { formatRAQBFields } from '@react-querybuilder/migrate-raqb';
const config = { ...BasicConfig, fields: formatRAQBFields(fields) };
```
`formatRAQB` accepts all `formatQuery` options except `format`, `ruleGroupProcessor`, and `fallbackExpression`, plus the RAQB-specific options below and `fallbackTree` (the tree returned when the query is empty or fails validation).
```
const jsonTree = formatRAQB(query, { fields, raqbFieldSeparator: '.', raqbValueTypes: true });
```
| Option | Default | Description |
| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `raqbOperatorMap` | `{}` | Additional/overriding RQB-to-RAQB operator mappings, keyed by RQB operator name. |
| `raqbFunctionMap` | `{}` | Additional/overriding expression-function-to-RAQB function name mappings. |
| `raqbFuncArgOrder` | `{}` | Argument names per RAQB function name. Defaults cover RAQB's built-ins; others get `arg0`, `arg1`, ... |
| `raqbFieldSeparator` | `"."` | Separator used to qualify sub-query rule fields with their parent `!group` field name. |
| `raqbRelativeDateTimes` | `true` | Convert relative date/time values to RAQB's built-in date/time functions. |
| `raqbValueTypes` | `false` | Emit `valueType` entries inferred from each field's `inputType`/`valueEditorType`. |
To combine RAQB output with other `formatQuery` behavior, or to supply a custom `ruleProcessor`, use the underlying `defaultRuleGroupProcessorRAQB` with the [`ruleGroupProcessor`](#rule-group-processor) option, which takes precedence over `format`. RAQB options then move into `context`, and `raqbFallback` should be passed as `fallbackExpression` if a `validator` can invalidate the whole query.
```
import { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';
const jsonTree = formatQuery(query, {
ruleGroupProcessor: defaultRuleGroupProcessorRAQB,
fields,
context: { raqbValueTypes: true },
fallbackExpression: raqbFallback as unknown as string,
});
```
note
RAQB's default configuration has no equivalent for RQB's `doesNotBeginWith`/`doesNotEndWith` operators, the `"parameter"` value source, or `endOf*` relative date/time anchors. Rules using them are omitted (or, for `endOf*` anchors, emitted as plain values). Use `raqbOperatorMap` to map them onto custom RAQB operators.
RAQB also restricts some constructs that RQB permits—notably `in`/`notIn` on non-`select` fields and most field-to-field comparisons. See [Migrating from react-awesome-query-builder](/docs/tips/migrate-from-raqb.md#converting-back-to-raqb) for the full list.
### Diagnostics[](#diagnostics "Direct link to Diagnostics")
Generate a diagnostics result object using the "diagnostics" format. The output includes an annotated copy of the query tree, a flat diagnostics array, aggregate statistics, and a per-field summary.
```
const result = formatQuery(query, {
format: 'diagnostics',
fields: [
{
name: 'firstName',
label: 'First Name',
validator: r => (r.value ? true : { valid: false, reasons: ['Value is required'] }),
},
{ name: 'age', label: 'Age', inputType: 'number' },
],
});
```
Output (object):
```
{
"query": {
"combinator": "and",
"valid": false,
"path": [],
"level": 0,
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "",
"valid": false,
"reasons": ["Value is required"],
"path": [0],
"level": 1
},
{
"field": "age",
"operator": ">",
"value": 26,
"valid": true,
"path": [1],
"level": 1
}
]
},
"diagnostics": [
{
"id": "r-1",
"path": [0],
"code": "CUSTOM_VALIDATOR",
"message": "Invalid: Value is required",
"source": "field-validator"
}
],
"stats": {
"totalRules": 2,
"totalGroups": 1,
"validRules": 1,
"invalidRules": 1,
"validGroups": 0,
"invalidGroups": 1
},
"fieldSummary": {
"firstName": { "ruleCount": 1, "invalidCount": 1 },
"age": { "ruleCount": 1, "invalidCount": 0 }
}
}
```
#### Annotated query tree[](#annotated-query-tree "Direct link to Annotated query tree")
Every rule and group in `result.query` includes:
* `valid` — whether the node passed all checks
* `reasons` — optional array of reasons (from validators)
* `path` — the position of the node in the tree (e.g., `[1, 0]`)
* `level` — the nesting depth (`path.length`)
A rule is considered invalid if any of the following are true:
* The rule is `muted`
* The rule fails validation via the `validator` option or a field-level `validator`
* The `field`, `operator`, or `value` matches its respective placeholder name
The root-level `valid` property is `true` only when the group itself is valid *and* all descendant rules and groups are valid, making it suitable for gating API calls.
#### Flat diagnostics array[](#flat-diagnostics-array "Direct link to Flat diagnostics array")
`result.diagnostics` is a flat array of `DiagnosticEntry` objects, each with an `id`, `path`, `code`, `message`, and `source`. Diagnostic codes include:
| Code | Source | Description |
| ---------------------- | ------------------------------------- | -------------------------------------------------- |
| `PLACEHOLDER_FIELD` | `placeholder` | Rule has a placeholder field name |
| `PLACEHOLDER_OPERATOR` | `placeholder` | Rule has a placeholder operator name |
| `PLACEHOLDER_VALUE` | `placeholder` | Rule has a placeholder value |
| `MUTED` | `muted` | Rule or group is muted |
| `CUSTOM_VALIDATOR` | `query-validator` / `field-validator` | Failed a custom validator |
| `UNDEFINED_FIELD` | `field-check` | Rule references a field not in the `fields` config |
| `UNREFERENCED_FIELD` | `field-check` | A field in the config is not used by any rule |
| `VALUE_TYPE_MISMATCH` | `type-check` | Value is incompatible with the field's `inputType` |
The `UNDEFINED_FIELD`, `UNREFERENCED_FIELD`, and `VALUE_TYPE_MISMATCH` diagnostics are only produced when a `fields` config is provided.
#### Stats and field summary[](#stats-and-field-summary "Direct link to Stats and field summary")
`result.stats` provides aggregate counts: `totalRules`, `totalGroups`, `validRules`, `invalidRules`, `validGroups`, `invalidGroups`.
`result.fieldSummary` is a record keyed by field name, where each value has `ruleCount` (total rules for that field) and `invalidCount` (invalid rules for that field).
## Configuration[](#configuration "Direct link to Configuration")
Pass an object as the second argument for fine-grained output control:
### Parse numbers[](#parse-numbers "Direct link to Parse numbers")
Render values as numbers instead of quoted strings using `parseNumbers: true`. See [Number parsing](/docs/utils/misc.md#number-parsing) for details.
#### Preserve value order[](#preserve-value-order "Direct link to Preserve value order")
`formatQuery` sorts "between"/"notBetween" values in ascending order when `parseNumbers` renders them as numbers. Disable with `preserveValueOrder`:
```
const query = {
rules: [{ field: 'age', operator: 'between', value: [30, 20] }],
};
formatQuery(query, { format: 'sql', parseNumbers: true });
/*
"(age between 20 and 30)"
*/
formatQuery(query, { format: 'sql', parseNumbers: true, preserveValueOrder: true });
/*
"(age between 30 and 20)"
*/
```
caution
This can create conditions that always evaluate to false. SQL's `X BETWEEN Y AND Z` equals `X >= Y AND X <= Z`—if Y > Z, no X value satisfies both conditions.
`formatQuery` assumes users mean "X is between points Y and Z" regardless of direction.
### Rule processor[](#rule-processor "Direct link to Rule processor")
Customize individual rule output using `ruleProcessor`. Only validated rules reach this function:
```
ruleProcessor(rule, { escapeQuotes, fieldData, ...otherOptions });
```
Arguments: `RuleType` object and `ValueProcessorOptions` object with `escapeQuotes` (true for string values, false for field names), `fieldData` (corresponding `Field` object), and other `formatQuery` options.
The default rule processors for each format are available as exports from `react-querybuilder`:
* `defaultRuleProcessorCEL`
* `defaultRuleProcessorElasticSearch`
* `defaultRuleProcessorJSONata`
* `defaultRuleProcessorJsonLogic`
* `defaultRuleProcessorMongoDB`
* `defaultRuleProcessorMongoDBQuery`
* `defaultRuleProcessorNL`
* `defaultRuleProcessorSpEL`
* `defaultRuleProcessorSQL`
* `defaultRuleProcessorParameterized`
* `defaultRuleProcessorTanStackDB`
Refer to the source code to determine the appropriate return type for custom rule processors.
Use the appropriate default rule processor as a fallback so your custom processor doesn't cover all cases:
```
const query: RuleGroupType = {
combinator: 'and',
not: false,
rules: [
{ field: 'firstName', operator: 'has', value: 'S' },
// non-standard operator ^^^^^
{ field: 'lastName', operator: '=', value: 'Vai' },
],
};
const customRuleProcessor: RuleProcessor = (rule, options) => {
// The "has" operator is not handled by the default processor
if (rule.operator === 'has') {
return { in: [rule.value, { var: rule.field }] };
}
// Defer to the default processor for all other operators
return defaultRuleProcessorJsonLogic(rule, options);
};
formatQuery(query, { format: 'jsonlogic', ruleProcessor: customRuleProcessor });
/*
{
and: [
{ in: ["S", { var: "firstName" }] },
{ "==": [{ var: "lastName" }, "Vai"] }
]
}
*/
```
This SQL example (using Oracle syntax) demonstrates the generation of a case-insensitive condition:
```
// `query` is the same as in the previous example
const customRuleProcessor: RuleProcessor = (rule, options) => {
if (rule.operator === 'has') {
return `UPPER(${rule.field}) LIKE UPPER('%${rule.value}%')`;
}
return defaultRuleProcessorSQL(rule, options);
};
formatQuery(query, { format: 'sql', ruleProcessor: customRuleProcessor });
/*
"(UPPER(firstName) LIKE UPPER('%S%') and lastName = 'Vai')"
^------------custom--------------^ ^------default-----^
*/
```
#### Generating parameter names[](#generating-parameter-names "Direct link to Generating parameter names")
The "parameterized" and "parameterized\_named" formats require rule processors to return an object resembling `formatQuery`'s return type for these formats. The `getNextNamedParam` utility helps generate unique parameter names. The example below matches the Oracle SQL example above, but uses "parameterized\_named" format.
```
const customRuleProcessor: RuleProcessor = (rule, options) => {
if (rule.operator === 'has') {
// TIP: `getNextNamedParam` can be called multiple times in case your SQL
// requires multiple unique parameters (e.g., in a "between" condition).
// Each call will generate a new name.
const paramName = options.getNextNamedParam!(rule.field);
return {
sql: `UPPER(${rule.field}) LIKE UPPER('%' || ${options.paramPrefix}${paramName} || '%')`,
params: { [paramName]: rule.value },
};
}
return defaultRuleProcessorSQLParameterized(rule, options);
};
formatQuery(query, { format: 'parameterized_named', ruleProcessor: customRuleProcessor });
/*
{
sql: "(UPPER(firstName) LIKE UPPER('%' || :firstName_1 || '%') and lastName = :lastName_1)",
params: {
firstName_1: "S",
lastName_1: "Vai"
}
}
*/
```
### Value processor[](#value-processor "Direct link to Value processor")
`valueProcessor` accepts the same arguments as `ruleProcessor`, but only affects the "value" portion (to the right of the operator) for "sql" format. If both are provided, `ruleProcessor` takes precedence.
tip
For all formats except "sql", `valueProcessor` is a synonym for `ruleProcessor`. Use `ruleProcessor` unless exporting SQL and only customizing the value portion.
```
// `query` is the same as in the previous example
const customValueProcessor: ValueProcessorByRule = (rule, options) => {
if (rule.operator === 'has') {
return `'%${rule.value}%'`;
}
return defaultValueProcessorByRule(rule, options);
};
formatQuery(query, { format: 'sql', valueProcessor: customValueProcessor });
/*
"(firstName like '%S%' and lastName = 'Vai')"
^---default---^ ^---^-custom ^--default--^
*/
```
#### Legacy `valueProcessor` behavior[](#legacy-valueprocessor-behavior "Direct link to legacy-valueprocessor-behavior")
caution
The legacy `valueProcessor` signature exists for backwards compatibility, but avoid it. Options aren't passed in, making it difficult to correctly fall back to default processors.
If the `valueProcessor` function accepts three or more arguments (excluding those with default values), it's called like this:
```
valueProcessor(field, operator, value, valueSource);
```
No options or additional rule properties are passed as arguments. This prevents `formatQuery` from setting the `escapeQuotes` option, among other problems.
This legacy behavior is documented for completeness but not recommended.
```
const query: RuleGroupType = {
combinator: 'and',
not: false,
rules: [
{ field: 'instrument', operator: 'in', value: ['Guitar', 'Vocals'] },
{ field: 'lastName', operator: '=', value: 'Vai' },
],
};
const customValueProcessor = (field, operator, value) => {
if (operator === 'in') {
// Assuming `value` is an array, such as from a multi-select
return `(${value.map(v => `'${v.trim()}'`).join(',')})`;
}
return defaultValueProcessor(field, operator, value);
};
formatQuery(query, { format: 'sql', valueProcessor: customValueProcessor });
/*
"(instrument in ('Guitar','Vocals') and lastName = 'Vai')"
*/
```
Default value processors using the legacy signature are available for some query language formats.
| Format | Current signature (recommended) | Legacy signature (not recommended) |
| ---------------------- | ------------------------------------ | ---------------------------------- |
| "sql" | `defaultValueProcessorByRule` | `defaultValueProcessor` |
| "parameterized" | `defaultValueProcessorByRule` | `defaultValueProcessor` |
| "parameterized\_named" | `defaultValueProcessorByRule` | `defaultValueProcessor` |
| "cel" | `defaultValueProcessorCELByRule` | `defaultCELValueProcessor` |
| "mongodb" | `defaultValueProcessorMongoDBByRule` | `defaultMongoDBValueProcessor` |
| "spel" | `defaultValueProcessorSpELByRule` | `defaultSpELValueProcessor` |
### Operator processor[](#operator-processor "Direct link to Operator processor")
`operatorProcessor` accepts the same arguments as `ruleProcessor`, but only affects the "operator" portion for "sql", "parameterized", "parameterized\_named", and "natural\_language" formats.
```
formatQuery(query, {
format: 'sql',
// Convert all operators to uppercase
operatorProcessor: (rule, options) => defaultOperatorProcessorSQL(rule, options).toUpperCase(),
});
/*
"(firstName LIKE 'Stev%' and lastName IN ('Vai', 'Vaughan'))"
*/
```
### Quote field names[](#quote-field-names "Direct link to Quote field names")
Some database engines wrap field names in backticks (`` ` ``) or square brackets (`[]`). Configure this with the `quoteFieldNamesWith` option (string or array of two strings).
```
formatQuery(query, { format: 'sql', quoteFieldNamesWith: '`' });
/*
"(`firstName` = 'Steve' and `lastName` = 'Vai')"
*/
formatQuery(query, { format: 'sql', quoteFieldNamesWith: ['[', ']'] });
/*
"([firstName] = 'Steve' and [lastName] = 'Vai')"
*/
```
#### Field identifier chains[](#field-identifier-chains "Direct link to Field identifier chains")
To quote members of field identifier chains independently, use `fieldIdentifierSeparator`. A common value is `"."`.
In this example, assume the field names are `musicians.firstName` and `musicians.lastName`.
```
formatQuery(query, {
format: 'sql',
quoteFieldNamesWith: ['[', ']'],
fieldIdentifierSeparator: '.',
});
/*
"([musicians].[firstName] = 'Steve' and [musicians].[lastName] = 'Vai')"
*/
```
### Quote values[](#quote-values "Direct link to Quote values")
Some database engines can accept string literals in double quotes (`"`). This can be configured with the `quoteValuesWith` option which should be assigned a one-character string.
```
formatQuery(query, { format: 'sql', quoteValuesWith: '"' });
/*
"(firstName = "Steve" and lastName = "Vai")"
*/
```
### Parameter prefix[](#parameter-prefix "Direct link to Parameter prefix")
If the "parameterized\_named" format is used, configure the parameter prefix used in the `sql` string with the `paramPrefix` option, should the default `":"` be inappropriate.
```
const p = formatQuery(query, {
format: 'parameterized_named',
paramPrefix: '$',
});
/*
p.sql === "(firstName = $firstName_1 and lastName = $lastName_1)"
// ^^^ ^^^
*/
```
### Retain parameter prefixes[](#retain-parameter-prefixes "Direct link to Retain parameter prefixes")
`paramsKeepPrefix` simplifies compatibility with [SQLite](https://sqlite.org/). With "parameterized\_named" format, `params` object keys maintain the `paramPrefix` string as it appears in the `sql` string (e.g. `{ ":param_1": "val" }` instead of `{ "param_1": "val" }`).
### Numbered parameters[](#numbered-parameters "Direct link to Numbered parameters")
For "parameterized" format, parameter placeholders in generated SQL are "?" by default. When `numberedParams` is `true`, placeholders become numbered indices starting with `1`, incrementing left to right. Each placeholder number is prefixed with the configured `paramPrefix` string (default `":"`).
```
const p = formatQuery(query, {
format: 'parameterized',
paramPrefix: '$',
numberedParams: true,
});
/*
p.sql === "(firstName = $1 and lastName = $2)"
*/
```
Previously, [manual post-processing](/docs/tips/custom-bind-variables.md) was necessary for this effect.
### Named parameters (value source)[](#named-parameters-value-source "Direct link to Named parameters (value source)")
The `getParameters` option supports rules whose `valueSource` is [`"parameter"`](/docs/components/valueeditor.md#the-parameter-value-source). Provide the same function passed to the [`getParameters` prop](/docs/components/querybuilder.md#getparameters) (names without a prefix):
```
formatQuery(query, {
format: 'sql',
getParameters: () => [{ name: 'p1', label: 'Param 1' }],
});
```
Behavior by format:
* **`sql`** — the prefixed name is emitted inline (e.g. `f1 = :p1`).
* **`parameterized`** — the name is emitted inline; positional placeholders are *not* pushed to `params`.
* **`parameterized_named`** — the name is registered as a `params` key with a `null` placeholder value (respecting `paramsKeepPrefix`), to be supplied at execution time. (`null` rather than `undefined`, so the key is preserved by `JSON.stringify`.)
* **`cel`, `spel`, `jsonlogic`** — the name is treated as an identifier/variable reference.
* Other formats emit the name as a literal.
When `getParameters` is supplied, rules referencing a name not in the list are treated as invalid (dropped or handled per your validation options).
tip
The [external parameter manager](/docs/tips/parameter-manager.md) example demonstrates merging user-supplied values over the `null` placeholders produced by the `"parameterized_named"` format.
### Concatenation operator[](#concatenation-operator "Direct link to Concatenation operator")
Most SQL database dialects use the `||` operator to concatenate strings. SQL Server uses `+`, and MySQL uses the `CONCAT` function instead.
Configure the concatenation operator (used for "contains", "beginswith", and "endswith" operators when `valueSource` is "field") with the `concatOperator` option. `formatQuery` uses the ANSI standard `||` by default.
If the value is `"CONCAT"` (case-insensitive), the `CONCAT` function is used. (Note: Oracle SQL doesn't support more than two values in `CONCAT`, so avoid this option with Oracle. The default `||` operator is Oracle-compatible.)
```
const query = {
combinator: 'and',
rules: [
{ field: 'firstName', operator: '=', value: 'Kris' },
{ field: 'lastName', operator: 'beginswith', value: 'firstName', valueSource: 'field' },
],
};
formatQuery(query, { format: 'sql', concatOperator: '+' });
/*
"(firstName = 'Kris' and lastName like firstName + '%')"
*/
formatQuery(query, { format: 'sql', concatOperator: 'CONCAT' });
/*
"(firstName = 'Kris' and lastName like CONCAT(firstName, '%'))"
*/
```
### Presets[](#presets "Direct link to Presets")
The `preset` option configures options known to enable or improve compatibility with particular query language dialects. Individual options override their respective preset values. Available presets:
info
If `preset` is from `sqlDialectPresets`, it only applies if `format` is undefined or one of the SQL-based formats.
| Dialect | Preset options |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `'ansi'` | ```
{}
``` |
| `'sqlite'` | ```
{ "paramsKeepPrefix": true }
``` |
| `'oracle'` | ```
{}
``` |
| `'mssql'` | ```
{
"quoteFieldNamesWith": ["[", "]"],
"concatOperator": "+",
"fieldIdentifierSeparator": ".",
"paramPrefix": "@"
}
``` |
| `'mysql'` | ```
{ "concatOperator": "CONCAT" }
``` |
| `'postgresql'` | ```
{ "quoteFieldNamesWith": "\"", "numberedParams": true, "paramPrefix": "$" }
``` |
Examples:
```
formatQuery(query, { format: 'parameterized', preset: 'postgresql' });
/*
{
sql: `("firstName" like $1 and "lastName" in ($2, $3))`,
params: ['Stev%', 'Vai', 'Vaughan']
}
*/
formatQuery(query, { format: 'sql', preset: 'mssql' });
/*
"([musicians].[firstName] = 'Kris' and [musicians].[lastName] like [musicians].[firstName] + '%')"
*/
```
### Fallback expression[](#fallback-expression "Direct link to Fallback expression")
`fallbackExpression` is a string included in output when `formatQuery` can't determine what to do for a particular rule or group. The intent is to maintain valid syntax while not affecting query criteria. If not provided, the default fallback expression for the format is used:
| Format | Default `fallbackExpression` |
| ----------------------- | ----------------------------- |
| `'sql'` | `'(1 = 1)'` |
| `'parameterized'` | `'(1 = 1)'` |
| `'parameterized_named'` | `'(1 = 1)'` |
| `'cypher'` / `'gql'` | `'(1 = 1)'` |
| `'sparql'` | `'1 = 1'` |
| `'gremlin'` | `''` |
| `'ldap'` | `''` |
| `'mongodb'` | `'{"$and":[{"$expr":true}]}'` |
| `'mongodb_query'` | `{"$and":[{"$expr":true}]}` |
| `'natural_language'` | `'1 is 1'` |
| `'cel'` | `'1 == 1'` |
| `'spel'` | `'1 == 1'` |
| `'jsonata'` | `'(1 = 1)'` |
| `'jsonlogic'` | `false` |
| `'elasticsearch'` | `{}` |
| `'drizzle'` | `undefined` |
| `'prisma'` | `{}` |
| `'sequelize'` | `{}` |
| `'tanstack_db'` | `eq(1, 1)` |
### Value sources[](#value-sources "Direct link to Value sources")
When a rule's `valueSource` property is "field", no parameters are generated.
```
const pf = formatQuery(
{
combinator: 'and',
rules: [
{ field: 'firstName', operator: '=', value: 'lastName', valueSource: 'field' },
{ field: 'firstName', operator: 'beginsWith', value: 'middleName', valueSource: 'field' },
],
},
'parameterized_named'
);
```
Output (JSON object):
```
{
"sql": "(firstName = lastName and firstName like middleName || '%')",
"params": {}
}
```
### Placeholder values[](#placeholder-values "Direct link to Placeholder values")
Rules where `field`, `operator`, or `value` matches the placeholder value (default `"~"`) are excluded from output for most export formats (see [Automatic validation](#automatic-validation)). To use a different placeholder string, set the `placeholderFieldName`, `placeholderOperatorName`, or `placeholderValueName` options. These correspond to `fields.placeholderName`, `operators.placeholderName`, and `values.placeholderName` properties on the main component's [`translations` prop](/docs/components/querybuilder.md#translations) object. This behavior for the `value` property only applies if `placeholderValueName` is explicitly set. The others use their defaults if undefined.
### Internationalization[](#internationalization "Direct link to Internationalization")
These i18n options are specific to ["natural\_language"](#natural-language) format.
#### Word order[](#word-order "Direct link to Word order")
Based on [constituent word order](https://en.wikipedia.org/wiki/Word_order#Constituent_word_orders), the `wordOrder` option accepts all permutations of "SVO" ("SOV", "VSO", etc.) and outputs field, operator, and value in corresponding order (S = field, V = operator, O = value).
```
formatQuery(query, {
format: 'natural_language',
wordOrder: 'SOV',
});
// `First Name 'Steve' is`
```
#### Translations[](#translations "Direct link to Translations")
Map "and", "or", "true", and "false" to their translated equivalents, plus prefix and suffix options for rule groups.
The base prefix/suffix options are "groupPrefix" and "groupSuffix". The applicability of a group-related translation is determined by two conditions: (1) whether the group's `not` property is true, and (2) whether the combinator for the group is `"xor"`. The base `"group*"` translations are the fallbacks for when neither condition is true. When one or more conditions are true, `formatQuery` will look for a property on the `translations` object that matches the base property with a suffix of underscore (`"_"`) plus the condition ID (`"not"` or `"xor"`).
For example, when a group has a `not: true` property, but the `combinator` is something other than `"xor"`, `formatQuery` will look for the `groupSuffix_not` key.
```
formatQuery(query, {
format: 'natural_language',
translations: {
groupSuffix: 'is def the truth',
groupSuffix_not: 'is so not true',
},
});
// Given the following query:
// const query = {
// rules: [
// { rules: [{ field: 'firstName', operator: '=', value: 'Steve' }] },
// 'and',
// { not: true, rules: [{ field: 'firstName', operator: '=', value: 'Vai' }] },
// ]
// };
// ...potential output could be:
// `(First Name is 'Steve') is def the truth, and (Last Name is 'Vai') is so not true`
```
When `not` is falsy but the `combinator` is `"xor"`, `groupSuffix_xor` will be used if it exists. Otherwise it will fall back to the default. If both conditions are true, the order of the suffixes doesn't matter: both "groupSuffix\_not\_xor" and "groupSuffix\_xor\_not" would be valid (although there is no guarantee which one will be used if both are present).
##### Rule separator[](#rule-separator "Direct link to Rule separator")
By default, rules within a group are separated by a comma followed by a space (`, `). Use the `ruleSeparator` translation key to change this. The value should include any trailing space if desired — for example, `'; '` for a semicolon separator, or `'、'` (ideographic comma, no space) for Japanese.
```
formatQuery(query, {
format: 'natural_language',
translations: { ruleSeparator: '、', and: 'かつ' },
});
// `First Name is 'Steve'、かつ Last Name is 'Vai'`
```
##### Between conjunction[](#between-conjunction "Direct link to Between conjunction")
The conjunction between the two values in a "between" expression defaults to the `and` translation (or `"and"` if not set). Use the `betweenAnd` translation key to override it independently. This is useful for languages where the logical conjunction (used between rules) and the range conjunction (used in "between X and Y") are different words.
```
formatQuery(query, {
format: 'natural_language',
translations: { and: 'かつ', betweenAnd: 'と' },
});
// `Age is between '12' と '14'`
// Rules are still joined with 'かつ'
```
##### Constituent particles[](#constituent-particles "Direct link to Constituent particles")
By default, the word order constituents (Subject, Verb, Object) are separated by a single space. Use `afterSubject`, `afterVerb`, and `afterObject` to insert particles or custom separators after each constituent. Each defaults to `' '` (space) when not set.
This is essential for languages that use grammatical particles between constituents — for example, Japanese requires `が` (subject marker) after the subject:
```
formatQuery(query, {
format: 'natural_language',
wordOrder: 'SOV',
translations: { afterSubject: 'が' },
operatorMap: { '=': 'である' },
});
// `First Nameが'Steve' である`
```
##### List separator[](#list-separator "Direct link to List separator")
The values in an "in" or "notin" list are separated by `', '` (comma + space) by default, with an Oxford comma before the final item when there are three or more values. Set the `listSeparator` translation key to change the separator. When a custom `listSeparator` is set, the Oxford comma is automatically disabled.
```
formatQuery(query, {
format: 'natural_language',
translations: { listSeparator: '、', or: 'または' },
});
// `Color is one of the values ('Red'、'Green' または 'Blue')`
```
#### Operator map[](#operator-map "Direct link to Operator map")
`operatorMap` is a map of operators to their natural language equivalents. If the result can differ based on the `valueSource`, the key should map to an array where the second element represents the string to be used when `valueSource` is "field"; the first element will be used in all other cases.
```
formatQuery(query, {
format: 'natural_language',
operatorMap: {
'=': 'is most assuredly',
'!=': ['is not', 'differs from'],
},
});
// `First Name is most assuredly 'Steve', and Last Name differs from First Name`
```
#### Sample language configurations[](#sample-language-configurations "Direct link to Sample language configurations")
These examples show how `wordOrder`, `operatorMap`, and `translations` combine to produce natural-sounding output in different languages. Each example targets a different [constituent word order](https://en.wikipedia.org/wiki/Word_order#Constituent_word_orders) family.
##### Japanese (SOV with particles)[](#japanese-sov-with-particles "Direct link to Japanese (SOV with particles)")
Japanese uses Subject-Object-Verb order and requires the particle `が` after the subject. Ideographic punctuation (`、`) replaces commas, and the `から…の間` pattern is more natural than `と…の間` for ranges.
```
formatQuery(query, {
format: 'natural_language',
wordOrder: 'SOV',
operatorMap: {
'=': ['である', 'と同じ値である'],
'>': ['より大きい', 'の値より大きい'],
beginswith: ['で始まる', 'の値で始まる'],
in: ['のいずれかである', 'と同じ値のいずれかである'],
between: ['の間である', 'の値の間である'],
// ... other operators
},
translations: {
and: 'かつ',
or: 'または',
true: '真',
false: '偽',
ruleSeparator: '、',
betweenAnd: 'から',
afterSubject: 'が',
afterObject: '',
listSeparator: '、',
groupSuffix: '',
},
});
// First Nameが'Stev'で始まる、かつ Ageが'28'より大きい
```
##### Spanish (SVO with translations)[](#spanish-svo-with-translations "Direct link to Spanish (SVO with translations)")
Spanish uses the same SVO order as English, so only `operatorMap` and `translations` are needed — no `wordOrder` change.
```
formatQuery(query, {
format: 'natural_language',
operatorMap: {
'=': ['es', 'es igual al valor de'],
'>': ['es mayor que', 'es mayor que el valor de'],
beginswith: ['comienza con', 'comienza con el valor de'],
in: ['es uno de los valores', 'es igual a un valor en'],
between: ['está entre', 'está entre los valores de'],
// ... other operators
},
translations: {
and: 'y',
or: 'o',
true: 'verdadero',
false: 'falso',
groupSuffix: 'es verdadero',
groupSuffix_not: 'no es verdadero',
},
});
// First Name comienza con 'Stev', y Age es mayor que '28'
```
##### Welsh (VSO)[](#welsh-vso "Direct link to Welsh (VSO)")
Welsh uses Verb-Subject-Object order, placing the operator first.
```
formatQuery(query, {
format: 'natural_language',
wordOrder: 'VSO',
operatorMap: {
'=': ['yw', "yr un fath â'r gwerth yn"],
'>': ['yn fwy na', "yn fwy na'r gwerth yn"],
beginswith: ['yn dechrau gyda', "yn dechrau gyda'r gwerth yn"],
in: ["yn un o'r gwerthoedd", "yr un fath ag un o'r gwerthoedd yn"],
between: ['rhwng', 'rhwng y gwerthoedd yn'],
// ... other operators
},
translations: {
and: 'a',
or: 'neu',
true: 'gwir',
false: 'gau',
betweenAnd: 'a',
groupSuffix: 'yn wir',
groupSuffix_not: 'ddim yn wir',
},
});
// yn dechrau gyda First Name 'Stev', a yn fwy na Age '28'
```
tip
Complete configurations for these languages (plus Korean) are available in the [demo](/demo) — use the language selector on the "Natural language" export tab to preview the output interactively.
### Rule group processor[](#rule-group-processor "Direct link to Rule group processor")
`formatQuery` processes, validates, and augments configuration options before passing the query and "final" options object to the appropriate rule group processor for the requested format.
To leverage this pre-processing but generate custom output, use the `ruleGroupProcessor` option. The function is called with the rule group and "final" prepared options object:
```
ruleGroupProcessor(ruleGroup, finalOptions);
```
> ***Note: The `ruleGroupProcessor` option overrides the `format` option.***
The default rule group processors for each format are available as exports from `react-querybuilder`:
* `defaultRuleGroupProcessorCEL`
* `defaultRuleGroupProcessorElasticSearch`
* `defaultRuleGroupProcessorJSONata`
* `defaultRuleGroupProcessorJsonLogic`
* `defaultRuleGroupProcessorMongoDB`
* `defaultRuleGroupProcessorMongoDBQuery`
* `defaultRuleGroupProcessorNL`
* `defaultRuleGroupProcessorSpEL`
* `defaultRuleGroupProcessorSQL`
* `defaultRuleGroupProcessorParameterized`
* `defaultRuleGroupProcessorTanStackDB`
Use the appropriate default rule group processor as a fallback so your custom processor doesn't need to cover all cases:
```
const query: RuleGroupType = {
combinator: 'and',
not: false,
rules: [
{ combinator: 'and', rules: [] },
// empty rules array ^^^^^^^^^
{ field: 'firstName', operator: 'beginsWith', value: 'S' },
],
};
const customRuleGroupProcessor: RuleGroupProcessor = (ruleGroup, options) => {
if (ruleGroup.rules.length === 0) {
// Normally, empty rule groups are ignored, but here they evaluate to false
return '(1 = 0)';
}
// Defer to the default rule group processor for all other operators
return defaultRuleGroupProcessorSQL(ruleGroup, options);
};
formatQuery(query, { ruleGroupProcessor: customRuleGroupProcessor });
/*
"((1 = 0) and firstName LIKE 'S%')"
*/
```
## Validation[](#validation "Direct link to Validation")
Validation options (`validator` and `fields` – see [Validation](/docs/utils/validation.md)) only affect output when `format` is not "json" or "json\_without\_ids". If the `validator` function returns `false`, the `fallbackExpression` is returned. Otherwise, groups and rules marked as invalid (by the validation map from the `validator` function or field-based `validator` function) are ignored.
Example:
```
const query: RuleGroupType = {
id: 'root',
rules: [
{ id: 'r1', field: 'firstName', value: '', operator: '=' },
{ id: 'r2', field: 'lastName', value: 'Vai', operator: '=' },
],
combinator: 'and',
not: false,
};
// Example 1
// Query is invalid based on the validator function
formatQuery(query, {
format: 'sql',
validator: () => false,
});
/*
"(1 = 1)" <-- see `fallbackExpression` option
*/
// Example 2
// Rule "r1" is invalid based on the validation map
formatQuery(query, {
format: 'sql',
validator: () => ({ r1: false }),
});
/*
"(lastName = 'Vai')" <-- skipped `firstName` rule with `id === 'r1'`
*/
// Example 3
// Rule "r1" is invalid based on the field validator for `firstName`
formatQuery(query, {
format: 'sql',
fields: [{ name: 'firstName', validator: () => false }],
});
/*
"(lastName = 'Vai')" <-- skipped `firstName` rule because field validator returned `false`
*/
```
### Muted rules and groups[](#muted-rules-and-groups "Direct link to Muted rules and groups")
Rules and groups with the `muted` property set to `true` are excluded from output for all formats except "json" and "json\_without\_ids", similar to invalid rules and groups. This allows temporary exclusion of conditions without removing them from the query structure.
```
const query: RuleGroupType = {
combinator: 'and',
rules: [
{ field: 'firstName', operator: '=', value: 'Steve' },
{ field: 'lastName', operator: '=', value: 'Vai', muted: true },
],
};
formatQuery(query, 'sql');
// "(firstName = 'Steve')" - lastName rule is excluded
```
When a group is muted, it's replaced with the [fallback expression](#fallback-expression):
```
const query: RuleGroupType = {
combinator: 'and',
rules: [
{ field: 'firstName', operator: '=', value: 'Steve' },
{
combinator: 'or',
rules: [
{ field: 'lastName', operator: '=', value: 'Vai' },
{ field: 'instrument', operator: '=', value: 'Guitar' },
],
muted: true,
},
],
};
formatQuery(query, 'sql');
// "(firstName = 'Steve' and (1 = 1))" - muted group becomes fallback
```
tip
Enable mute functionality in the UI by setting [`showMuteButtons`](/docs/components/querybuilder.md#showmutebuttons) to `true` on the main `QueryBuilder` component.
### Automatic validation[](#automatic-validation "Direct link to Automatic validation")
To minimize invalid syntax, `formatQuery` performs basic validation for "in", "notIn", "between", and "notBetween" operators for all formats except "json" and "json\_without\_ids", even without specified validator functions or field validators.
* Rules with "in" or "notIn" operators are invalid if the `value` is neither an array with at least one element (`value.length > 0`) nor a non-empty string.
* Rules with "between" or "notBetween" operators are invalid if the `value` is neither an array with at least two elements (`value.length >= 2`) nor a string with at least one comma not at the first or last position (`value.split(',').length >= 2`, and neither element is empty).
* Rules where `field`, `operator`, or `value` match their respective placeholder are invalid:
```
field === placeholderFieldName ||
operator === placeholderOperatorName ||
(placeholderValueName !== undefined && value === placeholderValueName)
```
---
# Framework adapter API
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
`@react-querybuilder/core` contains every derivation the ` ` component performs, with no React dependency. A framework port—Solid, Svelte, Vue, or anything else—can render an equivalent UI by calling these functions directly instead of reimplementing the precedence rules for fields, operators, values, class names, and history.
info
The exports listed on this page are **public API covered by semantic versioning**. They will not be removed or have their signatures changed outside a major release.
The full *runtime* export surface of `@react-querybuilder/core` (type-only exports are not covered) is locked by a test (`packages/core/src/__tests__/publicApi.test.ts`) that fails on both accidental removal *and* accidental addition, so every change to the surface requires a deliberate changelog entry.
## The `/derivations` subpath[](#the-derivations-subpath "Direct link to the-derivations-subpath")
Everything on this page is available from the package root, and—except for `QueryManager`, `formatQuery`, and the parsers—also from `@react-querybuilder/core/derivations`:
```
import { deriveRuleContext, shouldCoalesce } from '@react-querybuilder/core/derivations';
```
The subpath contains the same bindings as the root, minus `QueryManager` and the query formatter. Importing from it guarantees that neither ends up in the bundle. That matters for adapters that own their own state and never construct a `QueryManager`: importing the same names from the root leaves their elimination up to the bundler, which is a property worth having as a contract rather than as an optimization that may or may not happen.
`bun check-derivations-purity` gates the guarantee against the built output, so an import added to a derivation module cannot quietly undo it.
The parsers (`parseSQL`, `parseCEL`, and the rest) have subpaths of their own and are not exported from the root, so they are excluded from `/derivations` as well.
## Option list preparation[](#option-list-preparation "Direct link to Option list preparation")
| Function | Purpose |
| ------------------------ | ---------------------------------------------------------------------------------- |
| `prepareRuleGroup` | Normalizes a query object, assigning `id`s where missing. |
| `prepareOptionList` | Normalizes any option list prop into a `FullOptionList`, applying placeholders. |
| `toFlatOptionArray` | Flattens option groups into a single array. |
| `resolveOperatorList` | The operator list for a field, applying the same precedence as ` `. |
| `resolveDefaultOperator` | The default operator for a field. |
| `resolveValueList` | The value list for a field/operator pair. |
| `resolveValueEditorType` | The value editor type for a field/operator pair. |
| `getValueSourcesUtil` | The value sources available for a field/operator pair. |
| `getMatchModesUtil` | The match modes available for a field. |
| `getRuleDefaultValue` | The default `value` for a rule. |
| `getFieldData` | Looks a field up in a flattened field map, with a minimal fallback. |
## Rule and group derivations[](#rule-and-group-derivations "Direct link to Rule and group derivations")
| Function | Purpose |
| ------------------------------- | ---------------------------------------------------------------------------- |
| `deriveRuleContext` | Everything a rule's UI needs: field data, operators, value editor type, etc. |
| `deriveRuleGroupContext` | The equivalent derivation for a group. |
| `deriveQueryBuilderClassNames` | The resolved class names for every element. |
| `generateAccessibleDescription` | The accessible description for a group. |
| `resolveCandidateQuery` | Resolves a controlled/uncontrolled query prop against the current state. |
## Query manipulation[](#query-manipulation "Direct link to Query manipulation")
`createRule`, `createRuleGroup`, `createQueryActions`, and the query tools `add`, `remove`, `update`, `move`, `insert`, and `group`. See [Query management](/docs/utils/query-management.md) for full signatures.
Path helpers: `findPath`, `findID`, `getPathOfID`, `pathIsDisabled`, `pathIsDisabledByPaths`, `exceedsMaxLevels`.
Every query tool accepts `freeze: false`, which skips the deep freeze applied to the returned query. Frameworks that wrap state in proxies (Vue `reactive`, Solid stores) need that; see [Freezing](/docs/utils/query-management.md#freezing-tools). `QueryManager` accepts an option of the same name, and `setAutoFreeze` is re-exported from immer as a process-wide switch.
## History[](#history "Direct link to History")
| Export | Purpose |
| -------------------------------------------- | ------------------------------------------------------------------- |
| `signatureOf` | Describes what changed between two queries. |
| `structuralSignature` / `unchangedSignature` | Sentinel signatures: shape changed / nothing observable changed. |
| `shouldCoalesce` | Whether a change should be absorbed into the current history entry. |
| `defaultCoalesceMs` / `defaultMaxHistory` | Defaults for the coalescing window and stack depth. |
`shouldCoalesce` is the same predicate `QueryManager` applies internally, so a port that owns its own `past`/`future` stacks records history identically without duplicating the rule:
```
import { shouldCoalesce, signatureOf, unchangedSignature } from '@react-querybuilder/core';
import type { RuleGroupTypeAny } from '@react-querybuilder/core';
let past: RuleGroupTypeAny[] = [];
let future: RuleGroupTypeAny[] = [];
let lastSig = '';
let lastAt = 0;
const record = (prev: RuleGroupTypeAny, next: RuleGroupTypeAny) => {
const sig = signatureOf(prev, next);
// A change with no observable difference is never recorded at all.
if (sig === unchangedSignature) return;
if (!shouldCoalesce(lastSig, sig, lastAt, Date.now())) {
past.push(prev);
future = [];
}
lastSig = sig;
lastAt = Date.now();
};
```
## Configuration[](#configuration "Direct link to Configuration")
| Export | Purpose |
| -------------------- | -------------------------------------------------------------------------- |
| `optionsEqual` | Whether two `QueryManagerOptions` objects describe the same configuration. |
| `valuesEqual` | The underlying comparison, for individual option values. |
| `SubscriptionChange` | The payload passed to `QueryManager` subscribers (type-only). |
Both comparisons treat arrays and plain objects as values—descending into nested objects such as `history` and `translations`—and everything else, functions included, by identity. That split is what lets an options object rebuilt on every render compare equal as long as its data did not change, which every adapter needs: an identity-only comparison makes the effect that calls `reconfigure` self-perpetuating.
[`QueryManager#reconfigure`](/docs/utils/query-management.md#reconfiguration) applies this gate itself, so an adapter can call it unconditionally. `optionsEqual` is exported for adapters that want to skip building the merged options object in the first place.
```
import { optionsEqual } from '@react-querybuilder/core';
// Only builds the merged object when something actually changed.
if (!optionsEqual(manager.getOptions(), nextOptions)) manager.reconfigure(nextOptions);
```
## Controls[](#controls "Direct link to Controls")
| Export | Purpose |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `controlKeys` | The name of every query builder control. |
| `controlPropKeys` | The complete set of prop names each control receives. |
| `controlKind` | Which bulk override (`actionElement` / `valueSelector`) applies to each control, if any. |
`controlPropKeys` lets a port's default controls declare every prop they receive explicitly, instead of relying on framework-specific attribute fall-through. `controlKind` replaces name-suffix sniffing such as `key.endsWith('Action')`, which silently misclassifies controls whose names happen to end the same way.
## Validation and export[](#validation-and-export "Direct link to Validation and export")
`defaultValidator` and `formatQuery` are also framework-agnostic. See [Validation](/docs/utils/validation.md) and [Export](/docs/utils/export.md). `defaultValidator` is in the `/derivations` subpath; `formatQuery` is not, since it is by far the largest thing in the package and most adapters never call it. Import it from the package root or from `@react-querybuilder/core/formatQuery`.
---
# Hooks
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
These hooks are used internally by React Query Builder and exported for use in custom components.
## State access[](#state-access "Direct link to State access")
React Query Builder uses Redux within a custom React context to manage state without interfering with existing Redux stores.
### `useQueryBuilderQuery`[](#usequerybuilderquery "Direct link to usequerybuilderquery")
Retrieves the complete, current query object for the nearest ancestor `QueryBuilder` component.
The optional parameter should only be used when retrieving a query object from a different `QueryBuilder` than the nearest ancestor. It can be either a complete props object as passed to a custom component or any object matching the interface `{ schema: { qbId: string } }`.
```
function useQueryBuilderQuery(props?: { schema: { qbId: string } }): RuleGroupTypeAny;
```
tip
As a React hook, this function must follow the [appropriate rules](https://react.dev/warnings/invalid-hook-call-warning). It provides access to the current query during the render phase, *not in event handlers*.
**To access the current query from an event handler, use `props.schema.getQuery()` instead.**
### `useQueryBuilderHistory`[](#usequerybuilderhistory "Direct link to usequerybuilderhistory")
Records undo/redo history for the query builder with the given `qbId`, and returns controls for navigating it. Available from the `react-querybuilder/history` entry point.
Unlike the other hooks in this section, it does not need to be rendered beneath a `QueryBuilder`, which is what allows external toolbars and keyboard shortcut handlers to drive a query builder's history.
```
function useQueryBuilderHistory(
qbId: string,
options?: { maxHistory?: number; coalesceMs?: number }
): {
undo: () => void;
redo: () => void;
clear: () => void;
canUndo: boolean;
canRedo: boolean;
past: RuleGroupTypeAny[];
future: RuleGroupTypeAny[];
};
```
See [Undo/redo](/docs/tips/undo-redo.md) for the full guide.
### `useQueryBuilderSelector`[](#usequerybuilderselector "Direct link to usequerybuilderselector")
tip
Prefer [`useQueryBuilderQuery`](#usequerybuilderquery) if you only need the query object for the nearest ancestor `QueryBuilder` component.
Returns the current query from the Redux store when used with `getQuerySelectorById` (see example below).
```
function useQueryBuilderSelector(selector: (state: RqbState) => RuleGroupTypeAny): RuleGroupTypeAny;
```
Example:
```
const CustomValueEditor = (props: ValueEditorProps) => {
const fullQuery = useQueryBuilderSelector(getQuerySelectorById(props.schema.qbId));
// Here you can use utilities like `findPath(getParentPath(props.path), fullQuery)`.
// This allows you to, for example, inspect the parent group of the current rule.
// You can then count sibling rules, check for unique `field` selections, etc.
// That information can be used for validation, information to the user, or
// anything else in your render function.
};
```
### `getDispatchQueryById`[](#getdispatchquerybyid "Direct link to getdispatchquerybyid")
Not a hook, but the write-side counterpart to the selectors above. Returns the `dispatchQuery` function for the mounted query builder with the given `qbId`, or `undefined` if no such query builder is mounted.
```
function getDispatchQueryById(qbId: string): DispatchQueryFn | undefined;
```
Updating a query through this function is equivalent to a user edit: the query is applied to the internal store *and* the query builder's `onQueryChange` callback fires, so it works whether the query builder is [controlled or uncontrolled](/docs/components/querybuilder.md#query). That makes it possible to drive a query builder from outside its own component tree—an external toolbar, a keyboard shortcut handler, or the undo/redo controls in [`react-querybuilder/history`](/docs/tips/undo-redo.md), which use it to apply restored queries.
Requires an explicit [`qbId`](/docs/components/querybuilder.md#qbid) prop, since automatically generated identifiers are not discoverable from outside the component.
```
const dispatchQuery = getDispatchQueryById('main');
dispatchQuery?.(add(currentQuery, { field: 'firstName', operator: '=', value: '' }, []));
```
### `useQueryManager`[](#usequerymanager "Direct link to usequerymanager")
Subscribes to a [`QueryManager`](/docs/utils/query-management.md#query-manager) and returns its current query alongside the manager itself, re-rendering whenever the query changes. Returns a `[query, manager]` tuple.
```
function useQueryManager(
manager: QueryManager
): [RG, QueryManager];
function useQueryManager(
query?: RG,
options?: QueryManagerOptions
): [RG, QueryManager];
```
Unlike every other hook in this section, `useQueryManager` does *not* use Redux and does not need to be rendered beneath a `QueryBuilder`. `QueryManager` maintains its own state, so queries managed this way are invisible to [`useQueryBuilderQuery`](#usequerybuilderquery), [`useQueryBuilderSelector`](#usequerybuilderselector), [`useQueryBuilderHistory`](#usequerybuilderhistory), and [`getDispatchQueryById`](#getdispatchquerybyid). Use it to build a headless or fully custom interface; it is not a replacement for the [`QueryBuilder`](/docs/components/querybuilder.md) component.
Pass an existing manager to control its lifetime yourself:
```
const qm = useMemo(() => new QueryManager(initialQuery, { fields }), []);
const CustomUI = () => {
const [query] = useQueryManager(qm);
// ...
};
```
Or let the hook create one. The manager is created on the first render and never recreated, so `query` is an *initial* value and `options` are captured once—later changes to either argument are ignored:
```
const CustomUI = () => {
const [query, qm] = useQueryManager(initialQuery, { fields });
return (
<>
qm.add(qm.createRule())}>Add rule
{JSON.stringify(query, null, 2)}
>
);
};
```
Since the manager is stable, its methods are safe to call from event handlers and to use in dependency arrays. A [batch](/docs/utils/query-management.md#batching) triggers a single re-render regardless of how many changes it contains, and mutations that resolve to a no-op trigger none.
To change the configuration after the first render, call [`reconfigure`](/docs/utils/query-management.md#reconfiguration) on the manager. The hook subscribes to the manager's config version in addition to its query, so a reconfiguration re-renders the component even though the query object is unchanged. A `reconfigure` call that changes nothing is a no-op, so an effect like the one below is safe to run on every render of a component whose `translations` object is rebuilt each time.
```
useEffect(() => {
qm.reconfigure({ translations });
}, [qm, translations]);
```
## Component logic[](#component-logic "Direct link to Component logic")
The core logic of each component is encapsulated in a reusable hook. Each main component is little more than a call to its respective hook plus the JSX that uses the properties returned from that hook. This enables creating a custom presentation layer without copying logic code from the default components.
tip
The `@react-querybuilder/native` package demonstrates this concept well. It calls the hooks from its own query builder, rule group, and rule components, but nests the sub-components within React Native `View` elements instead of HTML `div` elements used by `react-querybuilder` components.
### `useRule`[](#userule "Direct link to userule")
Called by the [`Rule`](/docs/components/rule.md) component. See [source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/hooks/useRule.ts) for returned properties.
```
function useRule(props: RuleProps): {
// See source code for returned properties
};
```
The configuration-dependent half of its result—field data, operators, value editor type, value list, value sources, match modes, and validation result—is derived by the framework-agnostic `deriveRuleContext` utility from `@react-querybuilder/core`. [`QueryManager.getRuleContext()`](/docs/utils/query-management.md#rule-configuration) calls the same utility, so non-React implementations resolve rules identically. Its `classNames` and `outerClassName` come from the equally framework-agnostic [`deriveRuleClassNames`/`deriveRuleOuterClassName`](/docs/utils/query-management.md#classnames).
### `useRuleGroup`[](#userulegroup "Direct link to userulegroup")
Called by the [`RuleGroup`](/docs/components/rulegroup.md) component. See [source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/hooks/useRuleGroup.ts) for returned properties.
```
function useRuleGroup(props: RuleGroupProps): {
// See source code for returned properties
};
```
Its `classNames` and `outerClassName` come from [`deriveRuleGroupClassNames`/`deriveRuleGroupOuterClassName`](/docs/utils/query-management.md#classnames), and its combinator resolution from `getRuleGroupCombinator` (see [`QueryManager.getRuleGroupContext()`](/docs/utils/query-management.md#rule-configuration)).
### `useValueEditor`[](#usevalueeditor "Direct link to usevalueeditor")
Called by the [`ValueEditor`](/docs/components/valueeditor.md) component. Accepts the same `ValueEditorProps` as the component and returns an object with these properties: the value as an array, a multi-value handler, a processed version of the `parseNumbers` prop, and the classname(s) to be applied to each editor in editor series.
```
function useValueEditor(props: ValueEditorProps): {
valueAsArray: any[];
multiValueHandler: (val: string, idx: number) => void;
parseNumberMethod: ParseNumberMethod;
valueListItemClassName: string;
};
```
This hook updates the `value` as a side effect when these conditions are true:
* `skipHook` is `false` (the value editors in the [compatibility packages](/docs/compat.md) set this to `true` to avoid infinite loops)
* `inputType` is `"number"`
* `operator` is something other than `"between"`, `"notBetween"`, `"in"`, or `"notIn"`
* `valueEditorType` is not "multiselect"
* `value` is an array or a string containing a comma (`,`) and at least one non-whitespace character on either side.
If all of these conditions are met, `handleOnChange` will be called with the first element of the array, or any characters before the first comma if `value` is a string.
Everything except that side effect is derived by the framework-agnostic [value editor utilities](/docs/utils/query-management.md#value-editors) from `@react-querybuilder/core`, so an implementation in another framework can reuse the logic without reimplementing it.
### `useValueSelector`[](#usevalueselector "Direct link to usevalueselector")
Called by the [`ValueSelector`](/docs/components/valueselector.md) component. Returns the given value as an array (unchanged if already an array) and a memoized change handler.
```
function useValueSelector(
props: Pick
): {
onChange: (v: string | string[]) => void;
val?: string | any[];
};
```
### `useSelectElementChangeHandler`[](#useselectelementchangehandler "Direct link to useselectelementchangehandler")
Used by the [`ValueSelector`](/docs/components/valueselector.md) component. Returns a memoized change handler designed specifically for HTML ` ` elements.
```
function useSelectElementChangeHandler(props: {
multiple?: boolean;
onChange: (v: string | string[]) => void;
}): (e: ChangeEvent) => void;
```
### `useShiftActions`[](#useshiftactions "Direct link to useshiftactions")
Used by the [`ShiftActions`](/docs/components/shiftactions.md) component. Generates `shiftUp` and `shiftDown` methods to move a rule/group up or down in the query hierarchy, plus `shiftUpDisabled`/`shiftDownDisabled` to indicate whether either button should be disabled (`shiftUpDisabled` is `true` for the first rule/group in the root group; `shiftDownDisabled` is `true` for the last rule/group in the root group).
```
function useShiftActions(
props: { path: Path } & Pick
): {
shiftDown: () => void;
shiftDownDisabled: boolean;
shiftUp: () => void;
shiftUpDisabled: boolean;
};
```
### `useStopEventPropagation`[](#usestopeventpropagation "Direct link to usestopeventpropagation")
Used by the default [`Rule`](/docs/components/rule.md) and [`RuleGroup`](/docs/components/rulegroup.md) components to prevent default behavior and stop event propagation (e.g., a `MouseEvent` after clicking a ``). Takes a function that accepts a `MouseEvent` and context parameters, then returns a new function that calls `event.preventDefault()` and `event.stopPropagation()` before calling the original function with the same arguments.
```
function useStopEventPropagation(
method: (event: React.MouseEvent, context: any) => void
): (event: React.MouseEvent, context: any) => void;
```
This hook is *not* used in `RuleNative` and `RuleGroupNative`—the `Rule` and `RuleGroup` components for `@react-querybuilder/native`.
### `useQueryBuilder`[](#usequerybuilder "Direct link to usequerybuilder")
Returns everything needed to render a [`QueryBuilder`](/docs/components/querybuilder.md) component. Internally, this hook passes the result of [`useQueryBuilderSetup`](#usequerybuildersetup) to [`useQueryBuilderSchema`](#usequerybuilderschema) and returns the result of `useQueryBuilderSchema`. The returned object combines the results of `useQueryBuilderSchema` and `useQueryBuilderSetup` (see below).
As with `useQueryBuilderSchema`, this Hook must be called from a descendant component of `QueryBuilderStateProvider`. See [`QueryBuilder` source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/components/QueryBuilder.tsx) for an example.
info
This hook is unlikely to be necessary unless you're reimplementing the *entire* `QueryBuilder` component structure.
### `useQueryBuilderSetup`[](#usequerybuildersetup "Direct link to usequerybuildersetup")
Called by the internal component rendered by [`QueryBuilder`](/docs/components/querybuilder.md). Merges props and context values with the defaults and generates actions.
info
This hook is unlikely to be necessary unless you're reimplementing the *entire* `QueryBuilder` component structure.
```
function useQueryBuilderSetup(props: QueryBuilderProps): {
qbId: qbId.current;
rqbContext: ReturnType;
fields: OptionList;
fieldMap: Record;
combinators: OptionList;
getOperatorsMain: (field: string) => OptionList;
getRuleDefaultOperator: (field: string) => string;
getValueEditorTypeMain: (field: string, operator: string) => ValueEditorType;
getValueSourcesMain: (field: string, operator: string) => ValueSources;
getValuesMain: (field: string, operator: string) => OptionList;
getRuleDefaultValue: (rule: RuleType) => any;
getInputTypeMain: (field: string, operator: string) => string;
createRule: () => RuleType;
createRuleGroup: () => RuleGroupTypeAny;
};
```
### `useQueryBuilderSchema`[](#usequerybuilderschema "Direct link to usequerybuilderschema")
Called by the internal component rendered by [`QueryBuilder`](/docs/components/querybuilder.md). Returns everything needed to render a wrapper element (e.g., ``) and the root [`RuleGroup`](/docs/components/rulegroup.md) element based on the provided props and the result from [`useQueryBuilderSetup`](#usequerybuildersetup).
This Hook must be called from a descendant component of `QueryBuilderStateProvider`. See [`QueryBuilder` source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/components/QueryBuilder.tsx) for an example.
info
This hook is unlikely to be necessary unless you're reimplementing the *entire* `QueryBuilder` component structure.
```
function useQueryBuilderSchema(
props: QueryBuilderProps,
setup: ReturnType
): QueryBuilderProps & {
actions: QueryActions;
rootGroup: RuleGroupTypeAny;
rootGroupDisabled: RuleGroupTypeAny;
queryDisabled: boolean;
rqbContext: ReturnType;
schema: Schema;
translations: TranslationsFull;
wrapperClassName: string;
dndEnabledAttr: 'enabled' | 'disabled';
inlineCombinatorsAttr: 'enabled' | 'disabled';
combinatorPropObject: Pick;
};
```
## Other utilities[](#other-utilities "Direct link to Other utilities")
### `useAsyncOptionList`[](#useasyncoptionlist "Direct link to useasyncoptionlist")
Augments a `ValueSelectorProps` or `ValueEditorProps` object with [async option loading](/docs/tips/async-option-lists.md).
```
import { type UseAsyncOptionListParams, useAsyncOptionList } from 'react-querybuilder';
const useAsyncOptionListParams: UseAsyncOptionListParams = {
getCacheKey: 'field',
loadOptionList: async (value, { ruleOrGroup }) => {
const response = await fetch(`/api/operators?field=${ruleOrGroup.field}`);
return response.json();
},
};
const AsyncOperatorSelector = (props: ValueSelectorProps) => {
const asyncProps = useAsyncOptionList(props, useAsyncOptionListParams);
return ;
};
const App = () => ;
```
### `useMergedContext`[](#usemergedcontext "Direct link to usemergedcontext")
Merges the values inherited from the nearest ancestor `QueryBuilderContext.Provider` with the current component's props. For `controlClassnames`, `controlElements`, and `translations`, options that are not defined through either context or props will fall back to the defaults.
```
function useMergedContext(props: QueryBuilderContextProps): QueryBuilderContextProps;
```
### `usePreferProp`[](#usepreferprop "Direct link to usepreferprop")
Given a default value, a prop value, and a context value (all `boolean` or `undefined`), returns the first one that is not `undefined` in the order of (1) prop, (2) context, (3) default.
```
function usePreferProp(default: boolean, prop?: boolean, context?: boolean): boolean;
```
### `usePrevious`[](#useprevious "Direct link to useprevious")
Returns the value of a prop or state variable from the previous render.
```
function usePrevious(prop: T): T | null;
```
## Internal[](#internal "Direct link to Internal")
These hooks log error messages to the console in certain situations (only in "development" mode). They encourage correct usage of React Query Builder and aren't intended for use in custom components.
### `useControlledOrUncontrolled`[](#usecontrolledoruncontrolled "Direct link to usecontrolledoruncontrolled")
Logs an error to the console if any of the following are true:
* Both `query` and `defaultQuery` props are defined.
* The `query` prop is defined during one render and undefined in a subsequent render.
* The `query` prop is undefined during one render and defined in a subsequent render.
### `useDeprecatedProps`[](#usedeprecatedprops "Direct link to usedeprecatedprops")
Logs an error to the console if any of the following are true:
* `QueryBuilder` is rendered with `independentCombinators` prop (see [Independent combinators](/docs/components/querybuilder.md#independent-combinators))
* `RuleGroup` is rendered with `combinator` or `rules` props (deprecated in favor of `ruleGroup`)
* `Rule` is rendered with `field`, `operator`, or `value` props (deprecated in favor of `rule`)
### `useReactDndWarning`[](#usereactdndwarning "Direct link to usereactdndwarning")
Logs an error to the console if the `enableDragAndDrop` prop is `true` but the `react-dnd` and `react-dnd-html5-backend` dependencies are not loaded.
---
# Import
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
Import/parser functions convert query strings or objects from specific languages to query objects for ` ` components.
The optional second parameter configures parsing behavior and query generation (see [Configuration](#configuration)).
info
Importing `parse*` functions
Since `parse*` functions are used less frequently and rarely together, they were removed from the main export in version 7.
```
// Version 6 only
-import { parseCEL } from '@react-querybuilder/core'
-import { parseJsonLogic } from '@react-querybuilder/core'
-import { parseMongoDB } from '@react-querybuilder/core'
-import { parseSQL } from '@react-querybuilder/core'
// Version 6 or 7
+import { parseCEL } from '@react-querybuilder/core/parseCEL'
+import { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic'
+import { parseMongoDB } from '@react-querybuilder/core/parseMongoDB'
+import { parseSQL } from '@react-querybuilder/core/parseSQL'
// (New in version 7)
+import { parseSpEL } from '@react-querybuilder/core/parseSpEL'
+import { parseJSONata } from '@react-querybuilder/core/parseJSONata'
// (New in version 8)
+import { parseCypher } from '@react-querybuilder/core/parseCypher'
+import { parseGQL } from '@react-querybuilder/core/parseGQL'
+import { parseSPARQL } from '@react-querybuilder/core/parseSPARQL'
+import { parseGremlin } from '@react-querybuilder/core/parseGremlin'
```
These functions were available as separate exports in version 6 (along with [`formatQuery`](/docs/utils/export.md) and [`transformQuery`](/docs/utils/misc.md#transformquery)) but could also be imported from `"react-querybuilder"`. In version 7, they're *only* available as separate exports. (This reduced the main bundle size by almost 50%.)
## SQL[](#sql "Direct link to SQL")
```
import { parseSQL } from '@react-querybuilder/core/parseSQL';
function parseSQL(sql: string, options?: ParseSQLOptions): RuleGroupTypeAny;
```
`parseSQL` accepts either a SQL `SELECT` statement or `WHERE` clause.
Click the "Import SQL" button in [the demo](/demo) to try it out.
### Options[](#options "Direct link to Options")
Beyond standard [configuration](#configuration) options, `parseSQL` accepts these options for handling named or anonymous bind variables in SQL strings:
* `params` (`any[] | Record`): An array of parameter values or a parameter-to-value mapping object.
* `paramPrefix` (`string`): Ignores this string at the beginning of parameter identifiers when matching to parameter names in the `params` object.
* `parseParameters` (`boolean | { prefix?: string | string[]; positional?: boolean }`): Preserves unresolved bind variables as parameter rules (`valueSource: 'parameter'`) instead of resolving them via `params`. Pass `true` to accept the default named prefix `':'` and positional `?`, or an object to configure one or more named `prefix`es (e.g. `'@'`, `'$'`) and toggle `positional` (default enabled). Positional `?` placeholders are named by 1-based ordinal (`?` → `1`). `params` substitution takes precedence when both are set.
* `getExpression` (`(node, ctx) => ExpressionNode | null`): A handler that converts an arithmetic/function operand subtree into an [expression](/docs/expr.md) node (`valueSource: 'expression'`, or a `lhs` when on the left of a comparison). Returning `null` drops the rule. Use [`expressionParserSQL`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in SQL inverse, or `getExpressionParserSQL` to add custom functions/operators.
### Usage[](#usage "Direct link to Usage")
All these statements produce the same result:
```
parseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`);
parseSQL(`SELECT * FROM t WHERE firstName = ? AND lastName = ?`, {
params: ['Steve', 'Vai'],
});
parseSQL(`SELECT * FROM t WHERE firstName = :p1 AND lastName = :p2`, {
params: { p1: 'Steve', p2: 'Vai' },
});
parseSQL(`SELECT * FROM t WHERE firstName = $p1 AND lastName = $p2`, {
params: { p1: 'Steve', p2: 'Vai' },
paramPrefix: '$',
});
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
tip
Since v5.0, `parseSQL` detects `XOR` operators and converts them to rule groups with the "xor" combinator. Since "xor" isn't in `defaultCombinators`, specify `defaultCombinatorsExtended` in your ` ` props if the original SQL might contain `XOR` clauses.
```
import { parseSQL } from '@react-querybuilder/core/parseSQL';
import { defaultCombinatorsExtended, QueryBuilder } from 'react-querybuilder';
const query = parseSQL(`SELECT * FROM tbl WHERE a = 'b' XOR c = 'd';`);
const App = () => {
return (
);
};
```
## MongoDB[](#mongodb "Direct link to MongoDB")
```
import { parseMongoDB } from '@react-querybuilder/core/parseMongoDB';
function parseMongoDB(
mongoDbQuery: string | Record,
options?: ParseMongoDbOptions
): RuleGroupTypeAny;
```
`parseMongoDB` accepts a MongoDB query as either a JSON object or `JSON.parse`-able string.
Click the "Import MongoDB" button in [the demo](/demo) to try it out.
### Usage[](#usage-1 "Direct link to Usage")
```
parseMongoDB(`{ "firstName": "Steve", "lastName": { $eq: "Vai" } }`);
// OR
parseMongoDB({ firstName: 'Steve', lastName: { $eq: 'Vai' } });
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
### Custom operators[](#custom-operators "Direct link to Custom operators")
`parseMongoDB` identifies and processes custom operators with the `additionalOperators` option. This option maps operators to their processing functions. Functions receive the operator, associated value, and other options, then should return `RuleType` or `RuleGroupType`. (Don't return `RuleGroupTypeIC`, even with [independent combinators](/docs/components/querybuilder.md#independent-combinators). If `independentCombinators` is `true`, `parseMongoDB` converts the final query to `RuleGroupTypeIC` before returning.)
Example:
```
parseMongoDB(
{
$myCustomOp: ['Vai', 'Vaughan'],
},
{
additionalOperators: {
$myCustomOp: (_op, val) => ({
field: 'lastName',
operator: 'in',
value: val,
}),
},
}
);
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "lastName",
"operator": "in",
"value": ["Vai", "Vaughan"]
}
]
}
```
tip
Valid MongoDB query strings may not strictly conform to JSON. To handle extended formats, pre-parse query strings with a library like [mongodb-query-parser](https://www.npmjs.com/package/mongodb-query-parser) before passing them to `parseMongoDB`.
### Expressions[](#expressions "Direct link to Expressions")
`parseMongoDB` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts a MongoDB `$expr` aggregation-expression operand into an [expression](/docs/expr.md) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Only `$expr` operands are routed through this handler; bare field references and literals fall through to the stock logic. Returning `null` drops the rule. Use [`expressionParserMongoDB`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in MongoDB inverse, or `getExpressionParserMongoDB` to add custom operations.
```
import { parseMongoDB } from '@react-querybuilder/core/parseMongoDB';
import { expressionParserMongoDB } from '@react-querybuilder/expr';
const query = parseMongoDB(mongoDbQuery, { getExpression: expressionParserMongoDB });
```
## JsonLogic[](#jsonlogic "Direct link to JsonLogic")
```
import { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic';
function parseJsonLogic(
jsonLogic: string | JsonLogic,
options?: ParseJsonLogicOptions
): RuleGroupTypeAny;
```
`parseJsonLogic` accepts a [JsonLogic](https://jsonlogic.com/) object or `JSON.parse`-able string.
Click the "Import JsonLogic" button in [the demo](/demo) to try it out.
### Usage[](#usage-2 "Direct link to Usage")
```
parseJsonLogic(
`{ "and": [{ "===": [{ "var": "firstName" }, "Steve"] }, { "===": [{ "var": "lastName" }, "Vai"] }] }`
);
// OR
parseJsonLogic({
and: [{ '===': [{ var: 'firstName' }, 'Steve'] }, { '===': [{ var: 'lastName' }, 'Vai'] }],
});
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
### Expressions[](#expressions-1 "Direct link to Expressions")
`parseJsonLogic` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](/docs/expr.md) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserJsonLogic`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in JsonLogic inverse, or `getExpressionParserJsonLogic` to add custom operations.
```
import { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic';
import { expressionParserJsonLogic } from '@react-querybuilder/expr';
const query = parseJsonLogic(jsonLogic, { getExpression: expressionParserJsonLogic });
```
### Custom operations[](#custom-operations "Direct link to Custom operations")
By default, `parseJsonLogic` handles standard JsonLogic operations that correspond to default React Query Builder operators. Use the `jsonLogicOperations` option to handle custom operations.
`jsonLogicOperations` is `Record RuleType | RuleGroupTypeAny>`. Keys are custom operations; values are functions returning a rule or group.
note
Including standard JsonLogic operations as keys in `jsonLogicOperations` overrides the default `parseJsonLogic` behavior for those operations.
This example uses a custom "regex" operation to produce a rule with the "contains" operator, using the regular expression's `source` property as the `value`.
```
parseJsonLogic(
{ regex: [{ var: 'firstName' }, /^Stev/] },
{
jsonLogicOperations: {
regex: val => ({ field: val[0].var, operator: 'contains', value: val[1].source }),
},
}
);
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "contains",
"value": "^Stev"
}
]
}
```
## Spring Expression Language (SpEL)[](#spring-expression-language-spel "Direct link to Spring Expression Language (SpEL)")
```
import { parseSpEL } from '@react-querybuilder/core/parseSpEL';
function parseSpEL(spelQuery: string, options?: ParseSpELOptions): RuleGroupTypeAny;
```
`parseSpEL` accepts a [SpEL](https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html) string.
Click the "Import SpEL" button in [the demo](/demo) to try it out.
### Usage[](#usage-3 "Direct link to Usage")
```
parseSpEL(`firstName == "Steve" && lastName == "Vai"`);
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
### Expressions[](#expressions-2 "Direct link to Expressions")
`parseSpEL` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic, method call, or function call operand subtree into an [expression](/docs/expr.md) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserSpEL`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in SpEL inverse, or `getExpressionParserSpEL` to add custom operators, functions, and methods. Infix arithmetic, `T(java.lang.Math).abs/min/max(...)` static calls, and `.toUpperCase()`/`.toLowerCase()` instance methods are all invertible; see the [expr docs](/docs/expr.md#spel) for details.
```
import { parseSpEL } from '@react-querybuilder/core/parseSpEL';
import { expressionParserSpEL } from '@react-querybuilder/expr';
const query = parseSpEL(spelQuery, { getExpression: expressionParserSpEL });
```
## Common Expression Language (CEL)[](#common-expression-language-cel "Direct link to Common Expression Language (CEL)")
```
import { parseCEL } from '@react-querybuilder/core/parseCEL';
function parseCEL(celQuery: string, options?: ParseCELOptions): RuleGroupTypeAny;
```
`parseCEL` accepts a [CEL](https://cel.dev) string.
Click the "Import CEL" button in [the demo](/demo) to try it out.
### Usage[](#usage-4 "Direct link to Usage")
```
parseCEL(`firstName == "Steve" && lastName == "Vai"`);
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
### Custom expressions[](#custom-expressions "Direct link to Custom expressions")
Provide a `customExpressionHandler` function to process any AST fragments not recognized by the default parser.
Example:
```
parseCEL('opted_in_at.isBirthday(-1)', {
customExpressionHandler: expr => ({
field: expr.left.value,
operator: expr.right.value,
value: expr.list.value[0].value,
}),
});
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "opted_in_at",
"operator": "isBirthday",
"value": -1
}
]
}
```
To assist with processing the AST fragments, all types, type guard functions, and other utilities used internally by `parseCEL` are exported.
### Expressions[](#expressions-3 "Direct link to Expressions")
`parseCEL` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](/docs/expr.md) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserCEL`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in CEL inverse, or `getExpressionParserCEL` to add custom functions. Note that `abs`/`upper`/`lower` are not invertible on the CEL side—see the [expr docs](/docs/expr.md#cel) for details.
```
import { parseCEL } from '@react-querybuilder/core/parseCEL';
import { expressionParserCEL } from '@react-querybuilder/expr';
const query = parseCEL(celQuery, { getExpression: expressionParserCEL });
```
## JSONata[](#jsonata "Direct link to JSONata")
```
import { parseJSONata } from '@react-querybuilder/core/parseJSONata';
function parseJSONata(jsonataQuery: string, options?: ParseJSONataOptions): RuleGroupTypeAny;
```
`parseJSONata` accepts a [JSONata](https://jsonata.org/) string.
Click the "Import JSONata" button in [the demo](/demo) to try it out.
### Usage[](#usage-5 "Direct link to Usage")
```
parseJSONata(`firstName = "Steve" and lastName in ["Vai", "Vaughan"]`);
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
{
"field": "lastName",
"operator": "in",
"value": ["Vai", "Vaughan"]
}
]
}
```
JSONata lists are always translated to arrays. The [`listsAsArrays` option](#lists-as-arrays) is ignored (effectively always `true`).
### Expressions[](#expressions-4 "Direct link to Expressions")
`parseJSONata` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](/docs/expr.md) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserJSONata`](/docs/expr.md#import-parsing) from `@react-querybuilder/expr` for the built-in JSONata inverse, or `getExpressionParserJSONata` to add custom functions.
```
import { parseJSONata } from '@react-querybuilder/core/parseJSONata';
import { expressionParserJSONata } from '@react-querybuilder/expr';
const query = parseJSONata(jsonataQuery, { getExpression: expressionParserJSONata });
```
## Cypher[](#cypher "Direct link to Cypher")
```
import { parseCypher } from '@react-querybuilder/core/parseCypher';
function parseCypher(cypherQuery: string, options?: ParseCypherOptions): RuleGroupTypeAny;
```
`parseCypher` accepts a [Cypher](https://neo4j.com/docs/cypher-manual/) query string, a `WHERE` clause, or a bare boolean expression. MATCH and RETURN clauses are consumed but discarded — only WHERE conditions are returned.
A `parseGQL` function is also exported since [GQL](https://www.iso.org/standard/76120.html) uses the same expression syntax.
note
`parseCypher` requires the `chevrotain` package (optional peer dependency).
### Usage[](#usage-6 "Direct link to Usage")
```
// Full query — extracts WHERE conditions only
parseCypher('MATCH (n:Person) WHERE n.age > 30 AND n.name CONTAINS "Alice" RETURN n');
// WHERE clause only
parseCypher('WHERE n.age > 30');
// Bare expression
parseCypher('n.age > 30 AND n.name CONTAINS "Alice"');
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{ "field": "n.age", "operator": ">", "value": 30 },
{ "field": "n.name", "operator": "contains", "value": "Alice" }
]
}
```
## SPARQL[](#sparql "Direct link to SPARQL")
```
import { parseSPARQL } from '@react-querybuilder/core/parseSPARQL';
function parseSPARQL(sparqlQuery: string, options?: ParseSPARQLOptions): RuleGroupTypeAny;
```
`parseSPARQL` accepts a [SPARQL](https://www.w3.org/TR/sparql11-query/) query string or a bare `FILTER` expression. Triple patterns (BGPs) are consumed but discarded — only FILTER conditions are returned.
note
`parseSPARQL` requires the `@traqula/parser-sparql-1-2` package (optional peer dependency).
### Usage[](#usage-7 "Direct link to Usage")
```
// Full query — extracts FILTER conditions only
parseSPARQL('SELECT ?x WHERE { ?x foaf:name ?name . FILTER(?age > 30) }');
// Bare FILTER expression (auto-wrapped in a stub query)
parseSPARQL('?age > 30 && ?name != "Alice"');
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{ "field": "?age", "operator": ">", "value": 30 },
{ "field": "?name", "operator": "!=", "value": "Alice" }
]
}
```
## Gremlin[](#gremlin "Direct link to Gremlin")
```
import { parseGremlin } from '@react-querybuilder/core/parseGremlin';
function parseGremlin(gremlinQuery: string, options?: ParseGremlinOptions): RuleGroupTypeAny;
```
`parseGremlin` accepts a [Gremlin](https://tinkerpop.apache.org/) traversal string or a chain of `.has()` steps. Pattern steps (`.hasLabel()`, `.out()`, `.in()`, `.as()`) are consumed but discarded — only `.has()` filter predicates are returned.
### Usage[](#usage-8 "Direct link to Usage")
```
// Full traversal — extracts .has() conditions only
parseGremlin("g.V().hasLabel('Person').has('age', gt(30)).has('name', 'Alice')");
// Bare .has() chain
parseGremlin(".has('age', gt(30)).has('name', 'Alice')");
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [
{ "field": "age", "operator": ">", "value": 30 },
{ "field": "name", "operator": "=", "value": "Alice" }
]
}
```
## react-awesome-query-builder[](#react-awesome-query-builder "Direct link to react-awesome-query-builder")
`parseRAQB` converts a [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB) query tree to an RQB query. It is *not* part of this package—since most projects need it exactly once, it lives in the separate [`@react-querybuilder/migrate-raqb`](https://github.com/react-querybuilder/migrate-raqb) package.
* npm
* Bun
* Yarn
* pnpm
```
npm i @react-querybuilder/migrate-raqb
```
```
bun add @react-querybuilder/migrate-raqb
```
```
yarn add @react-querybuilder/migrate-raqb
```
```
pnpm add @react-querybuilder/migrate-raqb
```
```
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';
const fields = parseRAQBFields(raqbConfig.fields);
const query = parseRAQB(Utils.getTree(immutableTree), { fields });
```
Output (`RuleGroupType`):
```
{
"combinator": "and",
"rules": [{ "field": "price", "operator": ">", "value": 10 }]
}
```
See [Migrating from react-awesome-query-builder](/docs/tips/migrate-from-raqb.md) for the full concept mapping, operator translation table, and options.
## Configuration[](#configuration "Direct link to Configuration")
### Lists as arrays[](#lists-as-arrays "Direct link to Lists as arrays")
To generate arrays instead of comma-separated strings for "in"- and "between"-type operator values, use the `listsAsArrays` option.
```
parseSQL(`SELECT * FROM t WHERE lastName IN ('Vai', 'Vaughan') AND age BETWEEN 20 AND 100`, {
listsAsArrays: true;
});
```
Output:
```
{
"combinator": "and",
"rules": [
{
"field": "lastName",
"operator": "in",
"value": ["Vai", "Vaughan"]
},
{
"field": "age",
"operator": "between",
"value": [20, 100]
}
]
}
```
### Independent combinators[](#independent-combinators "Direct link to Independent combinators")
When `independentCombinators` is `true`, `parse*` functions output queries with combinator identifiers *between* sibling rules/groups instead of at the group level.
```
parseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`, {
independentCombinators: true,
});
```
Output (`RuleGroupTypeIC`):
```
{
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "Steve"
},
"and",
{
"field": "lastName",
"operator": "=",
"value": "Vai"
}
]
}
```
### Fields as value source[](#fields-as-value-source "Direct link to Fields as value source")
When the `fields` option is provided (accepting the same types as the [`fields` prop](/docs/components/querybuilder.md#fields)), `parse*` functions validate clauses with field identifiers to the right of the operator instead of primitive values. A `getValueSources` function (same signature as the [prop](/docs/components/querybuilder.md#getvaluesources)) can also help validate rules.
For such rules to be valid, one of these must be an array including "field": (1) the `getValueSources` return value, (2) the field's `valueSources` function return value, or (3) the field's `valueSources` property. The code below demonstrates all three methods.
```
parseSQL(`SELECT * FROM t WHERE firstName = lastName`, {
fields: [
{ name: 'firstName', label: 'First Name', valueSources: ['value', 'field'] },
{ name: 'lastName', label: 'Last Name', valueSources: () => ['value', 'field'] },
],
getValueSources: () => ['value', 'field'],
});
```
Output:
```
{
"combinator": "and",
"rules": [
{
"field": "firstName",
"operator": "=",
"value": "lastName",
"valueSource": "field"
}
]
}
```
### Generating `id`s[](#generating-ids "Direct link to generating-ids")
When `generateIDs` is `true`, `parse*` functions generate a unique `id` property for the output query object and each nested rule and group using `prepareRuleGroup`.
note
`parse*` functions only validate clauses where "field" is the *only* detected value source. Operators like "between" and "in" must have either only field names or only scalar values to the right of the operator—not mixed. See examples below.
#### Invalid clauses[](#invalid-clauses "Direct link to Invalid clauses")
```
// 1 is a scalar value and `iq` is a field name
parseSQL(`SELECT * FROM tbl WHERE age between 1 and iq`);
// List contains a mix of scalar values and field names
parseSQL(`SELECT * FROM tbl WHERE firstName IN (lastName, 'Steve', 'Stevie')`);
```
#### Valid clauses[](#valid-clauses "Direct link to Valid clauses")
```
// Both are field names
parseSQL(`SELECT * FROM tbl WHERE age between numChildren and iq`);
// Both are scalar values
parseSQL(`SELECT * FROM tbl WHERE age between 26 and 52`);
// All items are field names
parseSQL(`SELECT * FROM tbl WHERE firstName IN (lastName, middleName)`);
// All items are scalar values
parseSQL(`SELECT * FROM tbl WHERE firstName IN ('Steve', 'Stevie')`);
```
---
# Miscellaneous
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
A partial list of exports from `react-querybuilder`.
## Utilities[](#utilities "Direct link to Utilities")
### `transformQuery`[](#transformquery "Direct link to transformquery")
```
function transformQuery(query: RuleGroupTypeAny, options: QueryTransformerOptions): any;
```
This function recursively processes a query object (`RuleGroupType` or `RuleGroupTypeIC`), passing each `RuleType` object to a provided `ruleProcessor` function. Available options include:
* `ruleProcessor`: Custom processing function for each rule.
* `ruleGroupProcessor`: Custom processing function for each rule group. Each group's `rules` property is retained and recursively processed regardless of other mutations.
* `propertyMap`: Keys in rule or group objects that match keys in this object are renamed to the corresponding value.
* `combinatorMap`: Translates combinators; for example, `{and: "&&", or: "||"}` would convert "and"/"or" combinators to "&&"/"||", respectively.
* `operatorMap`: Converts operators that match keys in this object to corresponding values, e.g., `{"=": "=="}`.
* `deleteRemappedProperties`: Defaults to `true`; pass `false` to retain both remapped properties *and* original properties in the resulting object.
See the [test suite](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/transformQuery.test.ts) for example usage.
### `defaultValidator`[](#defaultvalidator "Direct link to defaultvalidator")
```
function defaultValidator(query: RuleGroupTypeAny): {
[id: string]: { valid: boolean; reasons?: string[] };
};
```
Pass `validator={defaultValidator}` to automatically validate groups (rules are ignored). A group is marked invalid when either of these conditions are true:
1. The group has no child rules or groups (`query.rules.length === 0`)
2. The group has a missing/invalid `combinator` property and more than one child rule or group (`rules.length >= 2`)
You can see the default validator in action in the [demo](/demo) by checking the ['Use validation' option](/demo#validateQuery=true). Empty groups display bold text on their "+ Rule" button and a description where rules normally appear.
### `findPath`[](#findpath "Direct link to findpath")
```
function findPath(path: Path, query: RuleGroupTypeAny): RuleType | RuleGroupTypeAny | null;
```
`findPath` finds the rule or group within the query hierarchy that has a given `path`. This is useful in custom [`onAddRule`](/docs/components/querybuilder.md#onaddrule) and [`onAddGroup`](/docs/components/querybuilder.md#onaddgroup) functions.
More extensive documentation on the `path` property is [here](/docs/tips/path.md).
### `findID`[](#findid "Direct link to findid")
```
function findID(id: string, query: RuleGroupTypeAny): RuleType | RuleGroupTypeAny | null;
```
`findID` finds the rule or group within the query hierarchy that has a given `id`.
### `convertQuery`[](#convertquery "Direct link to convertquery")
```
function convertQuery(query: RuleGroupType): RuleGroupTypeIC;
// OR
function convertQuery(query: RuleGroupTypeIC): RuleGroupType;
```
`convertQuery` toggles a query between the conventional `RuleGroupType` structure (with combinators at the group level) and the "independent combinators" structure `RuleGroupTypeIC` (with combinators between every other rule/group).
`convertToIC` and `convertFromIC` perform the same function as `convertQuery` but only in the directions indicated by their names.
## Number parsing[](#number-parsing "Direct link to Number parsing")
HTML ` ` controls store values as strings (even for `type="number"`), but your requirements may call for true numeric values. The [`parseNumbers`](/docs/components/querybuilder.md#parsenumbers) prop on the `QueryBuilder` component and the `formatQuery` function's [`parseNumbers`](/docs/utils/export.md#parse-numbers) option can be used to convert values to numeric types from the UI or during export, respectively. Both configuration options have the same valid values (per the type below) and behave similarly.
`boolean | "enhanced" | "enhanced-limited" | "native" | "native-limited" | "strict" | "strict-limited"`
> *Tip: Try the **`"strict-limited"`** option first.*
* The `"*-limited"` options are equivalent to their non-suffixed counterparts except that numeric parsing is only performed when the value editor's `inputType` is `"number"`.
* Being based on the `inputType`, the `"*-limited"` suffix can only affect `formatQuery` output when a `fields` array is also provided.
* `true`, `"strict"`, and `"enhanced"` will retain the original value when numeric parsing fails.
* `"enhanced"` and `"native"` can lead to information loss since any trailing invalid characters will be removed.
* `true` and `"strict"` determine numericity using [`numeric-quantity`](https://www.npmjs.com/package/numeric-quantity) with `allowTrailingInvalid: false`. Values must be numeric *in their entirety* to be considered numeric, not just *start* with a number as with `parseFloat`.
* `"enhanced"` uses `numeric-quantity` with `allowTrailingInvalid: true`.
* `"native"` uses JavaScript's native `parseFloat` method, which is similar to the "enhanced" algorithm in that it will strip trailing invalid characters, but it will return `NaN` for non-numeric values instead of the original value.
#### Examples[](#examples "Direct link to Examples")
```
const query: RuleGroupType = {
combinator: 'and',
not: false,
rules: [
{ field: 'digits', operator: '=', value: '20' },
{ field: 'age', operator: 'between', value: '26, 52' },
{ field: 'lastName', operator: '=', value: 'Vai' },
],
};
// Default configuration - all values are strings:
formatQuery(query, { format: 'sql' });
// "(digits = '20' and age between '26' and '52' and lastName = 'Vai')"
// `parseNumbers: true` - numeric strings converted to actual numbers:
formatQuery(query, { format: 'sql', parseNumbers: true });
// "(digits = 20 and age between 26 and 52 and lastName = 'Vai')"
```
More about the "strict" option
To avoid information loss, the `true` and `"strict*"` options are more strict about what qualifies as "numeric" than [the standard `parseFloat` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). To oversimplify a bit, `parseFloat` works with any string that *starts* with a numeric sequence, ignoring the rest of the string beginning with the first non-numeric character. In contrast, when `parseNumbers` is `true`, `formatQuery` will only convert a `value` to a `number` if it appears to be numeric *in its entirety* (after trimming whitespace).
Each of the following expressions evaluates to `true`:
```
// Everything after the '3' is ignored by `parseFloat`
parseFloat('000123abcdef') === 123;
// `value` contains non-numeric characters, so remains as-is
formatQuery(
{ rules: [{ field: 'f', operator: '=', value: '000123abcdef' }] },
{ format: 'sql', parseNumbers: true }
) === "(f = '000123abcdef')";
// `value` is wholly numeric (after trimming whitespace) so it gets converted to a number
formatQuery(
{ rules: [{ field: 'f', operator: '=', value: ' 000123 ' }] },
{ format: 'sql', parseNumbers: true }
) === '(f = 123)';
```
## Defaults[](#defaults "Direct link to Defaults")
The default configuration objects are exported for convenience, including the following.
* `defaultCombinators` (see [`combinators` prop](/docs/components/querybuilder.md#combinators))
* `defaultOperators` (see [`operators` prop](/docs/components/querybuilder.md#operators))
* `defaultTranslations` (see [`translations` prop](/docs/components/querybuilder.md#translations))
* `defaultValueProcessor` and variants for non-SQL formats (see [Export](/docs/utils/export.md) > [Value processor](/docs/utils/export.md#value-processor))
* `defaultFields` (see [`fields` prop](/docs/components/querybuilder.md#fields))
* `standardClassnames` (see [CSS classes](/docs/styling/classnames.md))
The default components are also exported:
* [`ActionElement`](/docs/components/actionelement.md) - used for action buttons (to add rules, remove groups, etc.)
* [`DragHandle`](/docs/components/draghandle.md) - used for the drag handle on rules and group headers
* [`InlineCombinator`](/docs/components/rulegroup.md) - used when either [`showCombinatorsBetweenRules`](/docs/components/querybuilder.md#showcombinatorsbetweenrules) is `true` or the query is using independent combinators.
* [`NotToggle`](/docs/components/nottoggle.md) - used for the "Invert this group" toggle switch
* [`Rule`](/docs/components/rule.md) - the default rule component
* [`RuleGroup`](/docs/components/rulegroup.md) - the default rule group component
* [`ShiftActions`](/docs/components/shiftactions.md) - used for the "shift up"/"shift down" buttons when [`showShiftActions`](/docs/components/querybuilder.md#showshiftactions) is `true`
* [`ValueEditor`](/docs/components/valueeditor.md) - the default `valueEditor` component
* [`ValueSelector`](/docs/components/valueselector.md) - used for drop-down lists (combinator, field, and operator selectors)
---
# Query management
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
Utilities for building and modifying query objects programmatically, without the ` ` component.
## Query tools[](#query-tools "Direct link to Query tools")
Several methods are available to assist with programmatic manipulation of query objects. These methods are used by the ` ` component itself, so they're guaranteed to achieve the same result as a corresponding UI-based update. Each method returns the modified query.
Check out the ["External controls" Tips & Tricks page](/docs/tips/external-controls.md) to see these methods used outside the ` ` component context.
For a stateful, chainable wrapper around these methods that holds the query for you, see [`QueryManager`](#query-manager) below.
### `add`[](#add "Direct link to add")
`(query: RuleGroupTypeAny, ruleOrGroup: RuleGroupTypeAny | RuleType, parentPathOrID: Path | string, options: AddOptions) => RuleGroupTypeAny`
Adds a rule or group (and an independent combinator if necessary to keep the query valid) to the group at the specified `path` or with the given `id`.
AddOptions
```
export interface AddOptions extends QueryToolOptions {
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query has independent
* combinators), then the first combinator in this list will be inserted
* before the new rule/group if the parent group is not empty. This option
* is overridden by `combinatorPreceding`.
*/
combinators?: OptionList;
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query has independent
* combinators), then this combinator will be inserted before the new rule/group
* if the parent group is not empty. This option will supersede `combinators`.
*/
combinatorPreceding?: string;
/**
* ID generator.
*/
idGenerator?: () => string;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L190-L208](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L190-L208)*
### `remove`[](#remove "Direct link to remove")
`(query: RuleGroupTypeAny, pathOrID: Path | string, options: RemoveOptions) => RuleGroupTypeAny`
Removes a rule or group (and the preceding independent combinator if one exists) at the specified `path` or with the given `id`.
### `update`[](#update "Direct link to update")
`(query: RuleGroupTypeAny, prop: string, value: any, pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`
Updates a property of a rule or group, or an independent combinator, at the specified `path` or with the given `id`.
Multiple properties can be updated at once by passing either a property-to-value map or parallel arrays of property names and values (note that `pathOrID` shifts to the third argument for the map form):
`(query: RuleGroupTypeAny, props: UpdateValueMap, pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`
`(query: RuleGroupTypeAny, props: string[], values: any[], pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`
```
// Equivalent ways to set `valueSource` and `value` in a single call:
update(query, { valueSource: 'field', value: 'otherField' }, rulePath);
update(query, ['valueSource', 'value'], ['field', 'otherField'], rulePath);
```
Regardless of the order in which properties are listed, `field`, `operator`, and `valueSource` are applied before `value`. This ensures an explicitly-provided `value` is never reset by a change to one of those properties (e.g. updating `field` normally resets `value`).
UpdateOptions
```
export interface UpdateOptions extends QueryToolOptions {
/**
* When updating the `field` of a rule, the rule's `operator`, `value`, and `valueSource`
* will be reset to their respective defaults. Defaults to `true`.
*/
resetOnFieldChange?: boolean;
/**
* When updating the `operator` of a rule, the rule's `value` and `valueSource`
* will be reset to their respective defaults. Defaults to `false`.
*/
resetOnOperatorChange?: boolean;
/**
* Determines the default operator name for a given field.
*/
getRuleDefaultOperator?: (field: string) => string;
/**
* Determines the valid value sources for a given field and operator.
*/
getValueSources?: (field: string, operator: string) => ValueSources | ValueSourceFlexibleOptions;
/**
* Gets the default value for a given rule, in case the value needs to be reset.
*/
// oxlint-disable-next-line typescript/no-explicit-any
getRuleDefaultValue?: (rule: RuleType) => any;
/**
* Determines the valid match modes for a given field.
*/
getMatchModes?: (field: string) => MatchModeOptions;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L305-L333](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L305-L333)*
### `move`[](#move "Direct link to move")
`(query: RuleGroupTypeAny, oldPathOrID: Path | string, newPath: Path | 'up' | 'down', options: MoveOptions) => RuleGroupTypeAny`
Moves (or clones with a new `id`) a rule or group at the specified `path` or with the given `id` to a new location in the query tree. Using `"up"` or `"down"` as the `newPath` will "shift" the rule or group higher or lower, respectively.
MoveOptions
```
export interface MoveOptions extends QueryToolOptions {
/**
* When `true`, the source rule/group will not be removed from its original path.
*/
clone?: boolean;
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query is using independent
* combinators), then the first combinator in this list will be inserted before
* the rule/group if necessary.
*/
combinators?: OptionList;
/**
* ID generator.
*/
idGenerator?: () => string;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L789-L804](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L789-L804)*
### `insert`[](#insert "Direct link to insert")
`(query: RuleGroupTypeAny, ruleOrGroup: RuleGroupTypeAny | RuleType, path: Path, options: InsertOptions) => RuleGroupTypeAny`
Inserts a rule or group (and an independent combinator if necessary to keep the query valid) at the specified `path`.
InsertOptions
```
export interface InsertOptions extends QueryToolOptions {
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query has independent
* combinators), then the first combinator in this list will be inserted
* before the new rule/group if the parent group is not empty. This option
* is overridden by `combinatorPreceding`.
*/
combinators?: OptionList;
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query has independent
* combinators), then this combinator will be inserted before the new rule/group
* if the parent group is not empty and the new rule/group is not the first in the
* group (`path.at(-1) > 0`). This option will supersede `combinators`.
*/
combinatorPreceding?: string;
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query has independent
* combinators), then this combinator will be inserted after the new rule/group
* if the parent group is not empty and the new rule/group is the first in the
* group (`path.at(-1) === 0`). This option will supersede `combinators`.
*/
combinatorSucceeding?: string;
/**
* ID generator.
*
* @default generateID
*/
idGenerator?: () => string;
/**
* When `true`, the new rule/group will replace the rule/group at `path`.
*/
replace?: boolean;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L997-L1029](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L997-L1029)*
### `group`[](#group "Direct link to group")
`(query: RuleGroupTypeAny, sourcePathOrID: Path | string, targetPathOrID: Path | string, options: GroupOptions) => RuleGroupTypeAny`
Creates a new group at the target `path` (based on the specified `path` or given `id`) containing the existing rule/group at that path and the rule/group at the source path (or with the source `id`), in that order, in its `rules` array.
GroupOptions
```
export interface GroupOptions extends QueryToolOptions {
/**
* When `true`, the source rule/group will not be removed from its original path.
*/
clone?: boolean;
/**
* If the query extends `RuleGroupTypeIC` (i.e. the query is using independent
* combinators), then the first combinator in this list will be inserted between
* the two rules/groups.
*/
combinators?: OptionList;
/**
* ID generator.
*/
idGenerator?: () => string;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L1147-L1162](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L1147-L1162)*
### Aborted operations[](#aborted-operations "Direct link to Aborted operations")
Query tools never throw. When an operation can't be carried out—the `path`/`id` doesn't resolve, the parent is a rule instead of a group, the target is the root group, and so on—the original query is returned unmodified. Since the return value is the same object either way, that outcome is indistinguishable from a successful call that happened to change nothing.
Pass `onAbort` (accepted by every query tool) to find out which happened and why:
```
const newQuery = remove(query, 'some-id', {
onAbort: ({ reason, operation, pathOrID }) => {
console.warn(`${operation} aborted: ${reason}`, pathOrID);
},
});
```
AbortOptions
```
export interface AbortOptions extends GuardOptions {
/**
* Called when the operation returns the query unmodified, with the reason why. Query tools
* never throw, so this is the only way to distinguish "the target was invalid" from
* "the operation had nothing to do".
*/
onAbort?: (info: AbortInfo) => void;
}
```
> *Source: [/packages/core/src/utils/queryTools.ts#L123-L130](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/queryTools.ts#L123-L130)*
`reason` is one of:
| `AbortReason` | Meaning |
| ------------------------- | --------------------------------------------------------------------- |
| `"target-not-found"` | The rule/group identified by the given path or `id` does not exist. |
| `"parent-not-found"` | The parent group identified by the given path or `id` does not exist. |
| `"parent-not-a-group"` | The given parent path or `id` refers to a rule rather than a group. |
| `"destination-not-found"` | The destination's parent group does not exist. |
| `"root-not-allowed"` | The root group cannot be removed, moved, or grouped. |
| `"not-a-combinator-slot"` | In an independent combinators query, the target index holds a rule. |
| `"same-location"` | The rule/group is already at the destination. *Not an error.* |
| `"no-change"` | The property already has the given value. *Not an error.* |
| `"target-disabled"` | The target is disabled, or descends from a disabled group. |
| `"parent-disabled"` | The parent group is disabled, or descends from a disabled group. |
| `"max-levels-exceeded"` | Adding the group would nest it deeper than `maxLevels` allows. |
[`QueryManager`](#query-manager)'s [strict mode](#strict-mode) builds on this channel.
### Guards[](#guards "Direct link to Guards")
The last three reasons above are produced by opt-in guards, accepted by every query tool alongside `onAbort`:
* `respectDisabled` — abort when the target (or, for `add`/`insert`, the parent) is disabled, either directly or by descending from a disabled group. Defaults to `false`.
* `queryDisabled` — abort every mutation, as though the whole query were disabled. Defaults to `false`.
* `maxLevels` — the maximum depth at which a *group* may be added by `add` or `insert`. Rules are unaffected. Defaults to `Infinity`.
`disabled` is a property of the query itself, so honoring it is a matter of data integrity rather than presentation—a query saved with a locked rule should still be locked when it is loaded again. It is opt-in here only to preserve the existing behavior of the standalone tools; [`QueryManager`](#query-manager) enables it by default.
Updating a rule or group's own `disabled` property is always permitted under `respectDisabled`, since that is the only way to re-enable it. `queryDisabled` blocks even that.
```
// Aborts: the rule at [0] is disabled
update(query, 'value', 'x', [0], { respectDisabled: true });
// Succeeds: re-enabling is always allowed
update(query, 'disabled', false, [0], { respectDisabled: true });
```
`getGuardAbortReason(query, pathOrID, guards, { asParent })` and `exceedsMaxLevels(parentPath, guards)` are exported so a UI layer that runs its own logic before mutating—invoking a confirmation callback, for example—can apply the same rules without duplicating them.
### Freezing (tools)[](#freezing-tools "Direct link to Freezing (tools)")
The query tools produce their result with [immer](https://immerjs.github.io/immer/), which deep-freezes it. That makes accidental mutation throw in strict mode, but some frameworks—Vue with `reactive`, Solid with stores—cannot wrap a frozen object. Pass `freeze: false` to opt out:
```
const nextQuery = add(query, rule, [], { freeze: false });
Object.isFrozen(nextQuery); // false
```
Structural sharing is unaffected, so reference comparison still works. The `*InPlace` variants never freeze anything, so the option is inert for them.
`setAutoFreeze(false)` (re-exported from immer) disables freezing process-wide, including for `produce` calls you make yourself. Prefer the per-call option unless you need that.
## Query manager[](#query-manager "Direct link to Query manager")
`QueryManager` is a stateful wrapper around the [query tools](#query-tools) above. It holds the query internally, so each method takes the same arguments as its query tool counterpart minus the leading `query` parameter, and returns the manager itself for chaining. It has no React dependency, so it can be used on the server, in a CLI, or anywhere else a query needs to be built or modified programmatically.
```
import { QueryManager } from 'react-querybuilder';
// or: import { QueryManager } from '@react-querybuilder/core';
const q = new QueryManager(undefined, { fields });
q.add({ field: 'firstName', operator: '=', value: 'Steve' })
.add({ field: 'lastName', operator: '=', value: 'Vai' })
.update('operator', 'contains', 'rule-id-1');
q.validate(); // boolean | ValidationMap
q.format('sql'); // string
q.getQuery(); // RuleGroupType
```
### Constructor[](#constructor "Direct link to Constructor")
`new QueryManager(query?: RuleGroupTypeAny, options?: QueryManagerOptions)`
Without a `query`, an empty group is created using the configured `combinators` and `addRuleToNewGroups` options. With one, every rule and group is assigned an `id` if it doesn't already have one. Pass a `RuleGroupTypeIC` to manage a query with independent combinators.
QueryManagerOptions
```
export interface QueryManagerOptions<
F extends FullField = FullField,
O extends FullOperator = FullOperator,
C extends FullCombinator = FullCombinator,
> {
/** The fields available for rules. Accepts the same shapes as the `fields` prop. */
fields?: FlexibleOptionListProp | BaseOptionMap;
/** The operators available for rules. Accepts the same shapes as the `operators` prop. */
operators?: FlexibleOptionListProp | BaseOptionMap;
/** The combinators available for groups. Defaults to `defaultCombinators`. */
combinators?: FlexibleOptionListProp | BaseOptionMap;
/** Properties applied to every field in `fields`. */
baseField?: Record;
/** Properties applied to every operator in `operators`. */
baseOperator?: Record;
/** Properties applied to every combinator in `combinators`. */
baseCombinator?: Record;
/** When `false`, an empty placeholder option is prepended to the field list. */
autoSelectField?: boolean;
/** When `false`, an empty placeholder option is prepended to each operator list. */
autoSelectOperator?: boolean;
/** When `false`, an empty placeholder option is prepended to each value list. */
autoSelectValue?: boolean;
/**
* Translations, accepting the same shape as the `translations` prop. Only the placeholder
* properties of `fields`, `operators`, and `values` are used, and only when the corresponding
* `autoSelect*` option is `false`; the remaining keys describe UI elements that have no
* meaning outside the `QueryBuilder` component. Labels are typed as `unknown` so the React
* `Translations` type, whose labels are `ReactNode`, can be passed as-is.
*/
translations?: Partial>;
/** The default `field` for rules created by {@link QueryManager.createRule}. */
getDefaultField?: DefaultFieldProp;
/** The default `operator` for a given field. */
getDefaultOperator?: DefaultOperatorProp;
/** Overrides the computed default `value` for a new rule. */
getDefaultValue?: (rule: RuleType, misc: { fieldData: F }) => unknown;
/** The operators available for a given field. */
getOperators?: (field: string, misc: { fieldData: F }) => FlexibleOptionList | null;
/** The value editor type for a given field/operator, which informs the default value. */
getValueEditorType?: (field: string, operator: string, misc: { fieldData: F }) => ValueEditorType;
/** The value list for a given field/operator, which informs the default value. */
getValues?: (
field: string,
operator: string,
misc: { fieldData: F }
) => FlexibleOptionList;
/** The valid value sources for a given field/operator. */
getValueSources?: (
field: string,
operator: string,
misc: { fieldData: F }
) => ValueSources | ValueSourceFlexibleOptions;
/** The valid match modes for a given field. */
getMatchModes?: (
field: string,
misc: { fieldData: F }
) => boolean | MatchMode[] | FlexibleOption[];
/** The named parameters available for a given field/operator. */
getParameters?: (
field: string,
operator: string,
misc: { fieldData: F }
) => FlexibleOptionList | null;
/** When `true`, multi-value defaults are arrays instead of comma-joined strings. */
listsAsArrays?: boolean;
/** When `true`, groups created by {@link QueryManager.createRuleGroup} contain one new rule. */
addRuleToNewGroups?: boolean;
/**
* When updating a rule's `field`, reset its `operator`, `value`, and `valueSource` to their
* defaults. Defaults to `true`, matching the `QueryBuilder` prop of the same name.
*/
resetOnFieldChange?: boolean;
/**
* When updating a rule's `operator`, reset its `value` to the default. Defaults to `false`,
* matching the `QueryBuilder` prop of the same name.
*/
resetOnOperatorChange?: boolean;
/**
* The maximum depth at which groups may be added. As with the `QueryBuilder` prop of the same
* name, a non-positive value means unlimited. Defaults to `Infinity`.
*/
maxLevels?: number;
/**
* Honor `disabled` properties within the query, so mutations targeting a disabled rule or
* group (or a descendant of a disabled group) are aborted. Updating a node's own `disabled`
* property is always permitted. Defaults to `true`, matching the `QueryBuilder` component;
* pass `false` to mutate freely regardless of the property.
*/
respectDisabled?: boolean;
/**
* Paths that are disabled without the corresponding rule or group carrying a `disabled`
* property. This mirrors the array form of the `QueryBuilder` `disabled` prop
* (e.g. `disabled={[[2]]}`), which disables nodes by position rather than by data.
*
* A path is treated as disabled if it appears here or descends from a path that does. Honored
* only when `respectDisabled` is `true`; as with the `disabled` property, a node's own
* `disabled` can always be changed so it is never permanently locked.
*/
disabledPaths?: Path[];
/** Abort every mutation, as though the entire query were disabled. Defaults to `false`. */
queryDisabled?: boolean;
/** The input type for a given field/operator, surfaced by {@link QueryManager.getRuleContext}. */
getInputType?: (field: string, operator: string, misc: { fieldData: F }) => InputType | null;
/** Extra props for a subquery builder, surfaced by {@link QueryManager.getRuleContext}. */
getSubQueryBuilderProps?: (field: string, misc: { fieldData: F }) => Record;
/**
* Enables undo/redo recording. Pass `true` for the defaults, or an object to configure
* `maxHistory` and/or `coalesceMs`. Disabled by default, so instances that never undo
* retain no extra references.
*/
history?: boolean | QueryHistoryOptions;
/**
* Throw a {@link QueryManagerError} when a mutation is aborted because its target could not
* be used. Disabled by default, in which case such mutations are silent no-ops. Can be
* overridden per call.
*/
strict?: boolean;
/**
* Called whenever a mutation is aborted, including for the non-error reasons
* `"same-location"` and `"no-change"`. Can be overridden per call.
*/
onInvalidTarget?: (info: AbortInfo) => void;
/**
* Deep-freeze everything the manager hands out—the query, the field list, the field map, and
* the combinator list—so accidental mutation throws in strict mode. Defaults to `true`.
*
* Pass `false` when the manager's output is handed to a framework that wraps objects in
* proxies (Vue `reactive`, Solid stores) or otherwise needs to mutate them. This also disables
* immer's auto-freeze for mutations made through the manager.
*
* The shallow copy returned by {@link QueryManager.getOptions} is frozen either way: it is a
* one-level copy that no framework proxy is placed inside, so freezing it costs nothing and
* still prevents callers from mutating the options snapshot they were handed.
*/
freeze?: boolean;
/** Validates the query. Defaults to {@link defaultValidator}. */
validator?: QueryValidator;
/** Generates `id` properties for new rules and groups. Defaults to {@link generateID}. */
idGenerator?: () => string;
/**
* Clock used to time history coalescing. Defaults to `Date.now`.
*
* @internal Test seam. Exists so that history recording can be compared against the
* `react-querybuilder/history` implementation without depending on wall-clock timing.
*/
now?: () => number;
}
```
> *Source: [/packages/core/src/utils/QueryManager.ts#L174-L321](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/QueryManager.ts#L174-L321)*
The constructor also accepts the [guard options](#guards) `respectDisabled` (defaulting to **`true`** here, matching the `QueryBuilder` component), `queryDisabled`, and `maxLevels`, plus `resetOnFieldChange` (default `true`) and `resetOnOperatorChange` (default `false`), which mirror the props of the same names.
### Freezing (manager)[](#freezing-manager "Direct link to Freezing (manager)")
Everything the manager hands out directly—the query, the field list, the field map, and the combinator list—is deep-frozen. Pass `freeze: false` to disable that, along with immer's auto-freeze for mutations made through the manager:
```
const q = new QueryManager(query, { fields, freeze: false });
Object.isFrozen(q.getQuery()); // false
```
This is what makes a manager's output usable with a framework that wraps objects in proxies. The one exception is `getOptions()`, which is a one-level copy that no proxy is placed inside; it stays frozen either way, so callers can't mutate the options snapshot they were handed.
Note that under `freeze: false`, "a query previously handed out by `getQuery` is never modified" remains true—mutations still go through the non-`InPlace` tools—but it is a convention rather than a runtime-enforced guarantee.
### State access[](#state-access "Direct link to State access")
* `getQuery(): RuleGroupTypeAny` — The current query. The returned object is structurally shared, so it's safe to retain and compare by reference to detect changes. It's also frozen unless the [`freeze` option](#freezing-manager) is `false`.
* `setQuery(query: RuleGroupTypeAny)` — Replaces the current query, assigning `id`s as needed.
### Factories[](#factories "Direct link to Factories")
* `createRule(): RuleType` — Creates a rule using the configured fields, operators, and defaults, applying the same precedence rules as the ` ` component.
* `createRuleGroup(independentCombinators?: boolean): RuleGroupTypeAny` — Creates a group.
Neither method adds anything to the query; pass the result to `add` or `insert`.
### Mutation[](#mutation "Direct link to Mutation")
Each method below delegates to the query tool of the same name, using the manager's configuration (`combinators`, `idGenerator`, and the default resolvers) as the default options. Per-call options take precedence.
* `add(ruleOrGroup, parentPathOrID?, options?)` — Unlike the `add` query tool, `parentPathOrID` is optional and defaults to the root group.
* `remove(pathOrID, options?)`
* `update(prop, value, pathOrID, options?)`, `update(props, values, pathOrID, options?)`, or `update(propsMap, pathOrID, options?)` — All three argument forms of the `update` query tool are supported.
* `move(oldPathOrID, newPath, options?)`
* `insert(ruleOrGroup, path, options?)` — Like the `insert` query tool, this accepts a path only, not an `id`.
* `group(sourcePathOrID, targetPathOrID, options?)`
note
As with the query tools themselves, these methods are a no-op when the target path or `id` can't be resolved—including attempts to remove the root group. Nothing is thrown by default, and mutators always return the manager, so compare `getQuery()` by reference if you need to know whether a call had any effect. See [strict mode](#strict-mode) to turn these into errors instead.
### Validation and export[](#validation-and-export "Direct link to Validation and export")
* `validate(): boolean | ValidationMap` — Validates the current query with the configured `validator`, defaulting to [`defaultValidator`](/docs/utils/misc.md#defaultvalidator).
* `format(options?)` — Passes the current query to [`formatQuery`](/docs/utils/export.md). Accepts everything `formatQuery` accepts as its second parameter, including the format-name shorthand (`q.format('sql')`), and has the same return types.
### Cloning[](#cloning "Direct link to Cloning")
`clone(options?: { regenerateIDs?: boolean })` returns an independent manager with the same configuration and the current query. Subscribers and history are not carried over. Because every mutation produces a new query object, the two managers share the initial query safely and diverge from the first change. Pass `{ regenerateIDs: true }` to give every rule and group in the clone a new `id`, which is useful when both queries will be used together.
### Reconfiguration[](#reconfiguration "Direct link to Reconfiguration")
`reconfigure(options: Partial, config?: { replace?: boolean })` updates the manager's configuration in place, keeping the current query, the undo/redo history, and every subscriber. Use it to propagate new `translations`, `fields`, `operators`, and so on without discarding state:
```
q.reconfigure({ translations: { fields: { placeholderLabel: 'Choisir un champ' } } });
```
The incoming options are shallow-merged over the current ones, so keys left out are preserved. Passing a key explicitly as `undefined` resets it to its default. Object-valued options like `translations` are replaced wholesale rather than deep-merged—spread the current value in yourself to patch a single key. Pass `{ replace: true }` to discard the existing options entirely and start from the incoming set.
Related methods:
* `getOptions(): QueryManagerOptions` — The options currently in effect, as a frozen shallow copy. This one is frozen even under `freeze: false`; see [Freezing](#freezing-manager).
* `getConfigVersion(): number` — A counter incremented only when a `reconfigure` call changes the effective configuration; calls that resolve to the options already in effect leave it unchanged. Bound to the instance, so it can be passed directly to React's `useSyncExternalStore` alongside `subscribe`. The [`useQueryManager`](/docs/utils/hooks.md#usequerymanager) hook already does this.
note
`reconfigure` never rewrites the query, even when the new options no longer describe it—a rule whose `field` is not in the new `fields` list is left as-is. Call [`validate()`](#validation-and-export) to detect that, or `setQuery(getQuery())` to re-normalize.
A call that resolves to the configuration already in effect is a **no-op**: nothing is re-derived, `getConfigVersion()` does not change, and subscribers are not notified. Equality is structural for data and by identity for functions (see [`optionsEqual`](/docs/utils/framework-adapters.md#configuration)), so rebuilding the options object on every render—which every framework adapter does—does not force a reconfigure as long as the data is the same. Rebuilding a callback per render *does* count as a change; memoize it to avoid that. The comparison is against the merged options, or against the replacement under `{ replace: true }`, so `reconfigure({})` behaves like `reconfigure(getOptions())`.
Otherwise subscribers are notified once and `getConfigVersion()` is incremented. Inside a [batch](#batching) the options are applied immediately—configuration is not part of a batch's rollback—but the notification is deferred and merged into the batch's single notification. History options are honored immediately: lowering `maxHistory` trims the undo stack, and turning history off clears both stacks.
### Subscriptions[](#subscriptions "Direct link to Subscriptions")
`subscribe(listener: (change: SubscriptionChange) => void)` registers a listener called after every change to the query or the configuration, and returns a function that unregisters it. Mutations that resolve to a no-op do not notify, and a [batch](#batching) notifies once no matter how many changes it contains.
The listener receives a `SubscriptionChange`—`{ query: boolean; config: boolean }`, at least one of which is always `true`—describing what changed. A framework adapter can use it to skip work a given change does not affect (re-deriving option lists on a query-only change, say) instead of diffing the manager's output to find out. The argument is optional: a zero-argument listener, including `useSyncExternalStore`'s `onStoreChange`, is a valid listener.
| Notification source | `change` |
| ---------------------------------------- | -------------------------------- |
| Any mutation, `setQuery`, `undo`, `redo` | `{ query: true, config: false }` |
| `reconfigure` | `{ query: false, config: true }` |
| A batch containing both | `{ query: true, config: true }` |
The method is bound to the instance, so it is a stable reference across renders and can be passed directly to React's `useSyncExternalStore`. `getQuery` is bound as well, so it can serve as the snapshot getter without a wrapper:
```
const query = useSyncExternalStore(q.subscribe, q.getQuery);
```
In React, prefer the [`useQueryManager`](/docs/utils/hooks.md#usequerymanager) hook, which wraps this and handles creating the manager exactly once.
### Batching[](#batching "Direct link to Batching")
`batch(fn: () => void)` runs `fn`, deferring history recording and subscriber notification until it returns. The whole batch becomes a single undo step and triggers a single notification, or neither if the query ends up unchanged. Batches may be nested; only the outermost one commits.
If `fn` throws, the query and its history are restored to their pre-batch state and the error propagates, so a batch either applies completely or not at all.
[`undo()`, `redo()`, and `clearHistory()`](#undoredo) may be called inside a batch, and their notifications are deferred like everything else. Because they manage the history stacks themselves, a batch containing one of them records no entry of its own, leaving the stacks exactly as those methods left them.
```
q.batch(() => {
q.add(rule1);
q.add(rule2);
q.add(rule3);
}); // one notification, one undo step
```
### Strict mode[](#strict-mode "Direct link to Strict mode")
By default, a mutation whose target can't be used is a silent no-op (see [aborted operations](#aborted-operations)). Set `strict: true` to raise a `QueryManagerError` instead:
```
const q = new QueryManager(query, { fields, strict: true });
q.remove('no-such-id'); // throws QueryManagerError
```
The error carries a `code` (the [`AbortReason`](#aborted-operations)) and the full `info` object, so failures can be handled by kind rather than by message:
```
try {
q.remove(id);
} catch (error) {
if (error instanceof QueryManagerError && error.code === 'target-not-found') {
// ...
}
}
```
The two non-error reasons—`"same-location"` and `"no-change"`—never throw, so idempotent code (setting a value to what it already is, or dropping a rule where it started) keeps working under `strict`.
To observe aborted operations without changing control flow, use `onInvalidTarget`. It is called for *every* abort, including the non-error reasons, and runs before any `strict` throw, so an operation can be both observed and enforced:
```
const q = new QueryManager(query, {
fields,
onInvalidTarget: ({ reason, operation }) => logger.warn(`${operation}: ${reason}`),
});
```
Both options can be overridden per call, which takes precedence over the manager's own configuration:
```
q.remove(id, { strict: true }); // just this call
q.remove(id, { strict: false }); // opt out of a manager-wide strict setting
```
A `strict` throw inside [`batch`](#batching) rolls the whole batch back.
### Undo/redo[](#undoredo "Direct link to Undo/redo")
Recording is opt-in through the `history` option, so instances that never undo retain no extra references. Pass `true` for the defaults, or an object to configure `maxHistory` (default `50`) and/or `coalesceMs` (default `500`).
```
const q = new QueryManager(undefined, { fields, history: true });
```
* `undo()` / `redo()` — Restore the previous or most recently undone query. No-ops when the corresponding `can*` method returns `false`. Both notify subscribers.
* `canUndo(): boolean` / `canRedo(): boolean`
* `clearHistory()` — Discards all history without changing the current query.
* `getHistory(): { past, future }` — Copies of the recorded queries, `past` oldest first and `future` newest first.
`undo()`, `redo()`, and `clearHistory()` may be called inside a [batch](#batching); see [Batching](#batching) for how that interacts with the batch's own history entry.
Recording follows the same semantics as the [`react-querybuilder/history`](/docs/tips/undo-redo.md) entry point: consecutive changes to the same property of the same rule within `coalesceMs` are merged into a single undo step (so typing a value produces one entry rather than one per keystroke), structural changes never coalesce, and changes that alter no observable property are not recorded at all.
### Traversal[](#traversal "Direct link to Traversal")
`walk(options?)` is a generator yielding every rule and group in the query, depth-first in pre-order, starting with the root group itself. Combinator strings in independent-combinator queries are skipped.
Each entry is a `QueryNode`:
```
interface QueryNode {
node: RuleGroupTypeAny | RuleType;
path: Path;
parent: RuleGroupTypeAny | null; // `null` for the root group
}
```
```
for (const { node, path } of q.walk({ rulesOnly: true })) {
console.log(path, node.field);
}
```
`WalkOptions`:
| Option | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------- |
| `from` | Traverse only the subtree at this `Path` or `id`, visiting that node first. Yields nothing if unresolvable. |
| `rulesOnly` | Visit only rules. Groups are still traversed, just not yielded (except as `parent`). |
| `groupsOnly` | Visit only groups. |
Derived methods:
* `rules(options?)` / `groups(options?)` — Shorthand for `walk` with `rulesOnly`/`groupsOnly`.
* `find(predicate, options?): QueryNode | null` — The first matching node.
* `filter(predicate, options?): QueryNode[]` — All matching nodes.
* `[Symbol.iterator]()` — Equivalent to `walk()` with no options, so a manager can be spread (`[...q]`) or used directly in `for...of`.
Traversal operates on the query as it was when iteration began, so mutating the manager mid-iteration does not affect a walk already in progress. Because generators are lazy, that snapshot is taken on the first iteration step rather than when `walk()` is called.
### Lookups[](#lookups "Direct link to Lookups")
These mirror the standalone [`findPath`](/docs/utils/misc.md#findpath) and [`findID`](/docs/utils/misc.md#findid) utilities, minus the trailing `query` parameter.
* `findPath(path: Path)` — The rule or group at `path`.
* `findID(id: string)` — The rule or group with the given `id`.
* `getPathOfID(id: string): Path | null` — The path of the rule or group with the given `id`.
* `pathIsDisabled(path: Path): boolean` — Whether the node at `path` is disabled, either itself or by an ancestor group.
Methods that accept *either* a path or an `id`, and narrow the result:
* `getNode(pathOrID)` — The rule or group.
* `getRule(pathOrID): RuleType | null` — `null` if the target is a group.
* `getGroup(pathOrID): RuleGroupTypeAny | null` — `null` if the target is a rule.
* `getParent(pathOrID): RuleGroupTypeAny | null` — The containing group, or `null` for the root group.
Unlike the standalone `findPath`, which can return `undefined` for an out-of-range index, all of these normalize an unresolvable target to `null`.
`findID`, `getPathOfID`, and the `pathOrID` forms above are backed by an `id`-to-`Path` index built once per query, so repeated lookups are constant time rather than a tree walk. The index is rebuilt automatically whenever the query changes, including via `undo`, `redo`, and a rolled-back `batch`.
note
`validate()` results are cached against the current query for the same reason. A custom `validator` with side effects, or one that depends on anything other than the query, may therefore run fewer times than expected.
### Rule configuration[](#rule-configuration "Direct link to Rule configuration")
These methods resolve the same field/operator configuration the `QueryBuilder` component uses, applying the same precedence rules (field-level properties first, then the corresponding `get*` option, then the manager-level defaults).
* `getFields(): FullOptionList` — The normalized field list, for populating a field selector.
* `getCombinators(): FullOptionList` — The normalized combinator list, for populating a combinator selector.
* `getFieldData(field): FullField` — The configured field. For an unconfigured field, returns the same minimal fallback (`{ name, value, label }`, all set to the field name) that `getRuleContext` reports as `fieldData`.
`getFields` and `getCombinators` return frozen arrays (unless the [`freeze` option](#freezing-manager) is `false`), so they are safe to hand to rendering code without defensive copying. Either way, treat them as read-only.
* `getOperators(field): FullOptionList` — The operator list for a field.
* `getValueSources(field, operator): ValueSourceFullOptions` — The available value sources.
* `getMatchModes(field): MatchModeOptions` — The available match modes.
* `getValues(field, operator): FullOptionList` — The value option list.
* `getValueEditorType(field, operator): ValueEditorType` — The value editor type.
When `autoSelectField`, `autoSelectOperator`, or `autoSelectValue` is `false`, an empty placeholder option is prepended to the corresponding list. Pass the `translations` option to control those placeholders exactly as the `translations` prop does for the `QueryBuilder` component—only the placeholder properties of `fields`, `operators`, and `values` are used, since the remaining keys describe UI elements that have no meaning outside the component. This matters beyond presentation: `placeholderName` becomes the `field`, `operator`, or `value` written into rules created by the manager, so a manager paired with a component should receive the same translations.
```
const q = new QueryManager(undefined, {
fields,
autoSelectField: false,
translations: { fields: { placeholderName: '#', placeholderLabel: 'Select a field' } },
});
q.createRule(); // => { field: '#', ... }
```
`getRuleContext(pathOrID)` resolves all of the above for a specific rule at once, plus its validation result, returning `null` when the target can't be resolved or isn't a rule.
```
interface RuleContext {
fieldData: FullField;
hideValueControls: boolean;
inputType: InputType | null;
matchModes: MatchModeOptions;
operatorObject: FullOperator | undefined;
operators: OptionList;
parameters: FlexibleOptionList | null;
validationResult: boolean | ValidationResult;
valueEditorType: ValueEditorType;
values: FlexibleOptionList ;
valueSourceOptions: ValueSourceFullOptions;
valueSources: ValueSources;
}
```
This is the same derivation the [`useRule`](/docs/utils/hooks.md#userule) hook performs—both call the shared `deriveRuleContext` utility—so a non-React implementation can render a rule without reimplementing the precedence rules.
```
const ctx = q.getRuleContext([0]);
// => { fieldData: { name: 'firstName', ... }, valueEditorType: 'text', valueSources: ['value'], ... }
```
`getRuleGroupContext(pathOrID)` is the equivalent for groups, defaulting to the root group. It returns `null` when the target can't be resolved or isn't a group.
```
interface RuleGroupContext {
combinator: string;
combinatorObject: FullCombinator | undefined;
combinators: FullOptionList;
/** The selected combinator's `className`, or `null` for independent combinators. */
combinatorBasedClassName: Classname | null;
independentCombinators: boolean;
validationResult: boolean | ValidationResult;
}
```
note
Unlike a rule, a group's `validationResult` comes *only* from the query-level validation map—there is no field-level validator fallback.
### Classnames[](#classnames "Direct link to Classnames")
`@react-querybuilder/core` also exports the classname derivations used by `useRule` and `useRuleGroup`, so an implementation in any framework can produce a byte-identical `class` attribute for every element:
* `deriveRuleClassNames({ classNames, suppressStandardClassnames })` — the per-element classnames for a rule (`fields`, `operators`, `value`, `removeRule`, and so on).
* `deriveRuleOuterClassName({ classNames, suppressStandardClassnames, ...state })` — the rule's wrapper classname, including conditional state classes for `disabled`, `muted`, drag-and-drop, subqueries, and validation.
* `deriveRuleGroupClassNames({ classNames, suppressStandardClassnames, ...dndState })` — the per-element classnames for a rule group, including its `header`.
* `deriveRuleGroupOuterClassName({ ... })` — the group's wrapper classname.
* `deriveRuleClassName(key, { ... })` — a single rule classname, for cases where only one is needed.
* `deriveQueryBuilderClassNames({ classNames, suppressStandardClassnames, disabled, validationResult })` — the query builder's own wrapper classname, including the conditional `disabled`, `valid`, and `invalid` classes. Only a `boolean` `validationResult` contributes a class; a `ValidationMap` describes individual rules and groups, not the query as a whole.
caution
A group's wrapper reflects **fewer** states than a rule's: `dndOver`, `dndCopy`, `dndDropNotAllowed`, and `hasSubQuery` apply to rules only. The two condition sets are declared separately for exactly this reason, so use the matching function rather than assuming symmetry.
Each takes the merged [`controlClassnames`](/docs/components/querybuilder.md#controlclassnames) object and returns plain strings.
Composition is declared as data rather than code: every derived classname names the `controlClassnames` keys that contribute to it (in application order, after the standard classname) plus any state-dependent classes. Conditional elements—the rule wrapper and the group `header`—use the same declaration, so there is no element a port can handle differently by accident.
```
interface ClassnameSpec {
sources: readonly (keyof Classnames)[];
conditions?: readonly { key; when; standardOnly? }[];
}
```
For each entry the result is the standard classname, then each `sources` entry, then each active condition's custom class, then a single object of active standard classes.
```
deriveRuleClassNames({ classNames: { valueSelector: 'vs', fields: 'f' } });
// => { fields: 'rule-fields vs f', operators: 'rule-operators vs', ... }
```
### Option resolvers and factories[](#option-resolvers-and-factories "Direct link to Option resolvers and factories")
The precedence rules the `QueryBuilder` component applies when resolving a rule's configuration are exported as standalone functions, so `useQueryBuilderSetup` and `QueryManager` share one implementation rather than each keeping its own:
* `resolveOperatorList({ field, fieldData, getOperators, operators, ... })` — the field's own `operators`, then `getOperators`, then the query-level list.
* `resolveDefaultOperator({ field, fieldData, getDefaultOperator, getOperators })` — the field's `defaultOperator`, then `getDefaultOperator` (string or function), then the first available operator.
* `resolveValueEditorType({ field, operator, fieldData, getValueEditorType })` — the field's `valueEditorType` (string or function of the operator), then `getValueEditorType`, then `"text"`.
* `resolveValueList({ field, operator, fieldData, getValues, ... })` — the field's own `values`, then `getValues`, then an empty list.
* `createRule(options)` / `createRuleGroup(options, independentCombinators?)` — build a new rule or group from that configuration.
Each accepts an optional `placeholder` so a UI layer can supply translated placeholder options; omit it where translations don't apply.
note
`createRule` computes `value` in a second pass, once `field`, `operator`, and `valueSource` are known, since the default value depends on all three. `createRuleGroup` generates the group's own `id` *before* any contained rule's — observable when `idGenerator` is deterministic.
### Value editors[](#value-editors "Direct link to Value editors")
The logic behind the [`useValueEditor`](/docs/utils/hooks.md#usevalueeditor) hook is exported separately, since a value editor is the control an implementation is most likely to rewrite:
* `getValueEditorReset({ skipHook, type, operator, value, inputType })` — returns `{ reset, value }`. A rule's `value` must collapse to a single element when it's an array (or a comma-containing string in a `number` input) but the operator is no longer one of `between`/`notBetween`/`in`/`notIn` and the editor isn't a multiselect. React applies this in an effect; apply it wherever is idiomatic.
* `getMultiValueUpdate({ value, index, valueAsArray, operator, values, listsAsArrays, parseNumberMethod })` — the next value when the editor at `index` in a series changes. For `between`/`notBetween`, editing the first bound guarantees an array of at least two elements, seeding the second from the first available option.
* `coerceBigIntValue(value, parseNumberMethod)` — a `bigint`, falling back to the parsed number when the value can't be represented as one.
* `coerceInputType(inputType, operator)` — the `type` an ` ` should use. `bigint` values and the `in`/`notIn` operators both require a text input.
* `isBetweenOperator(operator)` — whether an operator's value is a pair of bounds.
* `getValueSelectorUpdate(value, { multiple, listsAsArrays })` / `normalizeValueSelectorValue(value, multiple)` — the equivalents for [`useValueSelector`](/docs/utils/hooks.md#usevalueselector). The latter stringifies multiselect values so they match option names, which are always strings.
### Query actions[](#query-actions "Direct link to Query actions")
`createQueryActions(config)` builds the six mutations a query builder performs as pure functions of the current query. Each returns the next query, or `undefined` when the mutation was aborted—because the target is disabled, a confirmation callback declined, or `maxLevels` was reached.
```
const actions = createQueryActions({
qbId,
combinators,
idGenerator,
maxLevels,
queryDisabled,
respectDisabled,
resetOnFieldChange,
resetOnOperatorChange,
getRuleDefaultOperator,
getValueSources,
getRuleDefaultValue,
getMatchModes,
onAddRule,
onAddGroup,
onRemove,
onMoveRule,
onMoveGroup,
onGroupRule,
onGroupGroup,
onLog,
});
// { addRule, addGroup, propChange, removeRuleOrGroup, moveRule, groupRule }
```
This is the policy that surrounds the [query tools](#query-tools)—disabled gating, the confirmation callback protocol, depth limits, and debug logging. An implementation supplies only its own storage: read the current query, call the action, apply a non-`undefined` result.
```
const newQuery = actions.addRule(currentQuery, rule, parentPath);
if (newQuery) applyQuery(newQuery);
```
The confirmation callbacks have three distinct return contracts:
| Callback | Return value |
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `onAddRule`, `onAddGroup` | `true` to proceed, falsy to cancel, or a **replacement rule/group** to add instead |
| `onMoveRule`, `onMoveGroup`, `onGroupRule`, `onGroupGroup` | `true` to proceed, falsy to cancel, or a **replacement query** to apply instead |
| `onRemove` | boolean only |
The move and group actions compute the prospective query *before* invoking their callback, so it can inspect the result it is being asked to approve.
### Controlled and uncontrolled queries[](#controlled-and-uncontrolled-queries "Direct link to Controlled and uncontrolled queries")
`resolveCandidateQuery({ query, storeQuery, defaultQuery, fallbackQuery }, { idGenerator })` applies the precedence a query builder uses to decide what to render: the controlled `query`, then whatever is already in its store, then the uncontrolled `defaultQuery`, then a freshly created empty group. The result is passed through `prepareRuleGroup` unless it already has an `id`, which is taken to mean it has been prepared before—most often because the caller is passing back the object it received from `onQueryChange`.
### Paths[](#paths "Direct link to Paths")
`derivePathInfo(path, childCount, { disabled, disabledPaths })` returns `{ path, disabled }` for each child of a group, applying the rule that a child is disabled if its parent is disabled or its own path appears in `disabledPaths`. The `usePathsMemo` hook wraps it to keep the array referentially stable across renders.
### Inspection[](#inspection "Direct link to Inspection")
* `isIC(): boolean` — Whether the current query uses independent combinators.
* `signatureOf(other: RuleGroupTypeAny): string` — How the current query differs from `other`, using the same signature scheme as [undo/redo](#undoredo) coalescing.
* `diagnostics(): DiagnosticsResult` — Shorthand for `format('diagnostics')`.
* `toJSON(): RuleGroupTypeAny` — Returns the current query, so `JSON.stringify(q)` matches `JSON.stringify(q.getQuery())`.
### Conversion[](#conversion "Direct link to Conversion")
`toIC()` and `fromIC()` return a **new manager** with the same configuration and the query converted to or from the independent combinators structure (see [`convertQuery`](/docs/utils/misc.md#convertquery)). Both are idempotent and never modify the original. As with [`clone`](#cloning), subscribers and history are not carried over.
```
const ic = q.toIC(); // QueryManager
q.getQuery(); // unchanged
```
`transform(options?)` runs [`transformQuery`](/docs/utils/misc.md#transformquery) against the current query and returns its result directly, rather than a new manager — `transformQuery` can produce arbitrary shapes that are no longer valid queries. The manager is never modified.
```
q.transform({ propertyMap: { combinator: 'AndOr' } });
```
---
# Validation
> *Refer to the [TypeScript reference](/docs/typescript.md) page for information about the types and interfaces referenced below.*
`react-querybuilder` provides two methods for validating queries: query-level validation and field-based validation.
## Query-level validation[](#query-level-validation "Direct link to Query-level validation")
Query-level validation can treat the entire query as valid or invalid, or you can report validation results for specific rules/groups within the query based on the `id` property.
To mark an entire query as valid or invalid, return a `boolean` value from the `validator` callback function (`true` for valid, `false` for invalid).
```
import { QueryBuilder, RuleGroupType } from 'react-querybuilder';
/**
* This function returns false (indicating "invalid") when no rules are present.
*/
const validator = (q: RuleGroupType) => q.rules.length > 0;
const App = () => {
return ;
};
```
The alternate return value from a query-level validator is a validation map—an object where each key represents the `id` of a rule or group. Associated values are either `boolean` (`true` for valid, `false` for invalid) or a validation result. Validation results are objects with two keys: a required `boolean` `valid` key and an optional `reasons` array specifying why that rule/group is valid or invalid. (Reasons are assumed to be strings, but the type is `any[]` since the QueryBuilder default components ignore them. Feel free to use them however you like in your custom components.)
```
import { QueryBuilder, RuleGroupType, ValidationMap } from 'react-querybuilder';
/**
* This function returns a validation map. A real validator function would
* have logic to determine which rules are valid/invalid and why, but
* this function is simplified for brevity.
*/
const validator: QueryValidator = (q): ValidationMap => ({
r1: true, // valid rule
r2: false, // invalid rule
r3: { valid: true, reasons: ['awesome rule'] }, // valid rule
r4: { valid: false, reasons: ['lame rule'] }, // invalid rule
});
const query: RuleGroupType = {
combinator: 'and',
rules: [
{ id: 'r1', field: 'field1', operator: '=', value: 'Value 1' },
{ id: 'r2', field: 'field2', operator: '=', value: 'Value 2' },
{ id: 'r3', field: 'field3', operator: '=', value: 'Value 3' },
{ id: 'r4', field: 'field4', operator: '=', value: 'Value 4' },
],
};
const App = () => {
return ;
};
```
## Field-based validation[](#field-based-validation "Direct link to Field-based validation")
Assigning a `validator` to individual fields allows you to provide separate callback functions depending on the field's value type or other attributes.
In the following configuration, any rule that specifies `field2` as the field (e.g., the second rule) will be marked invalid.
```
import {
Field,
QueryBuilder,
RuleGroupType,
RuleValidator,
ValidationResult,
} from 'react-querybuilder';
/**
* This function returns a validation result.
*/
const validator: RuleValidator = (q): ValidationResult => ({
valid: false,
reasons: ['this field is always invalid'],
});
const fields: Field[] = [
{ name: 'field1', label: 'Field 1' },
{ name: 'field2', label: 'Field 2', validator },
{ name: 'field3', label: 'Field 3' },
{ name: 'field4', label: 'Field 4' },
];
const query: RuleGroupType = {
combinator: 'and',
rules: [
{ field: 'field1', operator: '=', value: 'Value 1' },
{ field: 'field2', operator: '=', value: 'Value 2' },
{ field: 'field3', operator: '=', value: 'Value 3' },
{ field: 'field4', operator: '=', value: 'Value 4' },
],
};
const App = () => {
return ;
};
```
## Effect on HTML[](#effect-on-html "Direct link to Effect on HTML")
If you provide a query- or field-level validator function, the wrapper `` for each evaluated query object (rule, group, or entire query) will be assigned one of two classes: `queryBuilder-valid` or `queryBuilder-invalid`. You can use these classes to style the elements with CSS.
See it in action
In the [demo](/demo#validateQuery=true), check the "Use validation" option and create an empty group or text input without a value. Empty groups will show a message where rules usually appear (using the `:after` pseudo-selector), and text fields without values will have a [purple](https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/) background.
## Effect on exports[](#effect-on-exports "Direct link to Effect on exports")
See the [validation section on the Export page](/docs/utils/export.md#validation) for more information.
## Default validator[](#default-validator "Direct link to Default validator")
You can pass the provided [`defaultValidator`](/docs/utils/misc.md#defaultvalidator) to the `validator` prop to check for invalid combinators, empty groups, or (if the query uses independent combinators) out-of-sequence `rules` arrays. The [demo](/demo) uses the default validator.
---