# React Query Builder documentation and demos > Comprehensive guide to the React Query Builder component library and associated packages ## Documentation - [Buildless](/docs/buildless.md): Use React Query Builder directly in the browser without a build step via ESM imports. For JSX parsing in script tags, include Babel. - [Compatibility packages](/docs/compat.md): React Query Builder's default components use basic HTML5 form controls that are highly customizable through styling, especially via the controlClassnames prop. However, some UI libraries require specific components or HTML structures to properly style their form controls. - [Date/time features](/docs/datetime.md): By default, React Query Builder components and utilities handle dates and times in a generic, unopinionated way. We recommend storing dates as strings in ISO 8601-compatible format and taking advantage of built-in date/time functionality like "date" and "datetime-local" inputs (see inputType). - [Drag-and-drop](/docs/dnd.md): The @react-querybuilder/dnd package augments React Query Builder with drag-and-drop functionality. . - [Getting started](/docs/intro.md): Basic setup instructions and principles - [TypeScript reference](/docs/typescript.md): Here are the key TypeScript 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. ### Components - [ActionElement](/docs/components/actionelement.md): Button component - [DragHandle](/docs/components/draghandle.md): Drag-and-drop handle component - [InlineCombinator](/docs/components/inlinecombinator.md): Wrapper element for inline combinator selectors - [MatchModeEditor](/docs/components/matchmodeeditor.md): Match mode selector and threshold numeric value editor - [NotToggle](/docs/components/nottoggle.md): Checkbox component to invert groups - [QueryBuilder](/docs/components/querybuilder.md): The root component and context provider - [QueryBuilder controlElements prop](/docs/components/querybuilder-controlelements.md): Prop to override default components - [Rule](/docs/components/rule.md): Layout and config component for rules - [RuleGroup](/docs/components/rulegroup.md): Recursive layout and config component for groups - [ShiftActions](/docs/components/shiftactions.md): Shift up/down buttons - [UndoRedoActions](/docs/components/undoredoactions.md): Undo/redo buttons - [ValueEditor](/docs/components/valueeditor.md): Dynamic editor component - [ValueSelector](/docs/components/valueselector.md): Versatile selector component ### expr The @react-querybuilder/expr package augments React Query Builder with arithmetic and function expression support. . - [Expressions](/docs/expr.md): The @react-querybuilder/expr package augments React Query Builder with arithmetic and function expression support. . ### rules-engine Try the interactive Rules Engine demo with a musician-themed example, including live execution against sample data. - [Rules engine](/docs/rules-engine.md): Try the interactive Rules Engine demo with a musician-themed example, including live execution against sample data. ### Styling - [CSS classes](/docs/styling/classnames.md): Visual guide to CSS classes for each component element - [Styling overview](/docs/styling/overview.md): How to tweak or overhaul the default styles ### Tips and Tricks - [Adding and removing query properties](/docs/tips/adding-removing-query-properties.md): Customizing the query object - [Arbitrary updates](/docs/tips/arbitrary-updates.md): Non-standard query management from custom components - [Async option list loading](/docs/tips/async-option-lists.md): Augment value selectors or value editors with async option list loading - [Common mistakes](/docs/tips/common-mistakes.md): How to resolve some frequently-reported issues - [Comparison with other libraries](/docs/tips/comparison.md): How React Query Builder compares to react-awesome-query-builder - [Custom bind variables](/docs/tips/custom-bind-variables.md): Altering the SQL for certain RDBMS's - [Custom components with fallbacks](/docs/tips/custom-with-fallback.md): Augmenting the default components - [External controls](/docs/tips/external-controls.md): Managing queries outside the main component - [Limit rule groups](/docs/tips/limit-groups.md): Methods to prevent new groups - [Managing fields](/docs/tips/managing-fields.md): Generate a field array from database table/view specification - [Managing operators](/docs/tips/managing-operators.md): Which operators are allowed for which fields, and how they are labeled - [Maximizing rendering performance](/docs/tips/maximizing-performance.md): How to avoid common pitfalls for a smoother user experience - [Migrating from react-awesome-query-builder](/docs/tips/migrate-from-raqb.md): Convert RAQB configs and query trees to React Query Builder - [Working with option lists](/docs/tips/option-lists.md): Managing ambiguity in option list types - [External parameter manager](/docs/tips/parameter-manager.md): Feed an external parameter component into a query builder - [Path concepts](/docs/tips/path.md): Explanation of the "path" property of rules and groups - [Customization showcase](/docs/tips/showcase.md): Interesting/unconventional layouts or behavior - [Subqueries and nested data](/docs/tips/subqueries.md): Building queries for data with nested arrays and complex structures - [Undo / redo](/docs/tips/undo-redo.md): Recording and navigating query history ### Utilities - [Export](/docs/utils/export.md): Convert query builder objects to SQL, etc. - [Framework adapter API](/docs/utils/framework-adapters.md): Headless derivations for non-React ports of React Query Builder - [Hooks](/docs/utils/hooks.md): Reuse React Query Builder's logic in your own components - [Import](/docs/utils/import.md): Convert SQL and other formats to query builder objects - [Miscellaneous](/docs/utils/misc.md): Assorted utilities and other exports - [Query management](/docs/utils/query-management.md): Programmatic query manipulation - [Validation](/docs/utils/validation.md): Query validation methods and configuration ## Optional - [GitHub repository](https://github.com/react-querybuilder/react-querybuilder) - [Discord server](https://discord.gg/MnAQWyUtEg) --- # Full Documentation Content # Buildless Use React Query Builder directly in the browser without a build step via ESM imports. For JSX parsing in `script` tags, include Babel. ## Basic example[​](#basic-example "Direct link to Basic example") ``` React Query Builder ESM
``` ## Drag-and-drop example[​](#drag-and-drop-example "Direct link to Drag-and-drop example") For drag-and-drop functionality, import `react-dnd`, `react-dnd-html5-backend`, and `@react-querybuilder/dnd`. Wrap `` with `` and pass the combined exports to the `dnd` prop. ``` React Query Builder ESM (DnD)
``` --- # Compatibility packages React Query Builder's default components use basic HTML5 form controls that are highly customizable through styling, especially via the [`controlClassnames` prop](/docs/components/querybuilder.md#controlclassnames). However, some UI libraries require specific components or HTML structures to properly style their form controls. ## Packages[​](#packages "Direct link to Packages") Official component packages for several popular style libraries are available under the [`@react-querybuilder` org on npm](https://www.npmjs.com/org/react-querybuilder). See each component package in action by selecting the "Style library" option on the [demo page](/demo). The "Demo" links below load the demo with that library preselected, while [CodeSandbox](https://codesandbox.io)/[StackBlitz](https://stackblitz.com) links open editable examples on their respective platforms. | Official site | Compatibility package | Demo | CodeSandbox | StackBlitz | | --------------------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------- | ---------------------------------------- | | [Ant Design](https://ant.design/) | [@react-querybuilder/antd](https://www.npmjs.com/package/@react-querybuilder/antd) | [Demo](/demo/antd) | [CodeSandbox](/sandbox?t=antd) | [StackBlitz](/sandbox?p=sb\&t=antd) | | [Bootstrap](https://getbootstrap.com/) | [@react-querybuilder/bootstrap](https://www.npmjs.com/package/@react-querybuilder/bootstrap) | [Demo](/demo/bootstrap) | [CodeSandbox](/sandbox?t=bootstrap) | [StackBlitz](/sandbox?p=sb\&t=bootstrap) | | [Bulma](https://bulma.io/) | [@react-querybuilder/bulma](https://www.npmjs.com/package/@react-querybuilder/bulma) | [Demo](/demo/bulma) | [CodeSandbox](/sandbox?t=bulma) | [StackBlitz](/sandbox?p=sb\&t=bulma) | | [Chakra UI](https://chakra-ui.com/) | [@react-querybuilder/chakra](https://www.npmjs.com/package/@react-querybuilder/chakra) | [Demo](/demo/chakra) | [CodeSandbox](/sandbox?t=chakra) | [StackBlitz](/sandbox?p=sb\&t=chakra) | | [Fluent UI](https://github.com/microsoft/fluentui/) | [@react-querybuilder/fluent](https://www.npmjs.com/package/@react-querybuilder/fluent) | [Demo](/demo/fluent) | [CodeSandbox](/sandbox?t=fluent) | [StackBlitz](/sandbox?p=sb\&t=fluent) | | [Mantine](https://mantine.dev/) | [@react-querybuilder/mantine](https://www.npmjs.com/package/@react-querybuilder/mantine) | [Demo](/demo/mantine) | [CodeSandbox](/sandbox?t=mantine) | [StackBlitz](/sandbox?p=sb\&t=mantine) | | [MUI](https://mui.com/) | [@react-querybuilder/material](https://www.npmjs.com/package/@react-querybuilder/material) | [Demo](/demo/material) | [CodeSandbox](/sandbox?t=material) | [StackBlitz](/sandbox?p=sb\&t=material) | | [PrimeReact](https://primereact.org/) | [@react-querybuilder/prime](https://www.npmjs.com/package/@react-querybuilder/prime) | [Demo](/demo/prime) | [CodeSandbox](/sandbox?t=prime) | [StackBlitz](/sandbox?p=sb\&t=prime) | | [React Native](https://reactnative.dev/) | [@react-querybuilder/native](https://www.npmjs.com/package/@react-querybuilder/native) | *Coming soon!* | [CodeSandbox](/sandbox?t=native) | [StackBlitz](/sandbox?p=sb\&t=native) | | [Tremor](https://www.tremor.so/) | [@react-querybuilder/tremor](https://www.npmjs.com/package/@react-querybuilder/tremor) | [Demo](/demo/tremor) | [CodeSandbox](/sandbox?t=tremor) | [StackBlitz](/sandbox?p=sb\&t=tremor) | ## Usage[​](#usage "Direct link to Usage") The recommended approach is to wrap `` in the appropriate `QueryBuilder*` context provider from the compatibility package. This example uses the Ant Design library: ``` import { QueryBuilderAntD } from '@react-querybuilder/antd'; import 'antd/dist/antd.compact.css'; // <- include this only if using `antd@<5` import { QueryBuilder } from 'react-querybuilder'; import 'react-querybuilder/dist/query-builder.css'; import { defaultQuery, fields } from './constants'; export function App() { return ( ); } ``` Each compatibility package exports its own context provider, except `@react-querybuilder/native`, which provides `` as a direct replacement for `` with identical props. | Compatibility package | Context provider | | ------------------------------- | ----------------------- | | `@react-querybuilder/antd` | `QueryBuilderAntD` | | `@react-querybuilder/bootstrap` | `QueryBuilderBootstrap` | | `@react-querybuilder/bulma` | `QueryBuilderBulma` | | `@react-querybuilder/chakra` | `QueryBuilderChakra` | | `@react-querybuilder/fluent` | `QueryBuilderFluent` | | `@react-querybuilder/mantine` | `QueryBuilderMantine` | | `@react-querybuilder/material` | `QueryBuilderMaterial` | | `@react-querybuilder/prime` | `QueryBuilderPrime` | | `@react-querybuilder/tremor` | `QueryBuilderTremor` | tip Context providers can be nested to combine features. For example, nest a UI library provider under `QueryBuilderDnD` to add both drag-and-drop functionality and styled components. This example uses the Bulma library *and* enables drag-and-drop: ``` import { QueryBuilderBulma } from '@react-querybuilder/bulma'; import { QueryBuilderDnD } from '@react-querybuilder/dnd'; import 'bulma/bulma.sass'; import { QueryBuilder } from 'react-querybuilder'; import 'react-querybuilder/dist/query-builder.css'; import { defaultQuery, fields } from './constants'; export function App() { return ( ); } ``` ## Other exports[​](#other-exports "Direct link to Other exports") Each compatibility package exports a `*ControlElements` object for the [`controlElements` prop](/docs/components/querybuilder.md#controlelements). Some packages also include `*ControlClassnames` and `*Translations` objects for their respective props. Use these exports when you need granular control over component replacement. See [Customization](#customization) below for advanced usage. This example uses the Bootstrap library: ``` import { bootstrapControlClassnames, bootstrapControlElements, } from '@react-querybuilder/bootstrap'; import 'bootstrap/dist/css/bootstrap.css'; import { QueryBuilder } from 'react-querybuilder'; import { defaultQuery, fields } from './constants'; export function App() { return ( ); } ``` | Compatibility package | `controlElements` object | | ------------------------------- | -------------------------- | | `@react-querybuilder/antd` | `antdControlElements` | | `@react-querybuilder/bootstrap` | `bootstrapControlElements` | | `@react-querybuilder/bulma` | `bulmaControlElements` | | `@react-querybuilder/chakra` | `chakraControlElements` | | `@react-querybuilder/fluent` | `fluentControlElements` | | `@react-querybuilder/mantine` | `mantineControlElements` | | `@react-querybuilder/material` | `materialControlElements` | | `@react-querybuilder/native` | `nativeControlElements` | | `@react-querybuilder/tremor` | `tremorControlElements` | ## Customization[​](#customization "Direct link to Customization") All compatibility components except the `*ValueEditor`s accept props defined by the style library for the actual rendered component in addition to the standard props defined by `react-querybuilder`. This allows you to idiomatically customize the style library's component while leaving query builder integration to the compatibility layer. For example, the `AntDActionElement` component from `@react-querybuilder/antd` renders an `antd` `Button` component, so it accepts properties from both the `ActionProps` interface (`react-querybuilder`) and the `ButtonProps` interface (`antd`). In the example below, the `size` prop is accepted because it's an `antd` `Button` prop (from the `ButtonProps` interface), even though it's not in the `ActionProps` interface. ``` import { AntDActionElement, antdControlElements } from '@react-querybuilder/antd'; import { QueryBuilder, type ActionProps } from 'react-querybuilder'; const MyAntDActionElement = (props: ActionProps) => ; export function App() { return ( ); } ``` This list shows which library components' props are accepted by the compatibility components, in addition to those defined by `react-querybuilder`. | Component | Base props (from RQB) | Rendered library component | | ---------------------------------- | ------------------------ | --------------------------------------------------------------- | | **`@react-querybuilder/antd`** | | | | `AntDActionElement` | `ActionProps` | `import { Button } from 'antd'` | | `AntDDragHandle` | `DragHandleProps` | `import { HolderOutlined } from '@ant-design/icons'` | | `AntDNotToggle` | `NotToggleProps` | `import { Switch } from 'antd'` | | `AntDValueSelector` | `VersatileSelectorProps` | `import { Select } from 'antd'` | | **`@react-querybuilder/chakra`** | | | | `ChakraActionElement` | `ActionProps` | `import { Button } from '@chakra-ui/react'` | | `ChakraDragHandle` (removed in v8) | `DragHandleProps` | `import { IconButton } from '@chakra-ui/react'` | | `ChakraNotToggle` | `NotToggleProps` | `import { Switch } from '@chakra-ui/react'` | | `ChakraValueSelector` | `VersatileSelectorProps` | `import { Select } from '@chakra-ui/react'` | | **`@react-querybuilder/fluent`** | | | | `FluentActionElement` | `ActionProps` | `import { Button } from '@fluentui/react-components'` | | `FluentDragHandle` | `DragHandleProps` | `import { Text } from '@fluentui/react-components'` | | `FluentNotToggle` | `NotToggleProps` | `import { Switch } from '@fluentui/react-components'` | | `FluentValueSelector` | `VersatileSelectorProps` | `import { Dropdown, Select } from '@fluentui/react-components'` | | **`@react-querybuilder/mantine`** | | | | `MantineActionElement` | `ActionProps` | `import { Button } from '@mantine/core'` | | `MantineNotToggle` | `NotToggleProps` | `import { Switch } from '@mantine/core'` | | `MantineValueSelector` | `VersatileSelectorProps` | `import { Select } from '@mantine/core'` | | **`@react-querybuilder/material`** | | | | `MaterialActionElement` | `ActionProps` | `import { Button } from '@mui/material'` | | `MaterialDragHandle` | `DragHandleProps` | `import { DragIndicator } from '@mui/icons-material'` | | `MaterialNotToggle` | `NotToggleProps` | `import { Switch } from '@mui/material'` | | `MaterialValueSelector` | `VersatileSelectorProps` | `import { Select } from '@mui/material'` | | **`@react-querybuilder/tremor`** | | | | `TremorActionElement` | `ActionProps` | `import { Button } from '@tremor/react'` | | `TremorNotToggle` | `NotToggleProps` | `import { Switch } from '@tremor/react'` | | `TremorValueSelector` | `VersatileSelectorProps` | `import { MultiSelect, Select } from '@tremor/react'` | ## Preload MUI components[​](#preload-mui-components "Direct link to Preload MUI components") tip As of v6.2.0, MUI components are loaded synchronously in traditional environments. However, preloading MUI components *may* still be necessary when using React Server Components, even if the component file rendering the query builder includes the `"use client"` directive. Prior to v6.2.0, the `@react-querybuilder/material` compatibility package loaded components from `@mui/material` asynchronously to properly inherit the theme context ([issue](https://github.com/react-querybuilder/react-querybuilder/issues/321)/[PR](https://github.com/react-querybuilder/react-querybuilder/pull/324)). This meant the query builder was initially rendered with default components, then—very quickly, if all went well—the MUI components would replace them. To avoid rendering default components and render MUI components immediately instead, import the MUI components in your application code and assign them as properties of the `muiComponents` prop on the `QueryBuilderMaterial` context provider. (Individual MUI compatibility components—`MaterialValueEditor`, `MaterialActionElement`, etc.—also accept an optional `muiComponents` prop.) ``` import { createTheme, ThemeProvider } from '@mui/material/styles'; import { QueryBuilderMaterial } from '@react-querybuilder/material'; import { QueryBuilder } from 'react-querybuilder'; import { defaultQuery, fields } from './constants'; import CloseIcon from '@mui/icons-material/Close'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import DragIndicator from '@mui/icons-material/DragIndicator'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import RedoIcon from '@mui/icons-material/Redo'; import UndoIcon from '@mui/icons-material/Undo'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; import InputLabel from '@mui/material/InputLabel'; // (after 8.9.2) import ListSubheader from '@mui/material/ListSubheader'; import MenuItem from '@mui/material/MenuItem'; import Radio from '@mui/material/Radio'; import RadioGroup from '@mui/material/RadioGroup'; import Select from '@mui/material/Select'; import Switch from '@mui/material/Switch'; import TextareaAutosize from '@mui/material/TextareaAutosize'; // (Use `Input` instead of `TextField` in versions lower than 7.7.1) import TextField from '@mui/material/TextField'; const muiComponents = { Button, Checkbox, CloseIcon, ContentCopyIcon, DragIndicator, FormControl, FormControlLabel, InputLabel, // (after 8.9.2) KeyboardArrowDownIcon, KeyboardArrowUpIcon, ListSubheader, LockIcon, LockOpenIcon, MenuItem, Radio, RadioGroup, RedoIcon, // (after 8.22.0) Select, Switch, TextareaAutosize, TextField, // (Use `Input` instead of `TextField` in versions lower than 7.7.1) UndoIcon, // (after 8.22.0) }; const muiTheme = createTheme(); export function App() { return ( ); } ``` ## shadcn/ui[​](#shadcnui "Direct link to shadcn/ui") Because [shadcn/ui](https://ui.shadcn.com/) components are copied directly into your project rather than installed as a dependency, React Query Builder provides a **shadcn registry** instead of an npm compatibility package. ### Installation[​](#installation "Direct link to Installation") Run the following command in your project (requires shadcn CLI and an existing shadcn/ui setup): * npm * Bun * Yarn * pnpm ``` npx shadcn add https://react-querybuilder.js.org/r/query-builder.json ``` ``` bun x shadcn add https://react-querybuilder.js.org/r/query-builder.json ``` ``` yarn dlx shadcn add https://react-querybuilder.js.org/r/query-builder.json ``` ``` pnpm dlx shadcn add https://react-querybuilder.js.org/r/query-builder.json ``` This installs the React Query Builder components into your project's configured components directory (e.g. `src/components/query-builder/`) and automatically pulls in the required shadcn primitives (`button`, `checkbox`, `input`, `label`, `radio-group`, `select`, `switch`, `textarea`). ### Usage[​](#usage-1 "Direct link to Usage") ``` import { QueryBuilderShadcn } from '@/components/query-builder'; import { QueryBuilder } from 'react-querybuilder'; import 'react-querybuilder/dist/query-builder.css'; import { defaultQuery, fields } from './constants'; export function App() { return ( ); } ``` ### Manual usage[​](#manual-usage "Direct link to Manual usage") If you prefer not to use the CLI, copy the component files from [`website/registry/default/query-builder/`](https://github.com/react-querybuilder/react-querybuilder/tree/main/website/registry/default/query-builder) in the React Query Builder repository into your project. The `shadcnControlElements` named export is also available for the [`controlElements` prop](/docs/components/querybuilder.md#controlelements): ``` import { shadcnControlElements } from '@/components/query-builder'; import { QueryBuilder } from 'react-querybuilder'; export function App() { return ; } ``` --- # ActionElement Renders a ` ); } ``` 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 => , }} /> ); } ``` ### "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) => ( ); // ✅ 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, 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 ( ) } ``` Examples #### `Option[]` example[​](#option-example "Direct link to option-example") ``` toOptions([ { value: 'firstName', label: 'First Name' }, { value: 'lastName', label: 'Last Name' }, ]); // yields (approximately): [ , , ]; ``` #### `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): [ , , ]; ``` ### `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 (
{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 ( <>
{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 `` 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