@react-querybuilder/mcp 8.25.0 → 8.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import MiniSearch from "minisearch";
|
|
2
2
|
import { bigIntJsonParseReviver, bigIntJsonStringifyReplacer, formatQuery, getQueryJsonSchema, isRuleGroupTypeIC } from "@react-querybuilder/core";
|
|
3
3
|
import { McpServer, ResourceTemplate, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
4
|
-
|
|
5
|
-
//#region src/docs.ts
|
|
4
|
+
// src/docs.ts
|
|
6
5
|
/** Build-time docs snapshot. */
|
|
7
6
|
const docsIndex = {
|
|
8
|
-
version: "8.
|
|
7
|
+
version: "8.26.0",
|
|
9
8
|
pages: [
|
|
10
9
|
{
|
|
11
10
|
"slug": "ai",
|
|
@@ -178,7 +177,7 @@ const docsIndex = {
|
|
|
178
177
|
{
|
|
179
178
|
"slug": "tips/comparison",
|
|
180
179
|
"title": "Comparison with other libraries",
|
|
181
|
-
"description": "How React Query Builder compares to react-awesome-query-builder",
|
|
180
|
+
"description": "How React Query Builder compares to react-awesome-query-builder, jQuery QueryBuilder, commercial suites, and other filtering UIs",
|
|
182
181
|
"url": "https://react-querybuilder.js.org/docs/tips/comparison"
|
|
183
182
|
},
|
|
184
183
|
{
|
|
@@ -223,6 +222,12 @@ const docsIndex = {
|
|
|
223
222
|
"description": "How to avoid common pitfalls for a smoother user experience",
|
|
224
223
|
"url": "https://react-querybuilder.js.org/docs/tips/maximizing-performance"
|
|
225
224
|
},
|
|
225
|
+
{
|
|
226
|
+
"slug": "tips/migrate-from-jquery-querybuilder",
|
|
227
|
+
"title": "Migrating from jQuery QueryBuilder",
|
|
228
|
+
"description": "Convert jQuery QueryBuilder filters and rules to React Query Builder",
|
|
229
|
+
"url": "https://react-querybuilder.js.org/docs/tips/migrate-from-jquery-querybuilder"
|
|
230
|
+
},
|
|
226
231
|
{
|
|
227
232
|
"slug": "tips/migrate-from-raqb",
|
|
228
233
|
"title": "Migrating from react-awesome-query-builder",
|
|
@@ -1603,7 +1608,7 @@ const docsIndex = {
|
|
|
1603
1608
|
"page": 19,
|
|
1604
1609
|
"heading": "Onward and upward!",
|
|
1605
1610
|
"anchor": "onward-and-upward",
|
|
1606
|
-
"text": "## Onward and upward!\n\nExplore all React Query Builder options in the [main component documentation](./components/querybuilder). Try different configurations, export/import formats, and features in the interactive [demo](/demo).\n\nComing from another library? See [how React Query Builder compares](./tips/comparison), and the
|
|
1611
|
+
"text": "## Onward and upward!\n\nExplore all React Query Builder options in the [main component documentation](./components/querybuilder). Try different configurations, export/import formats, and features in the interactive [demo](/demo).\n\nComing from another library? See [how React Query Builder compares](./tips/comparison), and the guides for migrating from [react-awesome-query-builder](./tips/migrate-from-raqb) and [jQuery QueryBuilder](./tips/migrate-from-jquery-querybuilder)."
|
|
1607
1612
|
},
|
|
1608
1613
|
{
|
|
1609
1614
|
"page": 19,
|
|
@@ -2059,37 +2064,49 @@ const docsIndex = {
|
|
|
2059
2064
|
"page": 28,
|
|
2060
2065
|
"heading": "",
|
|
2061
2066
|
"anchor": "",
|
|
2062
|
-
"text": "
|
|
2067
|
+
"text": "This page compares React Query Builder with other query builder libraries in detail for [react-awesome-query-builder](#react-awesome-query-builder), and with a [summary matrix](#other-libraries) for everything else."
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
"page": 28,
|
|
2071
|
+
"heading": "react-awesome-query-builder",
|
|
2072
|
+
"anchor": "react-awesome-query-builder",
|
|
2073
|
+
"text": "## react-awesome-query-builder\n\nThe query builder library most often compared to React Query Builder (RQB) is [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB). Both render an interactive UI for building filter criteria, and both can export those criteria to several query languages, but they take meaningfully different approaches.\n\nThis page summarizes the differences as of RQB v8 and RAQB v6.6.15 (May 2025). If you're moving an existing RAQB implementation to RQB, see [Migrating from react-awesome-query-builder](./migrate-from-raqb)."
|
|
2063
2074
|
},
|
|
2064
2075
|
{
|
|
2065
2076
|
"page": 28,
|
|
2066
|
-
"heading": "Philosophy",
|
|
2077
|
+
"heading": "react-awesome-query-builder › Philosophy",
|
|
2067
2078
|
"anchor": "philosophy",
|
|
2068
|
-
"text": "
|
|
2079
|
+
"text": "### Philosophy\n\n| | React Query Builder | react-awesome-query-builder |\n| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| Query storage | Plain JSON ([`RuleGroupType`](../typescript#rules-and-groups)), serializable as-is | [immutable.js](https://immutable-js.com/) tree; plain JSON via `Utils.getTree()` |\n| Configuration | Discrete props (`fields`, `operators`, `combinators`, `controlElements`, etc.) | One monolithic `Config` object (types, widgets, operators, funcs, settings) |\n| Packaging | Composable packages — core, React, UI styles, and optional feature packages | Core package plus UI-specific packages |\n| Non-React usage | `@react-querybuilder/core` has no React dependency (server-safe); official [Solid, Svelte, and Vue ports](../compat#other-frameworks) | Core depends on React |\n\nRQB's plain-JSON query format means you can store queries in a database, send them over the wire, diff them, and hand-edit them without any conversion step. RQB's discrete props mean you only configure (and only bundle) the parts you use."
|
|
2069
2080
|
},
|
|
2070
2081
|
{
|
|
2071
2082
|
"page": 28,
|
|
2072
|
-
"heading": "Query model",
|
|
2083
|
+
"heading": "react-awesome-query-builder › Query model",
|
|
2073
2084
|
"anchor": "query-model",
|
|
2074
|
-
"text": "
|
|
2085
|
+
"text": "### Query model\n\n| Aspect | React Query Builder | react-awesome-query-builder |\n| -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |\n| Group children | `rules` | `children1` (array, or keyed object when `children1AsArray: false`) |\n| Combinators | `combinator` on the group, _or_ [independent combinators](../components/querybuilder#independent-combinators) interleaved between rules | `conjunction` on the group only |\n| Negation | `not` on groups | `not` on groups |\n| Rule value | Scalar or array, depending on the operator | Always an array (one entry per operand) |\n| Subqueries | [Match modes](./subqueries) — `all`, `some`, `none`, `atLeast`, `atMost`, `exactly` (with threshold) | `!group` fields with `some`/`all`/`none` and count operators |\n| Disabled rules | `disabled` | `isLocked` |\n| Muted rules | [`muted`](../utils/export#muted-rules-and-groups) (kept in the query, excluded from exports) | — |\n| Ternary/CASE | — | `switch_group` / `case_group` |\n| Value sources | `value`, `field`, [expressions](../expr), [parameters](./parameter-manager) | `value`, `field`, `func` |\n| Left-hand side | Expressions supported on the LHS as well as the RHS | `fieldSrc: \"func\"` |"
|
|
2075
2086
|
},
|
|
2076
2087
|
{
|
|
2077
2088
|
"page": 28,
|
|
2078
|
-
"heading": "Formats",
|
|
2089
|
+
"heading": "react-awesome-query-builder › Formats",
|
|
2079
2090
|
"anchor": "formats",
|
|
2080
|
-
"text": "
|
|
2091
|
+
"text": "### Formats\n\nRQB's [`formatQuery`](../utils/export) supports considerably more export targets, and its [parsers](../utils/import) support more import sources.\n\n| Direction | React Query Builder | react-awesome-query-builder |\n| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |\n| Export | JSON, SQL (plain/parameterized/named), MongoDB, CEL, JsonLogic, SpEL, ElasticSearch, JSONata, LDAP, natural language, Drizzle, TanStack DB, Prisma, Sequelize, Cypher, GraphQL, SPARQL, Gremlin, and more | SQL, MongoDB, JsonLogic, SpEL, ElasticSearch (partial), query string |\n| Import | SQL, CEL, JsonLogic, MongoDB, SpEL, JSONata, Cypher, GraphQL, SPARQL, Gremlin, **RAQB** | JsonLogic, SpEL, SQL (separate package) |"
|
|
2081
2092
|
},
|
|
2082
2093
|
{
|
|
2083
2094
|
"page": 28,
|
|
2084
|
-
"heading": "Ecosystem",
|
|
2095
|
+
"heading": "react-awesome-query-builder › Ecosystem",
|
|
2085
2096
|
"anchor": "ecosystem",
|
|
2086
|
-
"text": "
|
|
2097
|
+
"text": "### Ecosystem\n\n| Aspect | React Query Builder | react-awesome-query-builder |\n| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |\n| UI style packs | Ant Design, Bootstrap, Bulma, Chakra UI, Fluent UI, Mantine, MUI/Material, PrimeReact, Tremor, [shadcn/ui](../compat#shadcnui) (registry) | Ant Design, MUI, Material (v4), Bootstrap, Fluent UI |\n| React Native | `@react-querybuilder/native` | — |\n| Other frameworks | Official [Solid, Svelte, and Vue ports](../compat#other-frameworks) sharing the same core | — |\n| Drag-and-drop | [`@react-querybuilder/dnd`](../dnd) (opt-in) | Built in |\n| Date/time | [`@react-querybuilder/datetime`](../datetime) (relative dates, per-dialect formatting) | Built in (widgets and functions) |\n| Expressions | [`@react-querybuilder/expr`](../expr) | Built in (config `funcs`) |\n| Rules engine | [`@react-querybuilder/rules-engine`](../rules-engine) | — |\n| Accessibility | ARIA attributes, keyboard navigation, `data-testid` hooks | Not documented |\n| Internationalization | `translations` prop accepting strings or JSX | i18next-based locale packs |\n| Validation | Per-field `validator` plus [`defaultValidator`](../utils/validation) | `sanitizeTree` / `validateTree` / `isValidTree` |\n| Styling | Unstyled by default with CSS custom properties; official style packs | Bundled SCSS per UI package |"
|
|
2087
2098
|
},
|
|
2088
2099
|
{
|
|
2089
2100
|
"page": 28,
|
|
2090
|
-
"heading": "Feature-by-feature notes",
|
|
2101
|
+
"heading": "react-awesome-query-builder › Feature-by-feature notes",
|
|
2091
2102
|
"anchor": "feature-by-feature-notes",
|
|
2092
|
-
"text": "
|
|
2103
|
+
"text": "### Feature-by-feature notes\n\nMost RAQB concepts have a direct RQB counterpart. These are the exceptions:\n\n- **Ternary (`switch_group`)** — RAQB can build CASE/WHEN-style expressions. RQB has no equivalent in the query builder itself; conditional logic is generally modeled with [`@react-querybuilder/rules-engine`](../rules-engine) instead.\n- **`proximity` operator** — RAQB's full-text proximity operator (and its `operatorOptions`) has no RQB equivalent.\n- **`is_empty` / `is_not_empty`** — RQB expresses these as `=` and `!=` against an empty string.\n- **Locking** — RQB models this with the `disabled` property rather than a separate `locked` property, but the behavior and UI are equivalent. The [`showLockButtons`](../components/querybuilder#showlockbuttons) prop displays \"Lock rule\"/\"Lock group\" toggle buttons, which set `disabled` on the rule or group. As in RAQB, a locked group disables its header elements and all descendants. RQB has no equivalent to RAQB's `canDeleteLocked` setting.\n- **Date/time functions** — RAQB models `NOW`, `TODAY`, `RELATIVE_DATETIME`, etc. as functions. RQB treats the same concept as a _value_: [`@react-querybuilder/datetime`](../datetime) relative date/time values, which each rule processor serializes symbolically (e.g. `current_timestamp - interval '7 days'`).\n- **Cardinality operators** — RQB's match modes are a superset of RAQB's `!group` count operators, except that RQB has no strict-inequality modes (RAQB's `less`/`greater` counts)."
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
"page": 28,
|
|
2107
|
+
"heading": "Other libraries",
|
|
2108
|
+
"anchor": "other-libraries",
|
|
2109
|
+
"text": "## Other libraries\n\nThe table below summarizes other query builders and adjacent filtering UIs. \"Export\" and \"Import\" list built-in query languages only. Every library can save and load its own native format. Details were checked in October 2026.\n\n| Library | Framework | License | Nested groups | Native format | Export | Import | Drag-and-drop |\n| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------- | ------------------------------------------------------------------ | -------------------------------------------- | ------------------------------ | ------------------------ |\n| **React Query Builder** | React, React Native; official [Solid, Svelte, and Vue ports](../compat#other-frameworks) | MIT | Yes | Plain JSON (`{ combinator, rules }`) | [20+ formats](../utils/export) | [10+ formats](../utils/import) | Yes ([opt-in](../dnd)) |\n| [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) | React | MIT | Yes | immutable.js tree (plain JSON via `getTree`) | SQL, MongoDB, JsonLogic, SpEL, ElasticSearch | JsonLogic, SpEL, SQL | Yes |\n| [jQuery QueryBuilder](https://querybuilder.js.org/) | jQuery + Bootstrap | MIT | Yes | Plain JSON (`{ condition, rules }`) | SQL, MongoDB (plugins) | SQL, MongoDB (plugins) | Yes (`sortable` plugin) |\n| [angular2-query-builder](https://github.com/zebzhao/Angular-QueryBuilder) and forks ([ngx-angular-query-builder](https://www.npmjs.com/package/ngx-angular-query-builder)) | Angular | MIT | Yes | Plain JSON (`{ condition, rules }`) | — | — | No |\n| [vue-query-builder](https://github.com/dabernathy89/vue-query-builder) / [query-builder-vue](https://github.com/rtucek/vue-query-builder) | Vue 2 | MIT | Yes | Plain JSON | — | — | `query-builder-vue` only |\n| [Syncfusion QueryBuilder](https://www.syncfusion.com/react-components/react-query-builder) | React, Angular, Vue, JS, Blazor | Commercial (free community license) | Yes | Plain JSON (`{ condition, not, rules }`) | SQL (inline/parameterized), MongoDB | SQL, MongoDB | Yes |\n| [KendoReact Filter](https://www.telerik.com/kendo-react-ui/components/datatools/filter) | React | Commercial | Yes | `CompositeFilterDescriptor` (`{ logic, filters }`) | OData (via `kendo-data-query`) | — | No |\n| [DevExtreme FilterBuilder](https://js.devexpress.com/React/Documentation/Guide/UI_Components/FilterBuilder/) | React, Angular, Vue, jQuery | Commercial | Yes | Nested arrays (`[[\"price\", \">\", 10], \"and\", [...]]`) | — (consumed by DevExtreme data sources) | — | No |\n| [MUI X Data Grid filtering](https://mui.com/x/react-data-grid/filtering/) | React | MIT (Pro/Premium commercial) | No (flat list, single and/or) | `GridFilterModel` | — | — | No |\n| [AG Grid Advanced Filter](https://www.ag-grid.com/react-data-grid/filter-advanced/) | React, Angular, Vue, JS | Commercial (Enterprise) | Yes | `AdvancedFilterModel` (`{ filterType: 'join', type, conditions }`) | — | — | Yes (builder dialog) |\n| [Elastic EUI SearchBar](https://eui.elastic.co/docs/components/forms/search-and-filter/search-bar/) | React | Elastic License 2.0 / SSPL | Limited (OR clauses) | Text syntax parsed to a `Query` AST | Elasticsearch DSL | Text syntax | No |\n| [react-filter-box](https://github.com/nhabuiduc/react-filter-box) | React | MIT | Yes (parentheses) | Text syntax parsed to an expression array | — | Text syntax | No |\n| [Blockly](https://developers.google.com/blockly) | Framework-agnostic | Apache-2.0 | Yes | Block workspace (XML/JSON) | Via custom code generators | — | Yes (inherent) |\n\nSome notes on these categories:\n\n- **jQuery QueryBuilder** is the predecessor of most libraries in this space. RQB's original API was modeled on it, so concepts map closely. See [Migrating from jQuery QueryBuilder](./migrate-from-jquery-querybuilder).\n- **Angular and Vue query builders** mostly follow jQuery QueryBuilder's data model but don't export to any query language, and the community Vue builders are unmaintained. RQB has official [Solid, Svelte, and Vue ports](../compat#other-frameworks) that share its query format, import/export utilities, and class-compatible styling. For Angular or any other framework, [`@react-querybuilder/core`](../utils/framework-adapters) provides the framework-agnostic derivations those ports are built on, along with query management, validation, import, and export.\n- **Commercial suites** (Syncfusion, Kendo, DevExtreme) are a good fit if you already use their component library, since their builders share its theming and data layers. Syncfusion's is the most similar to RQB in scope. Kendo and DevExtreme filter builders are designed mainly to feed their own grids and data sources.\n- **Data grid filter panels** (MUI X, AG Grid) cover the common case of filtering one table without a standalone query object. MUI X's filter model has no nested groups. AG Grid's Advanced Filter combines a text expression bar with a tree builder.\n- **Search-bar style inputs** (EUI SearchBar, react-filter-box) trade the tree UI for typed syntax with autocomplete. That works well for power users but is harder to discover.\n- **Blockly** can be used as a visual query editor with custom blocks and code generators, but no maintained Blockly-based query builder package exists."
|
|
2093
2110
|
},
|
|
2094
2111
|
{
|
|
2095
2112
|
"page": 29,
|
|
@@ -2217,6 +2234,12 @@ const docsIndex = {
|
|
|
2217
2234
|
"anchor": "getoperators-prop",
|
|
2218
2235
|
"text": "## `getOperators` prop\n\nWhile the field `operators` property works well for individual field customizations, the `getOperators` function prop centralizes all operator logic in one location.\n\n`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.\n\nThis 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\"`.\n\n```tsx\nimport { defaultOperators, Field, QueryBuilder, RuleGroupType } from 'react-querybuilder';\nimport 'react-querybuilder/dist/query-builder.css';\n\nconst fields: Field[] = [\n { name: 'name', label: 'Name', datatype: 'text' },\n { name: 'birthday', label: 'Birthday', datatype: 'date', inputType: 'date' },\n { name: 'guitars', label: 'Guitars', datatype: 'number', inputType: 'number' },\n {\n name: 'favoriteMovie',\n label: 'Favorite Movie',\n datatype: 'text',\n operators: [{ name: '=', label: 'is' }],\n },\n];\n\nconst getOperators = (fieldName: string, { fieldData }: { fieldData: Field }) => {\n switch (fieldData.datatype) {\n case 'text':\n return [\n { name: '=', label: 'is' },\n { name: '!=', label: 'is not' },\n ...defaultOperators.filter(op =>\n [\n 'contains',\n 'beginsWith',\n 'endsWith',\n 'doesNotContain',\n 'doesNotBeginWith',\n 'doesNotEndWith',\n 'null',\n 'notNull',\n 'in',\n 'notIn',\n ].includes(op.name)\n ),\n ];\n case 'number':\n return [\n ...defaultOperators.filter(op => ['=', '!='].includes(op.name)),\n { name: '<', label: 'less than' },\n { name: '<=', label: 'less than or equal to' },\n { name: '>', label: 'greater than' },\n { name: '>=', label: 'greater than or equal to' },\n ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),\n ];\n case 'date':\n return [\n { name: '=', label: 'on' },\n { name: '!=', label: 'not on' },\n { name: '<', label: 'before' },\n { name: '<=', label: 'on or before' },\n { name: '>', label: 'after' },\n { name: '>=', label: 'on or after' },\n ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),\n ];\n }\n return defaultOperators;\n};\n\nconst defaultQuery: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'name', operator: 'beginsWith', value: 'Stev' },\n { field: 'birthday', operator: '<', value: '1970-01-01' },\n { field: 'guitars', operator: '>', value: 5 },\n { field: 'favoriteMovie', operator: '=', value: 'Crossroads (1986)' },\n ],\n};\n\nexport default () => (\n <QueryBuilder fields={fields} defaultQuery={defaultQuery} getOperators={getOperators} />\n);\n```"
|
|
2219
2236
|
},
|
|
2237
|
+
{
|
|
2238
|
+
"page": 34,
|
|
2239
|
+
"heading": "Regular expression operators",
|
|
2240
|
+
"anchor": "regular-expression-operators",
|
|
2241
|
+
"text": "## Regular expression operators\n\nThe `matches` and `doesNotMatch` operators test a field against a regular expression. They are not part of `defaultOperators`; opt in with `defaultOperatorsWithRegex` (or append `defaultRegexOperators` to your own list).\n\n```ts\nimport { defaultOperatorsWithRegex, type Field } from 'react-querybuilder';\n\nconst fields: Field[] = [{ name: 'sku', label: 'SKU', operators: defaultOperatorsWithRegex }];\n```\n\nThe rule `value` is the pattern string (no slashes), so the default text `ValueEditor` works as-is. Flags go in the optional `regexFlags` rule property:\n\n```json\n{ \"field\": \"sku\", \"operator\": \"matches\", \"value\": \"^AB-\\\\d+\", \"regexFlags\": \"i\" }\n```\n\nThere's no built-in control for `regexFlags`; use a custom `valueEditor` if users need to set flags. `matches` uses unanchored \"find\" semantics (like `RegExp#test`) in every export format. See [Export > Regular expressions](../utils/export#regular-expressions) for per-format output and [Import > Regular expressions](../utils/import#regular-expressions) for parser support."
|
|
2242
|
+
},
|
|
2220
2243
|
{
|
|
2221
2244
|
"page": 35,
|
|
2222
2245
|
"heading": "",
|
|
@@ -2257,1631 +2280,1721 @@ const docsIndex = {
|
|
|
2257
2280
|
"page": 36,
|
|
2258
2281
|
"heading": "",
|
|
2259
2282
|
"anchor": "",
|
|
2260
|
-
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nThis guide covers moving an existing [
|
|
2283
|
+
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nThis guide covers moving an existing [jQuery QueryBuilder](https://querybuilder.js.org/) (jQQB) implementation to React Query Builder (RQB). For a broader overview, see [Comparison with other libraries](./comparison).\n\nRQB's original API was modeled on jQQB, so most concepts map directly. Both libraries store queries as plain JSON with nearly the same structure. Most of the work is renaming properties and operators.\n\n**Tip:**\n\nNot moving to React? Official [Solid, Svelte, and Vue ports](../compat#other-frameworks) use the same query format, fields, operators, and import/export utilities, so everything below except the component examples applies to them as well."
|
|
2261
2284
|
},
|
|
2262
2285
|
{
|
|
2263
2286
|
"page": 36,
|
|
2264
2287
|
"heading": "Concept mapping",
|
|
2265
2288
|
"anchor": "concept-mapping",
|
|
2266
|
-
"text": "## Concept mapping\n\n|
|
|
2289
|
+
"text": "## Concept mapping\n\n| jQuery QueryBuilder | React Query Builder |\n| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `$('#builder').queryBuilder({ ... })` | A [`<QueryBuilder />`](../components/querybuilder) element |\n| `filters` | [`fields`](./managing-fields) |\n| `operators` (global) | [`operators`](../components/querybuilder#operators) / [`getOperators`](../components/querybuilder#getoperators) |\n| `conditions` | [`combinators`](../components/querybuilder#combinators) |\n| `default_condition` | `combinator` of the initial query |\n| `rules` option / `setRules()` | [`query`](../components/querybuilder#query) / [`defaultQuery`](../components/querybuilder#defaultquery) |\n| `getRules()` | The query object itself ([`onQueryChange`](../components/querybuilder#onquerychange)) |\n| `getSQL()` / `getMongo()` | [`formatQuery(query, 'sql')`](../utils/export) / `formatQuery(query, 'mongodb_query')` |\n| `setRulesFromSQL()` / `setRulesFromMongo()` | [`parseSQL`](../utils/import#sql) / [`parseMongoDB`](../utils/import#mongodb) |\n| `validate()` | [`validator`](../components/querybuilder#validator) prop, per-field `validator`, [`defaultValidator`](../utils/validation) |\n| `reset()` | Set the query to `{ combinator: 'and', rules: [] }` |\n| `templates`, `icons` | [`controlElements`](../components/querybuilder#controlelements), [`controlClassnames`](../components/querybuilder#controlclassnames), [`translations`](../components/querybuilder#translations) |\n| `lang_code` / `lang` / i18n files | [`translations`](../components/querybuilder#translations) |\n| `*.queryBuilder` events | [`onQueryChange`](../components/querybuilder#onquerychange), [`onAddRule`](../components/querybuilder#onaddrule), [`onAddGroup`](../components/querybuilder#onaddgroup), [`onRemove`](../components/querybuilder#onremove) |\n| Rule/group `data` | Arbitrary extra properties on rules and groups (preserved as-is) |"
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
"page": 36,
|
|
2293
|
+
"heading": "Settings",
|
|
2294
|
+
"anchor": "settings",
|
|
2295
|
+
"text": "## Settings\n\n| jQQB setting | RQB equivalent |\n| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `allow_groups: false` | `maxLevels={0}` (see also [Limiting groups](./limit-groups)) |\n| `allow_groups: <n>` | [`maxLevels={n}`](../components/querybuilder#maxlevels) |\n| `allow_empty` | Return `true` from a query-level [`validator`](../components/querybuilder#validator) for empty queries (RQB doesn't fail validation on empty queries by default) |\n| `default_filter` | [`getDefaultField`](../components/querybuilder#getdefaultfield) |\n| `display_empty_filter: false` | [`autoSelectField`](../components/querybuilder#autoselectfield) (default `true`) |\n| `select_placeholder` | `translations.fields.placeholderLabel` |\n| `sort_filters` | Sort the `fields` array before passing it in |\n| `optgroups` / filter `optgroup` | [Option groups](./option-lists) in `fields`, `operators`, or `values` |\n| `inputs_separator` | Custom [`valueEditor`](../components/valueeditor) |"
|
|
2296
|
+
},
|
|
2297
|
+
{
|
|
2298
|
+
"page": 36,
|
|
2299
|
+
"heading": "Plugins",
|
|
2300
|
+
"anchor": "plugins",
|
|
2301
|
+
"text": "## Plugins\n\n| jQQB plugin | RQB equivalent |\n| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |\n| `sortable` | [`@react-querybuilder/dnd`](../dnd) with [`enableDragAndDrop`](../components/querybuilder#enabledraganddrop) |\n| `not-group` | [`showNotToggle`](../components/querybuilder#shownottoggle) |\n| `sql-support` | [`formatQuery`](../utils/export) (`sql`, `parameterized`, `parameterized_named`) and [`parseSQL`](../utils/import#sql) |\n| `mongodb-support` | [`formatQuery`](../utils/export) (`mongodb_query`) and [`parseMongoDB`](../utils/import#mongodb) |\n| `invert` | No built-in action. Toggle `not` on the group, or write a custom action using [query tools](../utils/query-management) |\n| `unique-filter` | Filter the field list in a custom [`fieldSelector`](../components/querybuilder#controlelements) |\n| `change-filters` | Pass a new `fields` array, since props are reactive |\n| `filter-description` | Custom [`fieldSelector`](../components/querybuilder#controlelements) or field `label` |\n| `bt-tooltip-errors` | Validation results are applied as `queryBuilder-valid`/`queryBuilder-invalid` classes and passed to every component as `validation` |\n| `bt-checkbox`, `bt-selectpicker`, `chosen-selectpicker` | A [compatibility package](../compat) (e.g. Bootstrap) or a custom [`valueEditor`](../components/valueeditor) |\n| Filter `plugin` / `plugin_config` (datepickers, sliders, etc.) | A custom [`valueEditor`](../components/valueeditor), or [`@react-querybuilder/datetime`](../datetime) for dates |"
|
|
2267
2302
|
},
|
|
2268
2303
|
{
|
|
2269
2304
|
"page": 36,
|
|
2270
2305
|
"heading": "Rendering the component",
|
|
2271
2306
|
"anchor": "rendering-the-component",
|
|
2272
|
-
"text": "## Rendering the component\n\
|
|
2307
|
+
"text": "## Rendering the component\n\n```html title=\"Before (jQQB)\"\n<div id=\"builder\"></div>\n<button id=\"btn-sql\">Get SQL</button>\n\n<script>\n $('#builder').queryBuilder({\n plugins: ['sortable', 'not-group'],\n allow_groups: 2,\n filters: [\n { id: 'name', label: 'Name', type: 'string' },\n {\n id: 'category',\n label: 'Category',\n type: 'integer',\n input: 'select',\n values: { 1: 'Books', 2: 'Movies' },\n operators: ['equal', 'not_equal', 'in', 'not_in'],\n },\n { id: 'price', label: 'Price', type: 'double', validation: { min: 0, step: 0.01 } },\n ],\n rules: {\n condition: 'AND',\n rules: [{ id: 'price', operator: 'less', value: 10.25 }],\n },\n });\n\n $('#btn-sql').on('click', () => {\n console.log($('#builder').queryBuilder('getSQL', 'question_mark'));\n });\n<\/script>\n```\n\n```tsx title=\"After (RQB)\"\nimport { useState } from 'react';\nimport type { Field, RuleGroupType } from 'react-querybuilder';\nimport { formatQuery, QueryBuilder } from 'react-querybuilder';\nimport { QueryBuilderDnD } from '@react-querybuilder/dnd';\nimport * as ReactDnD from 'react-dnd';\nimport * as ReactDndHtml5Backend from 'react-dnd-html5-backend';\nimport 'react-querybuilder/dist/query-builder.css';\n\nconst fields: Field[] = [\n { name: 'name', label: 'Name' },\n {\n name: 'category',\n label: 'Category',\n valueEditorType: 'select',\n values: [\n { name: '1', label: 'Books' },\n { name: '2', label: 'Movies' },\n ],\n operators: [\n { name: '=', label: '=' },\n { name: '!=', label: '!=' },\n { name: 'in', label: 'in' },\n { name: 'notIn', label: 'not in' },\n ],\n },\n {\n name: 'price',\n label: 'Price',\n inputType: 'number',\n validator: ({ value }) => Number(value) >= 0,\n },\n];\n\nconst initialQuery: RuleGroupType = {\n combinator: 'and',\n rules: [{ field: 'price', operator: '<', value: 10.25 }],\n};\n\nexport const App = () => {\n const [query, setQuery] = useState(initialQuery);\n\n return (\n <>\n <QueryBuilderDnD dnd={{ ...ReactDnD, ...ReactDndHtml5Backend }}>\n <QueryBuilder\n fields={fields}\n query={query}\n onQueryChange={setQuery}\n showNotToggle\n maxLevels={2}\n parseNumbers=\"strict-limited\"\n />\n </QueryBuilderDnD>\n <pre>{JSON.stringify(formatQuery(query, 'parameterized'), null, 2)}</pre>\n </>\n );\n};\n```\n\nNotable differences:\n\n- There's no imperative instance. The query is React state, so \"get rules\" means reading the state and \"set rules\" means updating it.\n- `formatQuery(query, 'parameterized')` returns `{ sql, params }`, like `getSQL('question_mark')`. Use `'parameterized_named'` for named parameters (like `getSQL('named')`) and `'sql'` for inline values (like `getSQL(false)`).\n- jQQB's `type: 'integer' | 'double'` makes values numeric. RQB value editors produce strings by default; use [`parseNumbers`](../components/querybuilder#parsenumbers) to get numbers."
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
"page": 36,
|
|
2311
|
+
"heading": "Operator mapping",
|
|
2312
|
+
"anchor": "operator-mapping",
|
|
2313
|
+
"text": "## Operator mapping\n\n| jQQB | RQB |\n| --------------------------------- | --------------------------------- |\n| `equal` / `not_equal` | `=` / `!=` |\n| `less` / `less_or_equal` | `<` / `<=` |\n| `greater` / `greater_or_equal` | `>` / `>=` |\n| `in` / `not_in` | `in` / `notIn` |\n| `between` / `not_between` | `between` / `notBetween` |\n| `begins_with` / `not_begins_with` | `beginsWith` / `doesNotBeginWith` |\n| `contains` / `not_contains` | `contains` / `doesNotContain` |\n| `ends_with` / `not_ends_with` | `endsWith` / `doesNotEndWith` |\n| `is_null` / `is_not_null` | `null` / `notNull` |\n| `is_empty` / `is_not_empty` | `=` / `!=` with an empty string |\n\nCustom jQQB operators (`{ type, nb_inputs, multiple, apply_to }`) become RQB operators with an [`arity`](./managing-operators) (`'unary'` for `nb_inputs: 0`, `'binary'` for 1, `'ternary'` for 2). You'll also need a [custom rule processor](../utils/export#rule-processor) for each export format the operator should support."
|
|
2273
2314
|
},
|
|
2274
2315
|
{
|
|
2275
2316
|
"page": 36,
|
|
2317
|
+
"heading": "Converting filters and rules",
|
|
2318
|
+
"anchor": "converting-filters-and-rules",
|
|
2319
|
+
"text": "## Converting filters and rules\n\nThere's no migration package for jQQB, since the conversion is small enough to include in your own code. The utilities below convert jQQB `filters` to RQB `fields` and `getRules()` output to an RQB query. Copy them into your project and adjust as needed.\n\n```ts title=\"convertJQQB.ts\"\nimport type { Field, Option, RuleGroupType, RuleType } from 'react-querybuilder';\n\ntype JQQBValues =\n | (string | number)[]\n | Record<string, string>\n | Record<string, string>[]\n | { value: string | number; label: string; optgroup?: string }[];\n\nexport interface JQQBFilter {\n id: string;\n field?: string;\n label?: string | Record<string, string>;\n type: 'string' | 'integer' | 'double' | 'date' | 'time' | 'datetime' | 'boolean';\n input?:\n | 'text'\n | 'number'\n | 'textarea'\n | 'radio'\n | 'checkbox'\n | 'select'\n | ((...args: unknown[]) => string);\n values?: JQQBValues;\n multiple?: boolean;\n operators?: string[];\n default_operator?: string;\n default_value?: unknown;\n placeholder?: string;\n}\n\nexport interface JQQBRule {\n id?: string;\n field?: string;\n operator?: string;\n value?: unknown;\n flags?: Record<string, boolean>;\n data?: unknown;\n}\n\nexport interface JQQBGroup {\n condition?: string;\n not?: boolean;\n rules: (JQQBRule | JQQBGroup)[];\n flags?: Record<string, boolean>;\n data?: unknown;\n}\n\nexport const operatorMap: Record<string, string> = {\n equal: '=',\n not_equal: '!=',\n less: '<',\n less_or_equal: '<=',\n greater: '>',\n greater_or_equal: '>=',\n in: 'in',\n not_in: 'notIn',\n between: 'between',\n not_between: 'notBetween',\n begins_with: 'beginsWith',\n not_begins_with: 'doesNotBeginWith',\n contains: 'contains',\n not_contains: 'doesNotContain',\n ends_with: 'endsWith',\n not_ends_with: 'doesNotEndWith',\n is_null: 'null',\n is_not_null: 'notNull',\n is_empty: '=',\n is_not_empty: '!=',\n};\n\nconst inputTypeMap: Partial<Record<JQQBFilter['type'], string>> = {\n integer: 'number',\n double: 'number',\n date: 'date',\n time: 'time',\n datetime: 'datetime-local',\n};\n\nconst convertValues = (values: JQQBValues): Option[] => {\n if (Array.isArray(values)) {\n return values.flatMap((v): Option[] => {\n if (typeof v !== 'object') return [{ name: `${v}`, label: `${v}` }];\n if ('value' in v && 'label' in v) return [{ name: `${v.value}`, label: `${v.label}` }];\n return Object.entries(v).map(([name, label]) => ({ name, label: `${label}` }));\n });\n }\n return Object.entries(values).map(([name, label]) => ({ name, label }));\n};\n\nexport const convertJQQBFilters = (filters: JQQBFilter[], lang = 'en'): Field[] =>\n filters.map(f => {\n const field: Field = {\n // jQQB allows several filters per field; RQB field names must be unique\n name: f.id,\n label:\n typeof f.label === 'object' ? (f.label[lang] ?? f.label.en) : (f.label ?? f.field ?? f.id),\n };\n const inputType = inputTypeMap[f.type];\n if (inputType) field.inputType = inputType;\n if (f.input === 'select') field.valueEditorType = f.multiple ? 'multiselect' : 'select';\n else if (f.input === 'radio') field.valueEditorType = 'radio';\n else if (f.input === 'textarea') field.valueEditorType = 'textarea';\n else if (f.input === 'checkbox') field.valueEditorType = f.values ? 'multiselect' : 'checkbox';\n else if (f.type === 'boolean') field.valueEditorType = 'checkbox';\n if (f.values) field.values = convertValues(f.values);\n if (f.operators) {\n field.operators = f.operators.map(op => ({\n name: operatorMap[op] ?? op,\n label: op.replaceAll('_', ' '),\n }));\n }\n if (f.default_operator)\n field.defaultOperator = operatorMap[f.default_operator] ?? f.default_operator;\n if (f.default_value !== undefined) field.defaultValue = f.default_value;\n if (f.placeholder) field.placeholder = f.placeholder;\n return field;\n });\n\nconst isGroup = (r: JQQBRule | JQQBGroup): r is JQQBGroup => 'rules' in r && Array.isArray(r.rules);\n\nconst isReadonly = (flags?: Record<string, boolean>) =>\n !!flags && (flags.readonly || flags.filter_readonly || flags.condition_readonly);\n\nexport const convertJQQBRules = (group: JQQBGroup): RuleGroupType => ({\n combinator: (group.condition ?? 'AND').toLowerCase(),\n ...(group.not ? { not: true } : {}),\n ...(isReadonly(group.flags) ? { disabled: true } : {}),\n ...(group.data === undefined ? {} : { data: group.data }),\n rules: group.rules.map(r => (isGroup(r) ? convertJQQBRules(r) : convertJQQBRule(r))),\n});\n\nconst convertJQQBRule = (r: JQQBRule): RuleType => {\n const operator = r.operator ?? 'equal';\n const value = operator === 'is_empty' || operator === 'is_not_empty' ? '' : (r.value ?? '');\n return {\n field: r.id ?? r.field ?? '',\n operator: operatorMap[operator] ?? operator,\n value,\n ...(isReadonly(r.flags) ? { disabled: true } : {}),\n ...(r.data === undefined ? {} : { data: r.data }),\n };\n};\n```\n\n```ts title=\"Usage\"\nimport { convertJQQBFilters, convertJQQBRules } from './convertJQQB';\n\nconst fields = convertJQQBFilters(jqqbFilters);\nconst query = convertJQQBRules($('#builder').queryBuilder('getRules', { get_flags: true }));\n```\n\nTranslation notes:\n\n- **Field identity.** jQQB rules reference a filter by `id`, and several filters can share one `field`. RQB fields are identified by `name`, so the converter uses the jQQB `id`. If your filters use `id` and `field` differently, set each RQB field's [`name`](./managing-fields) to the `id` and rename it in exports with a custom [`ruleProcessor`](../utils/export#rule-processor).\n- **Values.** Multi-value operators (`between`, `in`, `multiple` selects) produce arrays in both libraries, and RQB accepts arrays as is. Pass [`listsAsArrays`](../components/querybuilder#listsasarrays) so RQB's value editors also emit arrays instead of comma-separated strings.\n- **Flags.** RQB has a single [`disabled`](../components/querybuilder#disabled) state. The converter maps `readonly`, `filter_readonly`, and `condition_readonly` to `disabled`. Finer-grained flags (`operator_readonly`, `value_readonly`, `no_delete`, `no_add_rule`, `no_add_group`) need custom [`controlElements`](../components/querybuilder#controlelements) that read the rule's properties. Keep the `flags` object on the rule if you need them.\n- **`is_empty` / `is_not_empty`.** These become `=` / `!=` with an empty string. If you need a true \"empty\" check (e.g. `NULL OR ''`), define custom operators.\n- **Validation.** jQQB's declarative `validation` (`min`, `max`, `step`, `format`, `allow_empty_value`, `callback`) becomes a per-field [`validator`](../utils/validation) function. Number bounds and `step` can also be passed to the value editor via [`inputType`](./managing-fields) and custom value editor props.\n- **SQL and MongoDB round-trips.** If your queries are stored as SQL or MongoDB rather than jQQB JSON, skip `convertJQQBRules` and use [`parseSQL`](../utils/import#sql) or [`parseMongoDB`](../utils/import#mongodb) directly."
|
|
2320
|
+
},
|
|
2321
|
+
{
|
|
2322
|
+
"page": 37,
|
|
2323
|
+
"heading": "",
|
|
2324
|
+
"anchor": "",
|
|
2325
|
+
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nThis 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](./comparison).\n\nTwo utilities do most of the work:\n\n- `parseRAQB` converts a saved RAQB query tree to an RQB query object.\n- `parseRAQBFields` converts an RAQB `Config` (or just its `fields`) to an RQB `fields` array.\n\nBoth 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.\n\n```bash npm2yarn\nnpm i @react-querybuilder/migrate-raqb\n```\n\n```ts\nimport { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';\n```\n\nThe package has no runtime dependencies—only a peer dependency on `@react-querybuilder/core` (v8.21.2 or later, which `react-querybuilder` itself depends on). It adds no dependency on RAQB or immutable.js.\n\n**Tip:**\n\nOnce your queries are converted and saved in RQB format, uninstall `@react-querybuilder/migrate-raqb`. Nothing in your application needs it at runtime."
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
"page": 37,
|
|
2329
|
+
"heading": "Concept mapping",
|
|
2330
|
+
"anchor": "concept-mapping",
|
|
2331
|
+
"text": "## Concept mapping\n\n| RAQB | React Query Builder |\n| ----------------------------------- | -------------------------------------------------------------------------------- |\n| `Config` | Discrete props: `fields`, `operators`, `combinators`, `controlElements` |\n| `Config.fields` | [`fields`](./managing-fields) |\n| `Query` + `Builder` render prop | A single [`<QueryBuilder />`](../components/querybuilder) element |\n| immutable tree + `Utils.getTree()` | Plain JSON query object, used directly |\n| `group` node | Rule group (an object with a `rules` array) |\n| `properties.conjunction` | `combinator` |\n| `properties.not` | `not` |\n| `rule` node | Rule (`{ field, operator, value }`) |\n| `properties.valueSrc` | `valueSource` |\n| `properties.isLocked` | `disabled` (see [`showLockButtons`](../components/querybuilder#showlockbuttons)) |\n| `rule_group` (`!group` field) | Rule with a [match mode](./subqueries) and a nested group as its value |\n| `switch_group` / `case_group` | No equivalent — see [Unsupported constructs](#unsupported-constructs) |\n| Widgets | [`valueEditorType` / `inputType`](./managing-fields) or a custom `valueEditor` |\n| `funcs` | [`@react-querybuilder/expr`](../expr) |\n| `sqlFormat`, `mongodbFormat`, etc. | [`formatQuery`](../utils/export) |\n| `loadFromJsonLogic`, `loadFromSpel` | [`parseJsonLogic`, `parseSpEL`](../utils/import) |"
|
|
2332
|
+
},
|
|
2333
|
+
{
|
|
2334
|
+
"page": 37,
|
|
2335
|
+
"heading": "Rendering the component",
|
|
2336
|
+
"anchor": "rendering-the-component",
|
|
2337
|
+
"text": "## Rendering the component\n\nRAQB splits rendering across `<Query>`, a `renderBuilder` callback, and `<Builder>`, 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.\n\n```tsx title=\"Before (RAQB)\"\nimport { useCallback, useState } from 'react';\nimport type {\n BuilderProps,\n Config,\n ImmutableTree,\n JsonGroup,\n} from '@react-awesome-query-builder/ui';\nimport { BasicConfig, Builder, Query, Utils as QbUtils } from '@react-awesome-query-builder/ui';\nimport '@react-awesome-query-builder/ui/css/styles.css';\n\nconst config: Config = {\n ...BasicConfig,\n fields: {\n price: { label: 'Price', type: 'number' },\n color: {\n label: 'Color',\n type: 'select',\n fieldSettings: {\n listValues: [\n { value: 'yellow', title: 'Yellow' },\n { value: 'green', title: 'Green' },\n ],\n },\n },\n },\n settings: {\n ...BasicConfig.settings,\n showNot: true,\n showLock: true,\n maxNesting: 3,\n addRuleLabel: 'New rule',\n },\n};\n\nconst initialTree: JsonGroup = { id: QbUtils.uuid(), type: 'group' };\n\nexport const App = () => {\n const [tree, setTree] = useState(() => QbUtils.loadTree(initialTree));\n\n const onChange = useCallback((immutableTree: ImmutableTree) => {\n setTree(immutableTree);\n // Convert before saving — the state itself is an immutable.js tree\n console.log(QbUtils.getTree(immutableTree));\n }, []);\n\n const renderBuilder = useCallback(\n (props: BuilderProps) => (\n <div className=\"query-builder-container\">\n <div className=\"query-builder qb-lite\">\n <Builder {...props} />\n </div>\n </div>\n ),\n []\n );\n\n return (\n <>\n <Query {...config} value={tree} onChange={onChange} renderBuilder={renderBuilder} />\n <pre>{QbUtils.sqlFormat(tree, config)}</pre>\n </>\n );\n};\n```\n\n```tsx title=\"After (RQB)\"\nimport { useState } from 'react';\nimport type { Field, RuleGroupType } from 'react-querybuilder';\nimport { formatQuery, QueryBuilder } from 'react-querybuilder';\nimport 'react-querybuilder/dist/query-builder.css';\n\nconst fields: Field[] = [\n { name: 'price', label: 'Price', inputType: 'number' },\n {\n name: 'color',\n label: 'Color',\n valueEditorType: 'select',\n values: [\n { name: 'yellow', label: 'Yellow' },\n { name: 'green', label: 'Green' },\n ],\n },\n];\n\nconst initialQuery: RuleGroupType = { combinator: 'and', rules: [] };\n\nexport const App = () => {\n // Already plain JSON — save it as-is\n const [query, setQuery] = useState(initialQuery);\n\n return (\n <>\n <QueryBuilder\n fields={fields}\n query={query}\n onQueryChange={setQuery}\n showNotToggle\n showLockButtons\n maxLevels={3}\n translations={{ addRule: { label: 'New rule' } }}\n />\n <pre>{formatQuery(query, 'sql')}</pre>\n </>\n );\n};\n```\n\nNotable differences:\n\n- 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.\n- `onQueryChange` receives the query object directly, so it can be passed straight to a `useState` setter and saved without conversion.\n- There is no `renderBuilder` indirection or required wrapper markup. Use [`controlClassnames`](../components/querybuilder#controlclassnames) and [`controlElements`](../components/querybuilder#controlelements) to customize markup and styling.\n- Export functions take the query object, not a tree plus a config."
|
|
2338
|
+
},
|
|
2339
|
+
{
|
|
2340
|
+
"page": 37,
|
|
2276
2341
|
"heading": "Rendering the component › Settings",
|
|
2277
2342
|
"anchor": "settings",
|
|
2278
2343
|
"text": "### Settings\n\nRAQB's `config.settings` correspond to individual `<QueryBuilder />` props:\n\n| RAQB setting | RQB prop |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `showNot` | [`showNotToggle`](../components/querybuilder#shownottoggle) |\n| `showLock` | [`showLockButtons`](../components/querybuilder#showlockbuttons) |\n| `canReorder` / `canRegroup` | [`enableDragAndDrop`](../components/querybuilder#enabledraganddrop) (requires [`@react-querybuilder/dnd`](../dnd)) |\n| `maxNesting` | [`maxLevels`](../components/querybuilder#maxlevels) |\n| `maxNumberOfRules` | Return `false` from [`onAddRule`](../components/querybuilder#onaddrule) |\n| `immutableGroupsMode` and friends | [`disabled`](../components/querybuilder#disabled) (all or by path) |\n| `clearValueOnChangeField` / `clearValueOnChangeOp` | [`resetOnFieldChange`](../components/querybuilder#resetonfieldchange) / [`resetOnOperatorChange`](../components/querybuilder#resetonoperatorchange) |\n| `setOpOnChangeField` | [`getDefaultOperator`](../components/querybuilder#getdefaultoperator), [`autoSelectOperator`](../components/querybuilder#autoselectoperator) |\n| `defaultField` / `defaultOperator` | [`getDefaultField`](../components/querybuilder#getdefaultfield) / [`getDefaultOperator`](../components/querybuilder#getdefaultoperator) |\n| `defaultConjunction` | First entry of [`combinators`](../components/querybuilder#combinators), or the `combinator` of your initial query |\n| `addRuleLabel`, `valuePlaceholder`, and other labels | [`translations`](../components/querybuilder#translations) |\n| `fieldSeparator` | Handled at conversion time by `parseRAQBFields` (field names are flattened) |"
|
|
2279
2344
|
},
|
|
2280
2345
|
{
|
|
2281
|
-
"page":
|
|
2346
|
+
"page": 37,
|
|
2282
2347
|
"heading": "Converting the config",
|
|
2283
2348
|
"anchor": "converting-the-config",
|
|
2284
2349
|
"text": "## Converting the config\n\n`parseRAQBFields` accepts either a full RAQB `Config` object or just the `fields` map, and returns an RQB `fields` array.\n\n```ts\nimport { parseRAQBFields } from '@react-querybuilder/migrate-raqb';\n\nconst fields = parseRAQBFields({\n price: {\n label: 'Price',\n type: 'number',\n fieldSettings: { min: 0, max: 100 },\n },\n color: {\n label: 'Color',\n type: 'select',\n fieldSettings: {\n listValues: [\n { value: 'yellow', title: 'Yellow' },\n { value: 'green', title: 'Green' },\n ],\n },\n },\n});\n```\n\n```json title=\"Result\"\n[\n { \"name\": \"price\", \"label\": \"Price\", \"inputType\": \"number\" },\n {\n \"name\": \"color\",\n \"label\": \"Color\",\n \"valueEditorType\": \"select\",\n \"values\": [\n { \"name\": \"yellow\", \"label\": \"Yellow\" },\n { \"name\": \"green\", \"label\": \"Green\" }\n ]\n }\n]\n```\n\nTranslation details:\n\n- 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.\n- `!group` fields become fields with `matchModes` and `subproperties`, which is RQB's [subquery](./subqueries) representation.\n- `type` maps to `inputType` (`number`, `date`, `time`, `datetime-local`, etc.) or `valueEditorType` (`select`, `multiselect`, `checkbox`).\n- `fieldSettings.listValues` and `fieldSettings.treeValues` map to `values`. Tree values are flattened.\n- `operators`, `defaultOperator`, `excludeOperators`, `valueSources`, and `defaultValue` are translated to their RQB equivalents.\n- Fields marked `hideForSelect` are omitted unless you pass `includeHidden: true`.\n- Export-time settings (`fieldName`, `tableName`, `jsonLogicVar`, `isSpelVariable`) and `fieldSettings.validateValue` are not translated."
|
|
2285
2350
|
},
|
|
2286
2351
|
{
|
|
2287
|
-
"page":
|
|
2352
|
+
"page": 37,
|
|
2288
2353
|
"heading": "Converting the config › parseRAQBFields options",
|
|
2289
2354
|
"anchor": "parseraqbfields-options",
|
|
2290
2355
|
"text": "### `parseRAQBFields` options\n\n- `fieldSeparator` (`string`): Character used to join nested field names.\n- `operatorMap` (`Record<string, string>`): Additional or overriding operator name mappings. Should match the `operatorMap` passed to `parseRAQB`.\n- `includeHidden` (`boolean`): Include fields marked `hideForSelect`. Defaults to `false`.\n- `onUnsupported` (`(info: RAQBUnsupportedInfo) => void`): Called for each construct that could not be fully translated."
|
|
2291
2356
|
},
|
|
2292
2357
|
{
|
|
2293
|
-
"page":
|
|
2358
|
+
"page": 37,
|
|
2294
2359
|
"heading": "Converting queries",
|
|
2295
2360
|
"anchor": "converting-queries",
|
|
2296
2361
|
"text": "## Converting queries\n\n`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.\n\n```ts\n// In your RAQB app, save the plain JSON form:\nconst jsonTree = QbUtils.getTree(immutableTree);\n```\n\n```ts\nimport { parseRAQB } from '@react-querybuilder/migrate-raqb';\n\nconst query = parseRAQB(jsonTree);\n```\n\n```ts title=\"Example\"\nparseRAQB({\n type: 'group',\n properties: { conjunction: 'AND' },\n children1: [\n {\n type: 'rule',\n properties: { field: 'price', operator: 'greater', value: [10], valueSrc: ['value'] },\n },\n {\n type: 'rule',\n properties: { field: 'color', operator: 'select_any_in', value: [['yellow', 'green']] },\n },\n ],\n});\n```\n\n```json title=\"Result\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"price\", \"operator\": \">\", \"value\": 10 },\n { \"field\": \"color\", \"operator\": \"in\", \"value\": \"yellow,green\" }\n ]\n}\n```\n\nBoth `children1` shapes are supported: the default array form and the keyed-object form produced when `children1AsArray` is `false`."
|
|
2297
2362
|
},
|
|
2298
2363
|
{
|
|
2299
|
-
"page":
|
|
2364
|
+
"page": 37,
|
|
2300
2365
|
"heading": "Converting queries › Operator mapping",
|
|
2301
2366
|
"anchor": "operator-mapping",
|
|
2302
2367
|
"text": "### Operator mapping\n\n| RAQB | RQB |\n| -------------------------------------------------------------- | --------------------------------------------------------------- |\n| `equal` / `not_equal` | `=` / `!=` |\n| `less` / `less_or_equal` | `<` / `<=` |\n| `greater` / `greater_or_equal` | `>` / `>=` |\n| `like` / `not_like` | `contains` / `doesNotContain` |\n| `starts_with` / `ends_with` | `beginsWith` / `endsWith` |\n| `between` / `not_between` | `between` / `notBetween` |\n| `is_null` / `is_not_null` | `null` / `notNull` |\n| `is_empty` / `is_not_empty` | `=` / `!=` with an empty string |\n| `select_equals` / `select_not_equals` | `=` / `!=` |\n| `select_any_in` / `select_not_any_in` | `in` / `notIn` |\n| `multiselect_equals` / `multiselect_not_equals` | `=` / `!=` (array value kept) |\n| `multiselect_contains` / `multiselect_not_contains` | `contains` / `doesNotContain` |\n| `some` / `all` / `none` | Match modes `some` / `all` / `none` |\n| `!group` counts `equal` / `greater_or_equal` / `less_or_equal` | Match modes `exactly` / `atLeast` / `atMost` with a `threshold` |\n\nAdd to or override this map with the `operatorMap` option."
|
|
2303
2368
|
},
|
|
2304
2369
|
{
|
|
2305
|
-
"page":
|
|
2370
|
+
"page": 37,
|
|
2306
2371
|
"heading": "Converting queries › parseRAQB options",
|
|
2307
2372
|
"anchor": "parseraqb-options",
|
|
2308
2373
|
"text": "### `parseRAQB` options\n\nBeyond the standard [import configuration](../utils/import#configuration) options (`fields`, `listsAsArrays`, `generateIDs`, `independentCombinators`, etc.), `parseRAQB` accepts:\n\n- `operatorMap` (`Record<string, DefaultOperatorName>`): Additional or overriding RAQB-to-RQB operator mappings, merged over the defaults.\n- `functionMap` (`Record<string, string>`): Additional or overriding RAQB-to-`expr` function name mappings, merged over the defaults.\n- `funcArgOrder` (`Record<string, string[]>`): Explicit argument order per RAQB function name. By default, argument order follows the key order of the serialized function object.\n- `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).\n- `onUnsupported` (`(info: RAQBUnsupportedInfo) => void`): Called for each construct that could not be converted.\n\nAs with other parsers, passing `fields` restricts the output to rules whose fields exist in the list."
|
|
2309
2374
|
},
|
|
2310
2375
|
{
|
|
2311
|
-
"page":
|
|
2376
|
+
"page": 37,
|
|
2312
2377
|
"heading": "Functions and expressions",
|
|
2313
2378
|
"anchor": "functions-and-expressions",
|
|
2314
2379
|
"text": "## Functions and expressions\n\nRAQB's `valueSrc: \"func\"` operands convert to RQB [expressions](../expr). 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.\n\n```ts title=\"Example\"\nparseRAQB({\n type: 'group',\n properties: { conjunction: 'AND' },\n children1: [\n {\n type: 'rule',\n properties: {\n field: 'name',\n operator: 'equal',\n valueSrc: ['func'],\n value: [{ func: 'LOWER', args: { str: { value: 'Steve', valueSrc: 'value' } } }],\n },\n },\n ],\n});\n```\n\n```json title=\"Result\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"name\",\n \"operator\": \"=\",\n \"valueSource\": \"expression\",\n \"value\": { \"kind\": \"func\", \"fn\": \"lower\", \"args\": [{ \"kind\": \"value\", \"value\": \"Steve\" }] }\n }\n ]\n}\n```\n\n**Caution:**\n\nQueries containing expressions require [`@react-querybuilder/expr`](../expr) to render, validate, or serialize. Wrap your query builder in `QueryBuilderExpressions` and register the export processor as described in the [expressions documentation](../expr). Without that package, expression values will not display or export correctly.\n\nRAQB'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`."
|
|
2315
2380
|
},
|
|
2316
2381
|
{
|
|
2317
|
-
"page":
|
|
2382
|
+
"page": 37,
|
|
2318
2383
|
"heading": "Date and time functions",
|
|
2319
2384
|
"anchor": "date-and-time-functions",
|
|
2320
2385
|
"text": "## Date and time functions\n\nRAQB'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`](../datetime)'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'`).\n\n| RAQB | RQB value |\n| ----------------------------------------- | -------------------------------------------------- |\n| `NOW()` | `{ anchor: 'now', offset: 0 }` |\n| `TODAY()` / `START_OF_TODAY()` | `{ anchor: 'startOfDay', offset: 0 }` |\n| `TRUNCATE_DATETIME(NOW(), dim)` | `{ anchor: 'startOf<Dim>', offset: 0 }` |\n| `RELATIVE_DATE(TIME)(date, op, val, dim)` | `{ anchor: <from date>, offset: ±val, unit: dim }` |\n\n```ts title=\"Example\"\nparseRAQB({\n type: 'group',\n properties: { conjunction: 'AND' },\n children1: [\n {\n type: 'rule',\n properties: {\n field: 'created',\n operator: 'between',\n valueSrc: ['func', 'func'],\n value: [\n {\n func: 'RELATIVE_DATETIME',\n args: { op: { value: 'minus' }, val: { value: 7 }, dim: { value: 'day' } },\n },\n { func: 'NOW', args: {} },\n ],\n },\n },\n ],\n});\n```\n\n```json title=\"Result\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"created\",\n \"operator\": \"between\",\n \"value\": [\n { \"mode\": \"relative\", \"anchor\": \"now\", \"offset\": -7, \"unit\": \"day\" },\n { \"mode\": \"relative\", \"anchor\": \"now\", \"offset\": 0, \"unit\": \"day\" }\n ]\n }\n ]\n}\n```\n\nWith `@react-querybuilder/datetime`'s SQL rule processor, that query exports as `\"created\" between current_timestamp - interval '7 days' and current_timestamp`.\n\nA few RAQB calls fall outside what relative values can express, and are converted to expressions instead (and reported to `onUnsupported`):\n\n- A `\"second\"` dimension. RQB's smallest relative unit is `minute`.\n- Truncation to `hour`, `minute`, or `second`. RQB truncates only to day, week, month, or year.\n- 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.\n\nPass `relativeDateTimes: false` to convert date/time functions to expressions like any other function."
|
|
2321
2386
|
},
|
|
2322
2387
|
{
|
|
2323
|
-
"page":
|
|
2388
|
+
"page": 37,
|
|
2324
2389
|
"heading": "Unsupported constructs",
|
|
2325
2390
|
"anchor": "unsupported-constructs",
|
|
2326
2391
|
"text": "## Unsupported constructs\n\n`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.\n\n```ts\nconst unsupported: RAQBUnsupportedInfo[] = [];\n\nconst query = parseRAQB(jsonTree, {\n onUnsupported: info => unsupported.push(info),\n});\n```\n\nReported constructs include:\n\n- **`switch_group` / `case_group`** (RAQB's ternary mode). RQB has no equivalent; consider [`@react-querybuilder/rules-engine`](../rules-engine) for conditional logic.\n- **`proximity`** and its `operatorOptions`, which have no RQB counterpart.\n- **`!group` strict-inequality count operators** (`less`, `greater`), since RQB match modes cover only `exactly`, `atLeast`, and `atMost`.\n- **Functions with no `@react-querybuilder/expr` equivalent**, which pass through by name."
|
|
2327
2392
|
},
|
|
2328
2393
|
{
|
|
2329
|
-
"page":
|
|
2394
|
+
"page": 37,
|
|
2330
2395
|
"heading": "Putting it together",
|
|
2331
2396
|
"anchor": "putting-it-together",
|
|
2332
2397
|
"text": "## Putting it together\n\n```tsx\nimport { QueryBuilder } from 'react-querybuilder';\nimport { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';\nimport raqbConfig from './raqbConfig';\nimport savedTree from './savedQuery.json';\n\nconst fields = parseRAQBFields(raqbConfig);\nconst defaultQuery = parseRAQB(savedTree, { fields });\n\nexport const App = () => <QueryBuilder fields={fields} defaultQuery={defaultQuery} />;\n```"
|
|
2333
2398
|
},
|
|
2334
2399
|
{
|
|
2335
|
-
"page":
|
|
2400
|
+
"page": 37,
|
|
2336
2401
|
"heading": "Converting back to RAQB",
|
|
2337
2402
|
"anchor": "converting-back-to-raqb",
|
|
2338
2403
|
"text": "## Converting back to RAQB\n\nThe 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`.)\n\n```ts\nimport { formatRAQB } from '@react-querybuilder/migrate-raqb';\nimport { Utils } from '@react-awesome-query-builder/core';\n\nconst jsonTree = formatRAQB(query, { fields });\nconst immutableTree = Utils.checkTree(Utils.loadTree(jsonTree), raqbConfig);\n```\n\nPass `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.\n\n`formatRAQBFields`, the inverse of `parseRAQBFields`, is exported from the same package:\n\n```ts\nimport { formatRAQBFields } from '@react-querybuilder/migrate-raqb';\n\nconst raqbConfig = { ...BasicConfig, fields: formatRAQBFields(fields) };\n```\n\n`formatRAQB` accepts all [`formatQuery` options](../utils/export) except `format`, `ruleGroupProcessor`, and `fallbackExpression`, plus `fallbackTree` (the tree returned when the query is empty or fails validation) and:\n\n| Option | Default | Description |\n| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------ |\n| `raqbOperatorMap` | `{}` | Additional/overriding RQB-to-RAQB operator mappings, keyed by RQB operator name. |\n| `raqbFunctionMap` | `{}` | Additional/overriding expression-function-to-RAQB function name mappings. |\n| `raqbFuncArgOrder` | `{}` | Argument names per RAQB function name. Defaults cover RAQB's built-ins; others get `arg0`, `arg1`, ... |\n| `raqbFieldSeparator` | `\".\"` | Separator used to qualify sub-query rule fields with their parent `!group` field name. |\n| `raqbRelativeDateTimes` | `true` | Convert relative date/time values to RAQB's built-in date/time functions. |\n| `raqbValueTypes` | `false` | Emit `valueType` entries inferred from each field's `inputType`/`valueEditorType`. |\n\nThe mapping is the inverse of the tables above, with these caveats:\n\n- 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.\n- The `\"parameter\"` value source has no RAQB counterpart, so those rules are omitted.\n- `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.\n- 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.\n- RAQB clamps inverted ranges, so a `between` rule with `preserveValueOrder` and out-of-order bounds (e.g. `[100, 0]`) becomes `[100, 100]` once loaded.\n- `bigint` values are narrowed to `number`, since RAQB trees must be JSON-serializable.\n- `endOf*` relative date/time anchors are emitted as plain values, since RAQB's built-in functions only truncate to the _start_ of a period.\n- 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.\n- 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.\n- 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.\n\nTo combine RAQB output with other `formatQuery` behavior, or to supply a custom `ruleProcessor`, use `defaultRuleGroupProcessorRAQB` directly. RAQB options then move into `context`:\n\n```ts\nimport { formatQuery } from 'react-querybuilder';\nimport { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';\n\nconst jsonTree = formatQuery(query, {\n ruleGroupProcessor: defaultRuleGroupProcessorRAQB,\n fields,\n context: { raqbValueTypes: true },\n fallbackExpression: raqbFallback as unknown as string,\n});\n```\n\nRAQB'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."
|
|
2339
2404
|
},
|
|
2340
2405
|
{
|
|
2341
|
-
"page":
|
|
2406
|
+
"page": 38,
|
|
2342
2407
|
"heading": "",
|
|
2343
2408
|
"anchor": "",
|
|
2344
2409
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nOption list props in React Query Builder—such as [`fields`](../components/querybuilder#fields), [`combinators`](../components/querybuilder#combinators), and [`operators`](../components/querybuilder#operators)—use the [`OptionList`](../typescript#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.\n\nThis guide offers strategies for managing the inherent ambiguity in option list TypeScript types.\n\n**Info:**\n\n- `Option` objects have `label`, `name`, and `value` properties, all of which extend `string`.\n- `OptionGroup` objects have a `label` property along with an `options` property which is an array of `Option` objects.\n\nThis mirrors the `children` prop structure of `<select>` elements, which can contain either `<option>` elements directly or `<optgroup>` elements with nested `<option>` lists."
|
|
2345
2410
|
},
|
|
2346
2411
|
{
|
|
2347
|
-
"page":
|
|
2412
|
+
"page": 38,
|
|
2348
2413
|
"heading": "An incorrect assumption",
|
|
2349
2414
|
"anchor": "an-incorrect-assumption",
|
|
2350
2415
|
"text": "## An incorrect assumption\n\nYou may have found this page after seeing a TypeScript error message similar to this:\n\n```\nProperty 'name' does not exist on type 'FullOption<string> | OptionGroup<FullOption<string>>'.\n Property 'name' does not exist on type 'OptionGroup<FullOption<string>>'. ts(2339)\n```\n\nThis typically occurs when treating option list elements as guaranteed `Option` types. For example, mapping through the list to access the `name` property:\n\n```tsx\nconst ListAllOptionNames = (props: ValueSelectorProps) => {\n return <div>{props.options.map(opt => opt.name).join(', ')}</div>;\n // ^^^^ error\n};\n```\n\nWhile it seems logical that an option list passed to `QueryBuilder` as `Option[]` would remain `Option[]` in subcomponents, React Query Builder's type generics only infer the option types within the list, not the list structure itself.\n\n**Tip:**\n\nAll options in subcomponent props are guaranteed to include both `name` and `value` properties, even if the original `QueryBuilder` prop omitted one of them.\n\nAs an example, consider this `fields` array:\n\n```ts\nconst fields: Field[] = [\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n];\n```\n\nWhen this array is assigned to the [`fields` prop](../components/querybuilder#fields), the `fieldSelector` component will receive the same array but with each option object augmented with a `value` property equivalent to the original `name`:\n\n```tsx\nconst MyFieldSelector = (props: FieldSelectorProps) => {\n console.log(props.options); // =>\n // [\n // { name: 'firstName', value: 'firstName', label: 'First Name' },\n // { name: 'lastName', value: 'lastName', label: 'Last Name' }],\n // ]\n return <ValueSelector {...props} />;\n};\n\nconst App = () => (\n <QueryBuilder fields={fields} controlElements={{ fieldSelector: MyFieldSelector }} />\n);\n```\n\n**If `name` and `value` differ for a given option, `value` takes precedence.**"
|
|
2351
2416
|
},
|
|
2352
2417
|
{
|
|
2353
|
-
"page":
|
|
2418
|
+
"page": 38,
|
|
2354
2419
|
"heading": "Workarounds",
|
|
2355
2420
|
"anchor": "workarounds",
|
|
2356
2421
|
"text": "## Workarounds\n\nSeveral approaches can handle this ambiguity. One option is casting option list props to `Option[]` using the `as` keyword. While this may work if your option list is definitely `Option[]`, the `as` keyword only suppresses TypeScript errors without affecting runtime behavior. This essentially misleads the TypeScript compiler and prevents proper type checking, making it an unrecommended approach.\n\n```tsx\nconst MyComponent(props: ValueSelectorProps) => {\n return <div>{(props.options as Option[]).map(opt => opt.name).join(', ')}</div>;\n // ^^^^^^^^^^^ avoids TypeScript error; may have issues during execution\n};\n```\n\n**Info:**\n\nAll [default option lists](../utils/misc#defaults) (`defaultCombinators`, `defaultOperators`, etc.) are type `Option[]`.\n\nA better solution uses the [`isOptionGroupArray`](#isoptiongrouparray) type guard to determine the option list type. This approach avoids the deception of type casting while enabling different behaviors for `Option[]` versus `OptionGroup[]` arrays.\n\n```tsx\nconst MyComponent(props: ValueSelectorProps) => {\n if (isOptionGroupArray(props.options)) {\n return <div>{props.options.flatMap(og => og.options).map(opt => opt.name).join(', ')}</div>;\n }\n return <div>{(props.options).map(opt => opt.name).join(', ')}</div>;\n};\n```"
|
|
2357
2422
|
},
|
|
2358
2423
|
{
|
|
2359
|
-
"page":
|
|
2424
|
+
"page": 38,
|
|
2360
2425
|
"heading": "Utilities",
|
|
2361
2426
|
"anchor": "utilities",
|
|
2362
2427
|
"text": "## Utilities\n\nSeveral utility functions simplify working with option list props without requiring type guards or `as` casting."
|
|
2363
2428
|
},
|
|
2364
2429
|
{
|
|
2365
|
-
"page":
|
|
2430
|
+
"page": 38,
|
|
2366
2431
|
"heading": "Utilities › getOption",
|
|
2367
2432
|
"anchor": "getoption",
|
|
2368
2433
|
"text": "### `getOption`\n\n```ts\nfunction getOption(arr: OptionList, identifier: string): Option;\n```\n\nRetrieves the complete option object from an option list using the given identifier (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats.\n\n#### Examples\n\n```ts\ngetOption(\n [\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n ],\n 'lastName'\n);\n// => { name: 'lastName', label: 'Last Name' }\n\ngetOption(\n [\n { label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },\n { label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },\n ],\n 'lastName'\n);\n// => { name: 'lastName', label: 'Last Name' }\n```"
|
|
2369
2434
|
},
|
|
2370
2435
|
{
|
|
2371
|
-
"page":
|
|
2436
|
+
"page": 38,
|
|
2372
2437
|
"heading": "Utilities › getFirstOption",
|
|
2373
2438
|
"anchor": "getfirstoption",
|
|
2374
2439
|
"text": "### `getFirstOption`\n\n```ts\nfunction getFirstOption(arr: OptionList): Option;\n```\n\nReturns the identifier value (`name` or `value`) of the first `Option` in the list, supporting both `Option[]` and `OptionGroup[]` formats.\n\n`QueryBuilder` uses this function to establish default values for option lists in new rules and groups when no other default determination method is available.\n\n#### Examples\n\n```ts\ngetFirstOption([\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n]);\n// => 'firstName'\n\ngetFirstOption([\n { label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },\n { label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },\n]);\n// => 'firstName'\n```"
|
|
2375
2440
|
},
|
|
2376
2441
|
{
|
|
2377
|
-
"page":
|
|
2442
|
+
"page": 38,
|
|
2378
2443
|
"heading": "Utilities › toOptions",
|
|
2379
2444
|
"anchor": "tooptions",
|
|
2380
2445
|
"text": "### `toOptions`\n\n```ts\nfunction toOptions(arr: OptionList): ReactElement;\n```\n\nCreates `<option>` elements for `Option` arrays or `<optgroup>` elements for `OptionGroup` arrays. Designed for use as the `children` prop of `<select>` elements, as implemented in [`ValueSelector`](../components/valueselector).\n\n**Info:**\n\nSome of the [compatibility packages](../compat) implement their own `toOptions` method that generates \"option\" elements appropriate for their respective style library.\n\n#### Usage\n\n```tsx\nconst MyComponent(props: ValueSelectorProps) => {\n return (\n <select value={props.value} onChange={e => props.handleOnChange(e.target.value)}>\n {toOptions(props.options)}\n </select>\n )\n}\n```\n\n#### `Option[]` example\n\n```tsx\ntoOptions([\n { value: 'firstName', label: 'First Name' },\n { value: 'lastName', label: 'Last Name' },\n]);\n// yields (approximately):\n[\n <option key={'firstName'} value={'firstName'}>\n First Name\n </option>,\n <option key={'lastName'} value={'lastName'}>\n Last Name\n </option>,\n];\n```\n\n#### `OptionGroup[]` example\n\n```tsx\ntoOptions([\n { label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },\n { label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },\n]);\n// yields (approximately):\n[\n <optgroup key={'First'} label={'First'}>\n <option key={'firstName'} value={'firstName'}>\n First Name\n </option>\n </optgroup>,\n <optgroup key={'Last'} label={'Last'}>\n <option key={'lastName'} value={'lastName'}>\n Last Name\n </option>\n </optgroup>,\n];\n```"
|
|
2381
2446
|
},
|
|
2382
2447
|
{
|
|
2383
|
-
"page":
|
|
2448
|
+
"page": 38,
|
|
2384
2449
|
"heading": "Utilities › toFlatOptionArray",
|
|
2385
2450
|
"anchor": "toflatoptionarray",
|
|
2386
2451
|
"text": "### `toFlatOptionArray`\n\n```ts\nfunction toFlatOptionArray(arr: any): boolean;\n```\n\nConverts `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)."
|
|
2387
2452
|
},
|
|
2388
2453
|
{
|
|
2389
|
-
"page":
|
|
2454
|
+
"page": 38,
|
|
2390
2455
|
"heading": "Utilities › isOptionGroupArray",
|
|
2391
2456
|
"anchor": "isoptiongrouparray",
|
|
2392
2457
|
"text": "### `isOptionGroupArray`\n\n```ts\nfunction isOptionGroupArray(arr: any): boolean;\n```\n\nA 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.\n\n```ts\nisOptionGroupArray([\n { value: 'firstName', label: 'First Name' },\n { value: 'lastName', label: 'Last Name' },\n]);\n// => false\n\nisOptionGroupArray([\n { label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },\n { label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },\n]);\n// => true\n```"
|
|
2393
2458
|
},
|
|
2394
2459
|
{
|
|
2395
|
-
"page":
|
|
2460
|
+
"page": 38,
|
|
2396
2461
|
"heading": "Utilities › uniqOptList",
|
|
2397
2462
|
"anchor": "uniqoptlist",
|
|
2398
2463
|
"text": "### `uniqOptList`\n\n```ts\nfunction uniqOptList(arr: any): boolean;\n```\n\nRemoves duplicate options from an `OptionList` by comparing identifier properties (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats."
|
|
2399
2464
|
},
|
|
2400
2465
|
{
|
|
2401
|
-
"page":
|
|
2466
|
+
"page": 38,
|
|
2402
2467
|
"heading": "Utilities › uniqByIdentifier",
|
|
2403
2468
|
"anchor": "uniqbyidentifier",
|
|
2404
2469
|
"text": "### `uniqByIdentifier`\n\n```ts\nfunction uniqByIdentifier(arr: any): boolean;\n```\n\nRemoves duplicate options from an `Option` array by comparing identifier properties (`name` or `value`)."
|
|
2405
2470
|
},
|
|
2406
2471
|
{
|
|
2407
|
-
"page":
|
|
2472
|
+
"page": 38,
|
|
2408
2473
|
"heading": "String option lists",
|
|
2409
2474
|
"anchor": "string-option-lists",
|
|
2410
2475
|
"text": "## String option lists\n\nOption list props also accept `string[]`. Each string becomes an option with `name`, `value`, and `label` all set to that string.\n\n```tsx\n// These are equivalent:\n<QueryBuilder combinators={['and', 'or']} />\n<QueryBuilder combinators={[{ name: 'and', label: 'and' }, { name: 'or', label: 'or' }]} />\n```\n\nStrings can be mixed with option objects:\n\n```tsx\n<QueryBuilder operators={['=', '!=', { name: 'contains', label: 'Contains' }]} />\n```\n\nThis shorthand is convenient for simple option lists but lacks the flexibility of full option objects, such as custom labels."
|
|
2411
2476
|
},
|
|
2412
2477
|
{
|
|
2413
|
-
"page":
|
|
2478
|
+
"page": 39,
|
|
2414
2479
|
"heading": "",
|
|
2415
2480
|
"anchor": "",
|
|
2416
2481
|
"text": "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`](../components/querybuilder#getparameters) prop, so rules whose [`valueSource`](../components/valueeditor#the-parameter-value-source) is `\"parameter\"` present the managed names in their value editors.\n\nThe rendered SQL comes from the [`\"parameterized_named\"`](../utils/export#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.\n\n**Tip:**\n\nParameter 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.\n\n```tsx\nimport { useState } from 'react';\nimport type { Field, RuleGroupType } from 'react-querybuilder';\nimport { formatQuery, QueryBuilder } from 'react-querybuilder';\n\ninterface Parameter {\n id: string;\n name: string;\n label?: string;\n value?: string;\n}\n\nconst genId = () => crypto.randomUUID();\n\nconst fields: Field[] = [\n { name: 'firstName', label: 'First Name', valueSources: ['value', 'parameter'] },\n { name: 'lastName', label: 'Last Name', valueSources: ['value', 'parameter'] },\n];\n\nconst initialQuery: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: '=', value: 'p1', valueSource: 'parameter' },\n { field: 'lastName', operator: 'beginsWith', value: 'V' },\n ],\n};\n\nconst initialParameters: Parameter[] = [\n { id: genId(), name: 'p1', label: 'Param 1', value: 'Steve' },\n { id: genId(), name: 'p2', label: 'Param 2' },\n];\n\n// Strip a leading param prefix (\":\", \"@\", \"$\", etc.) from a params key.\nconst stripPrefix = (key: string) => key.replace(/^[^A-Za-z0-9_]+/, '');\n\n// The external \"parameter manager\" component. It owns the list of\n// parameters and reports changes up to the parent via `onChange`.\nconst ParameterManager = ({\n parameters,\n onChange,\n}: {\n parameters: Parameter[];\n onChange: (parameters: Parameter[]) => void;\n}) => {\n const update = (index: number, key: keyof Parameter, value: string) => {\n const next = parameters.map((p, i) => (i === index ? { ...p, [key]: value } : p));\n onChange(next);\n };\n const remove = (index: number) => onChange(parameters.filter((_, i) => i !== index));\n const add = () => onChange([...parameters, { id: genId(), name: `p${parameters.length + 1}` }]);\n\n return (\n <div style={{ marginBottom: '1rem' }}>\n <h4>Parameter manager</h4>\n <table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Label</th>\n <th>Value</th>\n <th />\n </tr>\n </thead>\n <tbody>\n {parameters.map((p, i) => (\n <tr key={p.id}>\n <td>\n <input\n value={p.name}\n placeholder=\"p1\"\n onChange={e => update(i, 'name', e.target.value)}\n />\n </td>\n <td>\n <input\n value={p.label ?? ''}\n placeholder=\"Param 1\"\n onChange={e => update(i, 'label', e.target.value)}\n />\n </td>\n <td>\n <input\n value={p.value ?? ''}\n placeholder=\"(no value)\"\n onChange={e => update(i, 'value', e.target.value)}\n />\n </td>\n <td>\n <button type=\"button\" onClick={() => remove(i)}>\n ×\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n <button type=\"button\" onClick={add}>\n Add parameter\n </button>\n </div>\n );\n};\n\nexport default () => {\n const [query, setQuery] = useState(initialQuery);\n const [parameters, setParameters] = useState(initialParameters);\n\n // Only expose named parameters (label falls back to the name).\n const getParameters = () =>\n parameters.filter(p => p.name).map(p => ({ name: p.name, label: p.label || p.name }));\n\n // Look up a supplied value by (prefix-less) parameter name.\n const valueByName = new Map(parameters.map(p => [p.name, p.value]));\n\n const { sql, params } = formatQuery(query, 'parameterized_named');\n\n // Merge the manager's values over the `null` placeholders.\n const mergedParams = Object.fromEntries(\n Object.keys(params).map(key => {\n const value = valueByName.get(stripPrefix(key));\n return [key, value ? value : null];\n })\n );\n\n return (\n <div>\n <ParameterManager parameters={parameters} onChange={setParameters} />\n <QueryBuilder\n fields={fields}\n query={query}\n onQueryChange={setQuery}\n getParameters={getParameters}\n />\n <h4>SQL</h4>\n <pre>{sql}</pre>\n <h4>Parameters</h4>\n <pre>{JSON.stringify(mergedParams, null, 2)}</pre>\n </div>\n );\n};\n```"
|
|
2417
2482
|
},
|
|
2418
2483
|
{
|
|
2419
|
-
"page":
|
|
2484
|
+
"page": 40,
|
|
2420
2485
|
"heading": "",
|
|
2421
2486
|
"anchor": "",
|
|
2422
2487
|
"text": "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.\n\nThe `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.\n\nEach object's `path` equals its parent group's `path` plus its index within the parent's `rules` array: `path = [...parentPath, index]`.\n\nThis example query shows the `path` for each rule and group with explanatory comments:\n\n```ts\n// [] (the root group)\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n // [0] (the first, aka zeroth, element in the root rules array)\n { field: 'f1', operator: '=', value: 'v1' },\n // [1] (the second element in the root rules array is a sub-group)\n {\n combinator: 'or',\n rules: [\n // [1, 0] (the first element within the rules array\n // of the group occupying the second position\n // in the root rules array)\n { field: 'f2', operator: '=', value: 'v2' },\n // [1, 1] (the second element within the rules array\n // of the group occupying the second position\n // in the root rules array)\n { field: 'f3', operator: '=', value: 'v3' },\n ],\n },\n ],\n};\n```\n\nThe 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."
|
|
2423
2488
|
},
|
|
2424
2489
|
{
|
|
2425
|
-
"page":
|
|
2490
|
+
"page": 40,
|
|
2426
2491
|
"heading": "Finding a path",
|
|
2427
2492
|
"anchor": "finding-a-path",
|
|
2428
2493
|
"text": "## Finding a `path`\n\nThe [`findPath`](../utils/misc#findpath) function locates specific rules or groups for examination or updates. Using the query above:\n\n```ts\nfindPath([1, 0], query);\n```\n\nReturns:\n\n```json\n{ \"field\": \"f2\", \"operator\": \"=\", \"value\": \"v2\" }\n```"
|
|
2429
2494
|
},
|
|
2430
2495
|
{
|
|
2431
|
-
"page":
|
|
2496
|
+
"page": 40,
|
|
2432
2497
|
"heading": "Example",
|
|
2433
2498
|
"anchor": "example",
|
|
2434
2499
|
"text": "## Example\n\nWhile most scenarios don't require direct `path` interaction, it's useful when custom components need to access other query parts.\n\nFor example, if a custom value editor needs sibling rule values, you can retrieve the full query object using the [`useQueryBuilderQuery`](../utils/hooks#usequerybuilderquery) hook (which connects to React Query Builder's Redux store), then find sibling rules using `getParentPath` and `findPath`.\n\n**Info:**\n\nBefore version 7, custom components only received props for their specific rule or group. Additional data (like the root query) required the [`context` prop](../components/querybuilder#context).\n\nThe `context` prop remains available, but query retrieval no longer requires it.\n\n```tsx CustomValueEditor.tsx\nimport {\n RuleGroupType,\n RuleType,\n ValueEditor,\n ValueEditorProps,\n findPath,\n getParentPath,\n useQueryBuilderQuery,\n} from 'react-querybuilder';\n\nexport const CustomValueEditor = (props: ValueEditorProps) => {\n // Get the full query object\n const query = useQueryBuilderQuery();\n // Get the path of this rule's parent group\n const parentPath = getParentPath(props.path);\n // Find the parent group object in the query\n const parentGroup = findPath(parentPath, query) as RuleGroupType;\n const id = findPath(props.path, query)!.id;\n // Get a comma-separated list of all sibling rule values\n const siblingValues = (\n parentGroup.rules.filter(\n r =>\n // filter out groups\n !('rules' in r) &&\n // filter out self\n r.id !== id\n ) as RuleType[]\n )\n // map the `value` property\n .map(r => r.value)\n // join with comma\n .join(', ');\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column' }}>\n <ValueEditor {...props} />\n <span>Others: {siblingValues}</span>\n </div>\n );\n};\n```\n\n```tsx\nimport { useState } from 'react';\nimport { QueryBuilder } from 'react-querybuilder';\nimport { CustomValueEditor } from './CustomValueEditor';\nimport { defaultQuery, fields, operators } from './constants';\n\nexport default function App() {\n const [query, setQuery] = useState(defaultQuery);\n\n return (\n <QueryBuilder\n fields={fields}\n operators={operators}\n query={query}\n onQueryChange={setQuery}\n controlElements={{ valueEditor: CustomValueEditor }}\n />\n );\n}\n```\n\n```ts constants.ts\nimport { defaultOperators, Field, RuleGroupType } from 'react-querybuilder';\n\nexport const operators = defaultOperators.filter(op => op.name === '=');\n\nexport const fields: Field[] = [\n { name: 'field1', label: 'Field 1' },\n { name: 'field2', label: 'Field 2' },\n { name: 'field3', label: 'Field 3' },\n { name: 'field4', label: 'Field 4' },\n];\n\nexport const defaultQuery: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'field1', operator: '=', value: '1' },\n { field: 'field2', operator: '=', value: '2' },\n { field: 'field3', operator: '=', value: '3' },\n { field: 'field4', operator: '=', value: '4' },\n ],\n};\n```\n\n```css\n.queryBuilder .rule {\n align-items: flex-start;\n}\n\nspan {\n color: gray;\n}\n```"
|
|
2435
2500
|
},
|
|
2436
2501
|
{
|
|
2437
|
-
"page":
|
|
2502
|
+
"page": 41,
|
|
2438
2503
|
"heading": "",
|
|
2439
2504
|
"anchor": "",
|
|
2440
2505
|
"text": "Every snippet on this page was run against the current source and the output shown is exact. All snippets work on the server with `@react-querybuilder/core` (`react-querybuilder` re-exports the same functions). See [Export](../utils/export) and [Import](../utils/import) for full option references."
|
|
2441
2506
|
},
|
|
2442
2507
|
{
|
|
2443
|
-
"page":
|
|
2508
|
+
"page": 41,
|
|
2444
2509
|
"heading": "Shared setup",
|
|
2445
2510
|
"anchor": "shared-setup",
|
|
2446
2511
|
"text": "## Shared setup\n\nThe export recipes use this `query` and `fields`:\n\n```ts\nimport type { Field, RuleGroupType } from '@react-querybuilder/core';\n\nconst fields: Field[] = [\n { name: 'firstName', label: 'First name' },\n { name: 'lastName', label: 'Last name' },\n { name: 'age', label: 'Age', inputType: 'number' },\n { name: 'isMusician', label: 'Is a musician', valueEditorType: 'checkbox' },\n];\n\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: 'beginsWith', value: 'Stev' },\n { field: 'age', operator: 'between', value: [18, 65] },\n {\n combinator: 'or',\n rules: [\n { field: 'lastName', operator: 'in', value: ['Vai', 'Vaughan'] },\n { field: 'isMusician', operator: '=', value: true },\n ],\n },\n ],\n};\n```"
|
|
2447
2512
|
},
|
|
2448
2513
|
{
|
|
2449
|
-
"page":
|
|
2514
|
+
"page": 41,
|
|
2450
2515
|
"heading": "Export (formatQuery)",
|
|
2451
2516
|
"anchor": "export-formatquery",
|
|
2452
2517
|
"text": "## Export (`formatQuery`)"
|
|
2453
2518
|
},
|
|
2454
2519
|
{
|
|
2455
|
-
"page":
|
|
2520
|
+
"page": 41,
|
|
2456
2521
|
"heading": "Export (formatQuery) › sql",
|
|
2457
2522
|
"anchor": "sql",
|
|
2458
2523
|
"text": "### `sql`\n\nInline values. **Not for untrusted input**; prefer `parameterized`. Add `parseNumbers: true` to emit unquoted numbers.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'sql');\n```\n\n```sql title=\"Output\"\n(firstName like 'Stev%' and age between '18' and '65' and (lastName in ('Vai', 'Vaughan') or isMusician = TRUE))\n```"
|
|
2459
2524
|
},
|
|
2460
2525
|
{
|
|
2461
|
-
"page":
|
|
2526
|
+
"page": 41,
|
|
2462
2527
|
"heading": "Export (formatQuery) › parameterized",
|
|
2463
2528
|
"anchor": "parameterized",
|
|
2464
2529
|
"text": "### `parameterized`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'parameterized');\n```\n\n```json title=\"Output\"\n{\n \"sql\": \"(firstName like ? and age between ? and ? and (lastName in (?, ?) or isMusician = ?))\",\n \"params\": [\"Stev%\", 18, 65, \"Vai\", \"Vaughan\", true]\n}\n```"
|
|
2465
2530
|
},
|
|
2466
2531
|
{
|
|
2467
|
-
"page":
|
|
2532
|
+
"page": 41,
|
|
2468
2533
|
"heading": "Export (formatQuery) › parameterizednamed",
|
|
2469
2534
|
"anchor": "parameterizednamed",
|
|
2470
2535
|
"text": "### `parameterized_named`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'parameterized_named');\n```\n\n```json title=\"Output\"\n{\n \"sql\": \"(firstName like :firstName_1 and age between :age_1 and :age_2 and (lastName in (:lastName_1, :lastName_2) or isMusician = :isMusician_1))\",\n \"params\": {\n \"firstName_1\": \"Stev%\",\n \"age_1\": 18,\n \"age_2\": 65,\n \"lastName_1\": \"Vai\",\n \"lastName_2\": \"Vaughan\",\n \"isMusician_1\": true\n }\n}\n```"
|
|
2471
2536
|
},
|
|
2472
2537
|
{
|
|
2473
|
-
"page":
|
|
2538
|
+
"page": 41,
|
|
2474
2539
|
"heading": "Export (formatQuery) › mongodbquery",
|
|
2475
2540
|
"anchor": "mongodbquery",
|
|
2476
2541
|
"text": "### `mongodb_query`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'mongodb_query');\n```\n\n```json title=\"Output\"\n{\n \"$and\": [\n { \"firstName\": { \"$regex\": \"^Stev\" } },\n { \"age\": { \"$gte\": 18, \"$lte\": 65 } },\n { \"$or\": [{ \"lastName\": { \"$in\": [\"Vai\", \"Vaughan\"] } }, { \"isMusician\": true }] }\n ]\n}\n```"
|
|
2477
2542
|
},
|
|
2478
2543
|
{
|
|
2479
|
-
"page":
|
|
2544
|
+
"page": 41,
|
|
2480
2545
|
"heading": "Export (formatQuery) › mongodb",
|
|
2481
2546
|
"anchor": "mongodb",
|
|
2482
2547
|
"text": "### `mongodb`\n\n**Deprecated**; use `mongodb_query`.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'mongodb');\n```\n\n```json title=\"Output\"\n{\n \"$and\": [\n { \"firstName\": { \"$regex\": \"^Stev\" } },\n { \"age\": { \"$gte\": 18, \"$lte\": 65 } },\n { \"$or\": [{ \"lastName\": { \"$in\": [\"Vai\", \"Vaughan\"] } }, { \"isMusician\": true }] }\n ]\n}\n```"
|
|
2483
2548
|
},
|
|
2484
2549
|
{
|
|
2485
|
-
"page":
|
|
2550
|
+
"page": 41,
|
|
2486
2551
|
"heading": "Export (formatQuery) › elasticsearch",
|
|
2487
2552
|
"anchor": "elasticsearch",
|
|
2488
2553
|
"text": "### `elasticsearch`\n\nExperimental.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'elasticsearch');\n```\n\n```json title=\"Output\"\n{\n \"bool\": {\n \"must\": [\n { \"regexp\": { \"firstName\": { \"value\": \"Stev.*\" } } },\n { \"range\": { \"age\": { \"gte\": 18, \"lte\": 65 } } },\n {\n \"bool\": {\n \"should\": [\n {\n \"bool\": {\n \"should\": [{ \"term\": { \"lastName\": \"Vai\" } }, { \"term\": { \"lastName\": \"Vaughan\" } }]\n }\n },\n { \"term\": { \"isMusician\": true } }\n ]\n }\n }\n ]\n }\n}\n```"
|
|
2489
2554
|
},
|
|
2490
2555
|
{
|
|
2491
|
-
"page":
|
|
2556
|
+
"page": 41,
|
|
2557
|
+
"heading": "Export (formatQuery) › qdrant",
|
|
2558
|
+
"anchor": "qdrant",
|
|
2559
|
+
"text": "### `qdrant`\n\nExperimental. Qdrant `Filter` for vector search metadata filtering. String ops other than `contains` (full-text `match.text`) are skipped.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'qdrant');\n```\n\n```json title=\"Output\"\n{\n \"must\": [\n { \"key\": \"firstName\", \"match\": { \"prefix\": \"Stev\" } },\n { \"key\": \"age\", \"range\": { \"gte\": 18, \"lte\": 65 } },\n {\n \"should\": [\n { \"key\": \"lastName\", \"match\": { \"any\": [\"Vai\", \"Vaughan\"] } },\n { \"key\": \"isMusician\", \"match\": { \"value\": true } }\n ]\n }\n ]\n}\n```"
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
"page": 41,
|
|
2563
|
+
"heading": "Export (formatQuery) › pinecone",
|
|
2564
|
+
"anchor": "pinecone",
|
|
2565
|
+
"text": "### `pinecone`\n\nExperimental. Pinecone metadata filter. String ops skipped; `not` groups via De Morgan.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'pinecone');\n```\n\n```json title=\"Output\"\n{\n \"$and\": [\n { \"age\": { \"$gte\": 18, \"$lte\": 65 } },\n { \"$or\": [{ \"lastName\": { \"$in\": [\"Vai\", \"Vaughan\"] } }, { \"isMusician\": { \"$eq\": true } }] }\n ]\n}\n```"
|
|
2566
|
+
},
|
|
2567
|
+
{
|
|
2568
|
+
"page": 41,
|
|
2569
|
+
"heading": "Export (formatQuery) › chroma",
|
|
2570
|
+
"anchor": "chroma",
|
|
2571
|
+
"text": "### `chroma`\n\nExperimental. Chroma `where` filter. String ops and `null`/`notNull` skipped; `not` groups via De Morgan.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'chroma');\n```\n\n```json title=\"Output\"\n{\n \"$and\": [\n { \"$and\": [{ \"age\": { \"$gte\": 18 } }, { \"age\": { \"$lte\": 65 } }] },\n { \"$or\": [{ \"lastName\": { \"$in\": [\"Vai\", \"Vaughan\"] } }, { \"isMusician\": { \"$eq\": true } }] }\n ]\n}\n```"
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
"page": 41,
|
|
2575
|
+
"heading": "Export (formatQuery) › weaviate",
|
|
2576
|
+
"anchor": "weaviate",
|
|
2577
|
+
"text": "### `weaviate`\n\nExperimental. Weaviate `where` filter (GraphQL/REST shape). String ops use `Like`.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'weaviate');\n```\n\n```json title=\"Output\"\n{\n \"operator\": \"And\",\n \"operands\": [\n { \"operator\": \"Like\", \"path\": [\"firstName\"], \"valueText\": \"Stev*\" },\n {\n \"operator\": \"And\",\n \"operands\": [\n { \"operator\": \"GreaterThanEqual\", \"path\": [\"age\"], \"valueInt\": 18 },\n { \"operator\": \"LessThanEqual\", \"path\": [\"age\"], \"valueInt\": 65 }\n ]\n },\n {\n \"operator\": \"Or\",\n \"operands\": [\n { \"operator\": \"ContainsAny\", \"path\": [\"lastName\"], \"valueText\": [\"Vai\", \"Vaughan\"] },\n { \"operator\": \"Equal\", \"path\": [\"isMusician\"], \"valueBoolean\": true }\n ]\n }\n ]\n}\n```"
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
"page": 41,
|
|
2492
2581
|
"heading": "Export (formatQuery) › jsonlogic",
|
|
2493
2582
|
"anchor": "jsonlogic",
|
|
2494
2583
|
"text": "### `jsonlogic`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'jsonlogic');\n```\n\n```json title=\"Output\"\n{\n \"and\": [\n { \"startsWith\": [{ \"var\": \"firstName\" }, \"Stev\"] },\n { \"<=\": [18, { \"var\": \"age\" }, 65] },\n {\n \"or\": [\n { \"in\": [{ \"var\": \"lastName\" }, [\"Vai\", \"Vaughan\"]] },\n { \"==\": [{ \"var\": \"isMusician\" }, true] }\n ]\n }\n ]\n}\n```"
|
|
2495
2584
|
},
|
|
2496
2585
|
{
|
|
2497
|
-
"page":
|
|
2586
|
+
"page": 41,
|
|
2587
|
+
"heading": "Export (formatQuery) › milvus",
|
|
2588
|
+
"anchor": "milvus",
|
|
2589
|
+
"text": "### `milvus`\n\nExperimental. Milvus boolean filter expression. Field names are not escaped.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'milvus');\n```\n\n```text title=\"Output\"\nfirstName like \"Stev%\" and (age >= 18 and age <= 65) and (lastName in [\"Vai\", \"Vaughan\"] or isMusician == true)\n```"
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
"page": 41,
|
|
2498
2593
|
"heading": "Export (formatQuery) › cel",
|
|
2499
2594
|
"anchor": "cel",
|
|
2500
2595
|
"text": "### `cel`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'cel');\n```\n\n```text title=\"Output\"\nfirstName.startsWith(\"Stev\") && (age >= 18 && age <= 65) && (lastName in [\"Vai\", \"Vaughan\"] || isMusician == true)\n```"
|
|
2501
2596
|
},
|
|
2502
2597
|
{
|
|
2503
|
-
"page":
|
|
2598
|
+
"page": 41,
|
|
2504
2599
|
"heading": "Export (formatQuery) › spel",
|
|
2505
2600
|
"anchor": "spel",
|
|
2506
2601
|
"text": "### `spel`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'spel');\n```\n\n```text title=\"Output\"\nfirstName matches '^Stev.*' and (age >= 18 and age <= 65) and ((lastName == 'Vai' or lastName == 'Vaughan') or isMusician == true)\n```"
|
|
2507
2602
|
},
|
|
2508
2603
|
{
|
|
2509
|
-
"page":
|
|
2604
|
+
"page": 41,
|
|
2510
2605
|
"heading": "Export (formatQuery) › jsonata",
|
|
2511
2606
|
"anchor": "jsonata",
|
|
2512
2607
|
"text": "### `jsonata`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, { format: 'jsonata', parseNumbers: true });\n```\n\n```text title=\"Output\"\n$contains(firstName, /^Stev/) and (age >= 18 and age <= 65) and (lastName in [\"Vai\", \"Vaughan\"] or isMusician = true)\n```"
|
|
2513
2608
|
},
|
|
2514
2609
|
{
|
|
2515
|
-
"page":
|
|
2610
|
+
"page": 41,
|
|
2516
2611
|
"heading": "Export (formatQuery) › ldap",
|
|
2517
2612
|
"anchor": "ldap",
|
|
2518
2613
|
"text": "### `ldap`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'ldap');\n```\n\n```text title=\"Output\"\n(&(firstName=Stev*)(&(age>=18)(age<=65))(|(|(lastName=Vai)(lastName=Vaughan))(isMusician=true)))\n```"
|
|
2519
2614
|
},
|
|
2520
2615
|
{
|
|
2521
|
-
"page":
|
|
2616
|
+
"page": 41,
|
|
2522
2617
|
"heading": "Export (formatQuery) › cypher",
|
|
2523
2618
|
"anchor": "cypher",
|
|
2524
2619
|
"text": "### `cypher`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'cypher');\n```\n\n```cypher title=\"Output\"\nfirstName STARTS WITH 'Stev' AND 18 <= age AND age <= 65 AND (lastName IN ['Vai', 'Vaughan'] OR isMusician = true)\n```"
|
|
2525
2620
|
},
|
|
2526
2621
|
{
|
|
2527
|
-
"page":
|
|
2622
|
+
"page": 41,
|
|
2528
2623
|
"heading": "Export (formatQuery) › gql",
|
|
2529
2624
|
"anchor": "gql",
|
|
2530
2625
|
"text": "### `gql`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'gql');\n```\n\n```cypher title=\"Output\"\nfirstName STARTS WITH 'Stev' AND 18 <= age AND age <= 65 AND (lastName IN ['Vai', 'Vaughan'] OR isMusician = true)\n```"
|
|
2531
2626
|
},
|
|
2532
2627
|
{
|
|
2533
|
-
"page":
|
|
2628
|
+
"page": 41,
|
|
2534
2629
|
"heading": "Export (formatQuery) › sparql",
|
|
2535
2630
|
"anchor": "sparql",
|
|
2536
2631
|
"text": "### `sparql`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'sparql');\n```\n\n```sparql title=\"Output\"\nSTRSTARTS(?firstName, \"Stev\") && ?age >= 18 && ?age <= 65 && (?lastName IN (\"Vai\", \"Vaughan\") || ?isMusician = \"true\"^^xsd:boolean)\n```"
|
|
2537
2632
|
},
|
|
2538
2633
|
{
|
|
2539
|
-
"page":
|
|
2634
|
+
"page": 41,
|
|
2540
2635
|
"heading": "Export (formatQuery) › gremlin",
|
|
2541
2636
|
"anchor": "gremlin",
|
|
2542
2637
|
"text": "### `gremlin`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'gremlin');\n```\n\n```groovy title=\"Output\"\n.has('firstName', startingWith('Stev')).has('age', between(18, 65)).or(__.has('lastName', within('Vai', 'Vaughan')), __.has('isMusician', true))\n```"
|
|
2543
2638
|
},
|
|
2544
2639
|
{
|
|
2545
|
-
"page":
|
|
2640
|
+
"page": 41,
|
|
2546
2641
|
"heading": "Export (formatQuery) › naturallanguage",
|
|
2547
2642
|
"anchor": "naturallanguage",
|
|
2548
2643
|
"text": "### `natural_language`\n\n**Display only.** Never parse it back.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, { format: 'natural_language', fields });\n```\n\n```text title=\"Output\"\nFirst name starts with 'Stev', and Age is between '18' and '65', and (Last name is one of the values ('Vai' or 'Vaughan'), or Is a musician is true) is true\n```"
|
|
2549
2644
|
},
|
|
2550
2645
|
{
|
|
2551
|
-
"page":
|
|
2646
|
+
"page": 41,
|
|
2552
2647
|
"heading": "Export (formatQuery) › prisma",
|
|
2553
2648
|
"anchor": "prisma",
|
|
2554
2649
|
"text": "### `prisma`\n\nPass as `where`. No `valueSource: 'field'` support.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'prisma');\n```\n\n```json title=\"Output\"\n{\n \"AND\": [\n { \"firstName\": { \"startsWith\": \"Stev\" } },\n { \"age\": { \"gte\": 18, \"lte\": 65 } },\n { \"OR\": [{ \"lastName\": { \"in\": [\"Vai\", \"Vaughan\"] } }, { \"isMusician\": true }] }\n ]\n}\n```"
|
|
2555
2650
|
},
|
|
2556
2651
|
{
|
|
2557
|
-
"page":
|
|
2652
|
+
"page": 41,
|
|
2558
2653
|
"heading": "Export (formatQuery) › drizzle",
|
|
2559
2654
|
"anchor": "drizzle",
|
|
2560
2655
|
"text": "### `drizzle`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'drizzle');\n```\n\nReturns `(columns, operators) => SQL | undefined`. Pass as a relational query `where`, or call with `(table, getOperators())` from `drizzle-orm`."
|
|
2561
2656
|
},
|
|
2562
2657
|
{
|
|
2563
|
-
"page":
|
|
2658
|
+
"page": 41,
|
|
2564
2659
|
"heading": "Export (formatQuery) › sequelize",
|
|
2565
2660
|
"anchor": "sequelize",
|
|
2566
2661
|
"text": "### `sequelize`\n\n```ts\nimport { Op } from 'sequelize';\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, { format: 'sequelize', context: { sequelizeOperators: Op } });\n```\n\nReturns a Sequelize `where` object (`Op` from `sequelize`)."
|
|
2567
2662
|
},
|
|
2568
2663
|
{
|
|
2569
|
-
"page":
|
|
2664
|
+
"page": 41,
|
|
2570
2665
|
"heading": "Export (formatQuery) › tanstackdb",
|
|
2571
2666
|
"anchor": "tanstackdb",
|
|
2572
2667
|
"text": "### `tanstack_db`\n\n```ts\nimport * as tanStackDbOperators from '@tanstack/db';\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, { format: 'tanstack_db', context: { tanStackDbOperators } });\n```\n\nReturns `refs => expression` for `.where()` in a TanStack DB live query (`tanStackDbOperators` = `@tanstack/db` module or picked operators)."
|
|
2573
2668
|
},
|
|
2574
2669
|
{
|
|
2575
|
-
"page":
|
|
2670
|
+
"page": 41,
|
|
2576
2671
|
"heading": "Export (formatQuery) › json",
|
|
2577
2672
|
"anchor": "json",
|
|
2578
2673
|
"text": "### `json`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'json');\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"beginsWith\",\n \"value\": \"Stev\"\n },\n {\n \"field\": \"age\",\n \"operator\": \"between\",\n \"value\": [18, 65]\n },\n {\n \"combinator\": \"or\",\n \"rules\": [\n {\n \"field\": \"lastName\",\n \"operator\": \"in\",\n \"value\": [\"Vai\", \"Vaughan\"]\n },\n {\n \"field\": \"isMusician\",\n \"operator\": \"=\",\n \"value\": true\n }\n ]\n }\n ]\n}\n```"
|
|
2579
2674
|
},
|
|
2580
2675
|
{
|
|
2581
|
-
"page":
|
|
2676
|
+
"page": 41,
|
|
2582
2677
|
"heading": "Export (formatQuery) › jsonwithoutids",
|
|
2583
2678
|
"anchor": "jsonwithoutids",
|
|
2584
2679
|
"text": "### `json_without_ids`\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, 'json_without_ids');\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"age\", \"operator\": \"between\", \"value\": [18, 65] },\n {\n \"combinator\": \"or\",\n \"rules\": [\n { \"field\": \"lastName\", \"operator\": \"in\", \"value\": [\"Vai\", \"Vaughan\"] },\n { \"field\": \"isMusician\", \"operator\": \"=\", \"value\": true }\n ]\n }\n ]\n}\n```"
|
|
2585
2680
|
},
|
|
2586
2681
|
{
|
|
2587
|
-
"page":
|
|
2682
|
+
"page": 41,
|
|
2588
2683
|
"heading": "Export (formatQuery) › diagnostics",
|
|
2589
2684
|
"anchor": "diagnostics",
|
|
2590
2685
|
"text": "### `diagnostics`\n\nAnnotated tree + flat diagnostics. Pass `fields` for field/type checks.\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nformatQuery(query, { format: 'diagnostics', fields });\n```\n\n```json title=\"Output\"\n{\n \"query\": {\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"beginsWith\",\n \"value\": \"Stev\",\n \"valid\": true,\n \"path\": [0],\n \"level\": 1\n },\n {\n \"field\": \"age\",\n \"operator\": \"between\",\n \"value\": [18, 65],\n \"valid\": true,\n \"path\": [1],\n \"level\": 1\n },\n {\n \"combinator\": \"or\",\n \"rules\": [\n {\n \"field\": \"lastName\",\n \"operator\": \"in\",\n \"value\": [\"Vai\", \"Vaughan\"],\n \"valid\": true,\n \"path\": [2, 0],\n \"level\": 2\n },\n {\n \"field\": \"isMusician\",\n \"operator\": \"=\",\n \"value\": true,\n \"valid\": true,\n \"path\": [2, 1],\n \"level\": 2\n }\n ],\n \"valid\": true,\n \"path\": [2],\n \"level\": 1\n }\n ],\n \"valid\": true,\n \"path\": [],\n \"level\": 0\n },\n \"diagnostics\": [],\n \"stats\": {\n \"totalRules\": 4,\n \"totalGroups\": 2,\n \"validRules\": 4,\n \"invalidRules\": 0,\n \"validGroups\": 2,\n \"invalidGroups\": 0\n },\n \"fieldSummary\": {\n \"firstName\": { \"ruleCount\": 1, \"invalidCount\": 0 },\n \"age\": { \"ruleCount\": 1, \"invalidCount\": 0 },\n \"lastName\": { \"ruleCount\": 1, \"invalidCount\": 0 },\n \"isMusician\": { \"ruleCount\": 1, \"invalidCount\": 0 }\n }\n}\n```"
|
|
2591
2686
|
},
|
|
2592
2687
|
{
|
|
2593
|
-
"page":
|
|
2688
|
+
"page": 41,
|
|
2594
2689
|
"heading": "Import (parsers)",
|
|
2595
2690
|
"anchor": "import-parsers",
|
|
2596
2691
|
"text": "## Import (parsers)\n\nParsers return a `RuleGroupType` without `id`s. Most parsers return list values (`in`, `between`) as comma-separated strings unless you pass `{ listsAsArrays: true }`. Pass `{ fields }` to drop unknown fields, and `{ independentCombinators: true }` for `RuleGroupTypeIC`. Use `prepareRuleGroup` before passing the result to a controlled `<QueryBuilder query>`."
|
|
2597
2692
|
},
|
|
2598
2693
|
{
|
|
2599
|
-
"page":
|
|
2694
|
+
"page": 41,
|
|
2600
2695
|
"heading": "Import (parsers) › parseSQL",
|
|
2601
2696
|
"anchor": "parsesql",
|
|
2602
2697
|
"text": "### `parseSQL`\n\n```ts\nimport { parseSQL } from '@react-querybuilder/core/parseSQL';\n\nparseSQL(\n \"SELECT * FROM t WHERE firstName LIKE 'Stev%' AND (lastName IN ('Vai', 'Vaughan') OR age BETWEEN 18 AND 65)\"\n);\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n {\n \"combinator\": \"or\",\n \"rules\": [\n { \"field\": \"lastName\", \"operator\": \"in\", \"value\": \"Vai, Vaughan\" },\n { \"field\": \"age\", \"operator\": \"between\", \"value\": \"18, 65\" }\n ]\n }\n ]\n}\n```"
|
|
2603
2698
|
},
|
|
2604
2699
|
{
|
|
2605
|
-
"page":
|
|
2700
|
+
"page": 41,
|
|
2606
2701
|
"heading": "Import (parsers) › parseMongoDB",
|
|
2607
2702
|
"anchor": "parsemongodb",
|
|
2608
2703
|
"text": "### `parseMongoDB`\n\n```ts\nimport { parseMongoDB } from '@react-querybuilder/core/parseMongoDB';\n\nparseMongoDB('{\"$and\":[{\"firstName\":{\"$regex\":\"^Stev\"}},{\"age\":{\"$gte\":18,\"$lte\":65}}]}');\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"age\", \"operator\": \"between\", \"value\": \"18,65\" }\n ]\n}\n```"
|
|
2609
2704
|
},
|
|
2610
2705
|
{
|
|
2611
|
-
"page":
|
|
2706
|
+
"page": 41,
|
|
2612
2707
|
"heading": "Import (parsers) › parseJsonLogic",
|
|
2613
2708
|
"anchor": "parsejsonlogic",
|
|
2614
2709
|
"text": "### `parseJsonLogic`\n\n```ts\nimport { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic';\n\nparseJsonLogic(\n '{\"and\":[{\"startsWith\":[{\"var\":\"firstName\"},\"Stev\"]},{\"<=\":[18,{\"var\":\"age\"},65]}]}'\n);\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"age\", \"operator\": \"between\", \"value\": \"18,65\" }\n ]\n}\n```"
|
|
2615
2710
|
},
|
|
2616
2711
|
{
|
|
2617
|
-
"page":
|
|
2712
|
+
"page": 41,
|
|
2618
2713
|
"heading": "Import (parsers) › parseCEL",
|
|
2619
2714
|
"anchor": "parsecel",
|
|
2620
2715
|
"text": "### `parseCEL`\n\n```ts\nimport { parseCEL } from '@react-querybuilder/core/parseCEL';\n\nparseCEL('firstName.startsWith(\"Stev\") && (age >= 18 || isMusician == true)');\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n {\n \"combinator\": \"or\",\n \"rules\": [\n { \"field\": \"age\", \"operator\": \">=\", \"value\": 18 },\n { \"field\": \"isMusician\", \"operator\": \"=\", \"value\": true }\n ]\n }\n ]\n}\n```"
|
|
2621
2716
|
},
|
|
2622
2717
|
{
|
|
2623
|
-
"page":
|
|
2718
|
+
"page": 41,
|
|
2624
2719
|
"heading": "Import (parsers) › parseSpEL",
|
|
2625
2720
|
"anchor": "parsespel",
|
|
2626
2721
|
"text": "### `parseSpEL`\n\n```ts\nimport { parseSpEL } from '@react-querybuilder/core/parseSpEL';\n\nparseSpEL(\"lastName == 'Vai' and age >= 18\");\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"lastName\", \"operator\": \"=\", \"value\": \"Vai\" },\n { \"field\": \"age\", \"operator\": \">=\", \"value\": 18 }\n ]\n}\n```"
|
|
2627
2722
|
},
|
|
2628
2723
|
{
|
|
2629
|
-
"page":
|
|
2724
|
+
"page": 41,
|
|
2630
2725
|
"heading": "Import (parsers) › parseJSONata",
|
|
2631
2726
|
"anchor": "parsejsonata",
|
|
2632
2727
|
"text": "### `parseJSONata`\n\n```ts\nimport { parseJSONata } from '@react-querybuilder/core/parseJSONata';\n\nparseJSONata('$contains(firstName, \"Stev\") and age >= 18');\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"contains\", \"value\": \"Stev\" },\n { \"field\": \"age\", \"operator\": \">=\", \"value\": 18 }\n ]\n}\n```"
|
|
2633
2728
|
},
|
|
2634
2729
|
{
|
|
2635
|
-
"page":
|
|
2730
|
+
"page": 41,
|
|
2636
2731
|
"heading": "Import (parsers) › parseCypher",
|
|
2637
2732
|
"anchor": "parsecypher",
|
|
2638
2733
|
"text": "### `parseCypher`\n\nOnly `WHERE` conditions are kept; property access (`p.age`) stays in `field`.\n\n```ts\nimport { parseCypher } from '@react-querybuilder/core/parseCypher';\n\nparseCypher(\"MATCH (p:Person) WHERE p.firstName STARTS WITH 'Stev' AND p.age >= 18 RETURN p\");\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"p.firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"p.age\", \"operator\": \">=\", \"value\": 18 }\n ]\n}\n```"
|
|
2639
2734
|
},
|
|
2640
2735
|
{
|
|
2641
|
-
"page":
|
|
2736
|
+
"page": 41,
|
|
2642
2737
|
"heading": "Import (parsers) › parseGQL",
|
|
2643
2738
|
"anchor": "parsegql",
|
|
2644
2739
|
"text": "### `parseGQL`\n\n```ts\nimport { parseGQL } from '@react-querybuilder/core/parseCypher';\n\nparseGQL(\"MATCH (p:Person) WHERE p.age >= 18 AND p.lastName IN ['Vai', 'Vaughan']\");\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"p.age\", \"operator\": \">=\", \"value\": 18 },\n { \"field\": \"p.lastName\", \"operator\": \"in\", \"value\": [\"Vai\", \"Vaughan\"] }\n ]\n}\n```"
|
|
2645
2740
|
},
|
|
2646
2741
|
{
|
|
2647
|
-
"page":
|
|
2742
|
+
"page": 41,
|
|
2648
2743
|
"heading": "Import (parsers) › parseGremlin",
|
|
2649
2744
|
"anchor": "parsegremlin",
|
|
2650
2745
|
"text": "### `parseGremlin`\n\n```ts\nimport { parseGremlin } from '@react-querybuilder/core/parseGremlin';\n\nparseGremlin(\"g.V().has('firstName', startingWith('Stev')).has('age', between(18, 65))\");\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"age\", \"operator\": \"between\", \"value\": [18, 65] }\n ]\n}\n```"
|
|
2651
2746
|
},
|
|
2652
2747
|
{
|
|
2653
|
-
"page":
|
|
2748
|
+
"page": 41,
|
|
2654
2749
|
"heading": "Import (parsers) › parseSPARQL",
|
|
2655
2750
|
"anchor": "parsesparql",
|
|
2656
2751
|
"text": "### `parseSPARQL`\n\nOnly `FILTER` conditions are kept; variables keep their `?` prefix.\n\n```ts\nimport { parseSPARQL } from '@react-querybuilder/core/parseSPARQL';\n\nparseSPARQL(\n 'SELECT ?p WHERE { ?p ex:age ?age . FILTER(STRSTARTS(?firstName, \"Stev\") && ?age >= 18) }'\n);\n```\n\n```json title=\"Output\"\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"?firstName\", \"operator\": \"beginsWith\", \"value\": \"Stev\" },\n { \"field\": \"?age\", \"operator\": \">=\", \"value\": 18 }\n ]\n}\n```"
|
|
2657
2752
|
},
|
|
2658
2753
|
{
|
|
2659
|
-
"page":
|
|
2754
|
+
"page": 41,
|
|
2660
2755
|
"heading": "JSON Schema (getQueryJsonSchema)",
|
|
2661
2756
|
"anchor": "json-schema-getqueryjsonschema",
|
|
2662
2757
|
"text": "## JSON Schema (`getQueryJsonSchema`)\n\nJSON Schema for queries matching `fields`, for LLM structured output/tool inputs or Ajv. Loose mode shown for brevity; the default `full` mode also constrains operators and values per field. Check the result with the `diagnostics` format. See [JSON Schema](../utils/json-schema).\n\n```ts\nimport { getQueryJsonSchema } from '@react-querybuilder/core';\n\ngetQueryJsonSchema(fields, { mode: 'loose', operators: ['=', 'in'] });\n```\n\n```json title=\"Output\"\n{\n \"$defs\": {\n \"group\": {\n \"type\": \"object\",\n \"properties\": {\n \"combinator\": { \"type\": \"string\", \"enum\": [\"and\", \"or\"] },\n \"rules\": {\n \"type\": \"array\",\n \"items\": { \"anyOf\": [{ \"$ref\": \"#/$defs/rule\" }, { \"$ref\": \"#/$defs/group\" }] }\n },\n \"not\": { \"type\": \"boolean\" }\n },\n \"required\": [\"combinator\", \"rules\"]\n },\n \"rule\": {\n \"type\": \"object\",\n \"properties\": {\n \"field\": { \"type\": \"string\", \"enum\": [\"firstName\", \"lastName\", \"age\", \"isMusician\"] },\n \"operator\": { \"type\": \"string\", \"enum\": [\"=\", \"in\"] },\n \"value\": {}\n },\n \"required\": [\"field\", \"operator\", \"value\"]\n }\n },\n \"$ref\": \"#/$defs/group\"\n}\n```"
|
|
2663
2758
|
},
|
|
2664
2759
|
{
|
|
2665
|
-
"page":
|
|
2760
|
+
"page": 42,
|
|
2666
2761
|
"heading": "",
|
|
2667
2762
|
"anchor": "",
|
|
2668
2763
|
"text": "{ name: 'firstName', label: 'First name' },\n { name: 'lastName', label: 'Last name' },\n];\n\nThese examples showcase React Query Builder's extensive customization capabilities. Submit a pull request to share your interesting or unusual implementations!"
|
|
2669
2764
|
},
|
|
2670
2765
|
{
|
|
2671
|
-
"page":
|
|
2766
|
+
"page": 42,
|
|
2672
2767
|
"heading": " › Justified layout",
|
|
2673
2768
|
"anchor": "justified-layout",
|
|
2674
2769
|
"text": "### Justified layout\n\nThese CSS rules push \"clone\", \"lock\", or \"remove\" buttons to the right edge, creating a justified appearance. <DemoLink option=\"justifiedLayout\" text=\"The demo has an option to enable this technique\" />.\n\n**Tip:**\n\nThese styles are now part of the default stylesheet.\n\nApply them by adding `queryBuilder-justified` to the `className` prop on your `<QueryBuilder />` component or an ancestor element.\n\n<details>\n <summary>CSS</summary>\n\n```css\n.queryBuilder .ruleGroup-addGroup + button.ruleGroup-cloneGroup,\n.queryBuilder .ruleGroup-addGroup + button.ruleGroup-lock,\n.queryBuilder .ruleGroup-addGroup + button.ruleGroup-remove,\n.queryBuilder .rule-operators + button.rule-cloneRule,\n.queryBuilder .rule-operators + button.rule-lock,\n.queryBuilder .rule-operators + button.rule-remove,\n.queryBuilder .rule-value + button.rule-cloneRule,\n.queryBuilder .rule-value + button.rule-lock,\n.queryBuilder .rule-value + button.rule-remove {\n margin-left: auto !important;\n}\n```\n\n</details>\n\n <div className=\"queryBuilder-justified\">\n </div>"
|
|
2675
2770
|
},
|
|
2676
2771
|
{
|
|
2677
|
-
"page":
|
|
2772
|
+
"page": 42,
|
|
2678
2773
|
"heading": " › Inline combinator selectors",
|
|
2679
2774
|
"anchor": "inline-combinator-selectors",
|
|
2680
2775
|
"text": "### Inline combinator selectors\n\nPositions combinator selectors to the right of their preceding rules or groups.\n\n**Note:**\n\nThese examples use [independent combinators](../components/querybuilder#independent-combinators), but the same styles work with [`showCombinatorsBetweenRules`](../components/querybuilder#showcombinatorsbetweenrules).\n\n<details>\n <summary>CSS</summary>\n\n```css\n.ruleGroup-body {\n /* Override the default flex layout */\n display: grid !important;\n /* Allow the left-hand column (the rule/subgroup) to expand as needed */\n /* Collapse the right-hand column (the combinator) to the width of the content */\n grid-template-columns: auto min-content;\n /* Keep the combinator aligned with the bottom of the rule/subgroup */\n align-items: end;\n}\n```\n\n</details>\n\nAlternatively, position combinators to the left of their following rules or groups:\n\n<details>\n <summary>CSS</summary>\n\n```css\n.ruleGroup-body {\n /* Override the default flex layout */\n display: grid !important;\n /* Allow the right-hand column (the rule/subgroup) to expand as needed */\n /* Collapse the left-hand column (the combinator) to the width of the content */\n grid-template-columns: min-content auto;\n /* Keep the combinator aligned with the top of the rule/subgroup */\n align-items: start;\n}\n\n/* Indent the first rule/subgroup since it has no preceding combinator */\n.ruleGroup-body > .rule:first-child:not(:only-child),\n.ruleGroup-body > .ruleGroup:first-child:not(:only-child) {\n grid-column-start: 2;\n}\n```\n\n</details>"
|
|
2681
2776
|
},
|
|
2682
2777
|
{
|
|
2683
|
-
"page":
|
|
2778
|
+
"page": 42,
|
|
2684
2779
|
"heading": "Disjunctive normal form",
|
|
2685
2780
|
"anchor": "disjunctive-normal-form",
|
|
2686
2781
|
"text": "## Disjunctive normal form\n\nThis 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:\n\n**CSS customizations:**\n\n- Swap header and body order within groups\n- Hide top-level \"add rule\" button (prevents rules in root group)\n- Hide subgroup \"add group\" buttons (prevents deep nesting)\n- Display subgroups horizontally\n- Stack rule elements vertically (accommodates horizontal layout)\n- Hide \"remove group\" buttons (would appear oddly positioned)\n\n**Component/prop customizations:**\n\n- Display combinators as static text (DNF is always \"OR of ANDs\")\n- Remove groups when their last rule is deleted\n- Auto-add default rules to new groups (enables immediate removal)\n\n**Tip:**\n\nThis example demonstrates techniques detailed in the [arbitrary updates guide](./arbitrary-updates) and [hooks documentation](../utils/hooks#usequerybuilderquery).\n\n> _You may want to hide the left-hand sidebar (click `<<` at the bottom) to have a wider view of this example._\n\n```css\n.queryBuilder .ruleGroup .ruleGroup-header {\n /* Move group header below body. */\n /* You could also accomplish the same effect with */\n /* a custom RuleGroup or Rule component without */\n /* rearranging the header and body. Also note that */\n /* this method can cause problems with drag-and-drop. */\n order: 2;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-header > .ruleGroup-remove {\n /* Hide \"remove group\" button */\n display: none;\n}\n.queryBuilder > .ruleGroup {\n flex-direction: row;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-header {\n flex-shrink: 1 !important;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-header > .ruleGroup-addRule {\n /* Hide \"add rule\" button from root group */\n display: none;\n}\n.queryBuilder .betweenRules {\n font-family: sans-serif;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body {\n width: 100%;\n flex-direction: row;\n align-items: stretch;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body > .ruleGroup {\n /* Extra spacing above the \"add rule\" buttons */\n gap: 1rem;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body .ruleGroup-addGroup {\n /* Hide \"add group\" button from \"and\" groups */\n display: none;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body > .betweenRules {\n /* Fix height of inline combinators */\n height: fit-content;\n /* Center vertically */\n margin-top: auto;\n margin-bottom: auto;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body > .ruleGroup {\n /* Span the full width of the \"or\" group body */\n width: 100%;\n}\n.queryBuilder > .ruleGroup > .ruleGroup-body > .ruleGroup .betweenRules {\n /* A little space between each combinator and the rule above it */\n margin-top: 1rem;\n}\n.queryBuilder > .ruleGroup .rule {\n /* Stack field/operator/value vertically */\n flex-direction: column;\n /* Left-align */\n align-items: flex-start;\n}\n.queryBuilder > .ruleGroup .rule > .rule-remove {\n /* Move \"remove rule\" button to top of rule */\n order: -1;\n /* Right-align \"remove rule\" button */\n margin-left: auto;\n}\n.queryBuilder .rule > .rule-value {\n max-width: 8rem;\n}\n```\n\n```tsx\nimport { useState } from 'react';\nimport type {\n Field,\n RuleGroupType,\n ActionProps,\n CombinatorSelectorProps,\n} from 'react-querybuilder';\nimport {\n ActionElement,\n getOption,\n QueryBuilder,\n findPath,\n getParentPath,\n remove,\n useQueryBuilderQuery,\n} from 'react-querybuilder';\n\nconst fields: Field[] = [\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n];\n\nconst initialQuery: RuleGroupType = {\n combinator: 'or',\n rules: [\n {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: 'beginsWith', value: 'Stev' },\n { field: 'lastName', operator: 'in', value: 'Vai,Vaughan' },\n ],\n },\n {\n combinator: 'and',\n rules: [\n {\n field: 'firstName',\n operator: 'beginsWith',\n value: 'To remove group, remove last rule',\n },\n ],\n },\n ],\n};\n\nconst RemoveRule = (props: ActionProps) => {\n const query = useQueryBuilderQuery();\n if (!query) return null;\n const parentPath = getParentPath(props.path);\n /** Count of rules in the same group */\n const siblingCount = (findPath(parentPath, query) as RuleGroupType).rules.length - 1;\n const onClick = (e: React.MouseEvent) => {\n if (siblingCount > 0) {\n // If sibling rules exist, remove the current rule\n // using the default handler\n props.handleOnClick(e, props.context);\n } else {\n // Otherwise, remove the entire group\n props.schema.dispatchQuery(remove(query, getParentPath(props.path)));\n }\n };\n // Render with default props except for our custom click handler\n if (siblingCount === 0 && query.rules.length === 1) {\n // Don't render the \"remove rule\" action if this is\n // the only rule in the only group\n return null;\n // (You could also go ahead and render the button but\n // not remove its group in the click handler)\n }\n return <ActionElement {...props} handleOnClick={onClick} />;\n};\n\nconst CombinatorSelector = (props: CombinatorSelectorProps) => (\n // Render static value to prevent combinator updates\n <div className={props.className} title={props.title}>\n {getOption(props.options, props.value!)?.label}\n </div>\n);\n\nexport default () => {\n const [query, setQuery] = useState(initialQuery);\n\n return (\n <QueryBuilder\n fields={fields}\n query={query}\n onQueryChange={setQuery}\n // Move combinator control from group\n // header to group body:\n showCombinatorsBetweenRules\n // Ensure new groups are not empty:\n addRuleToNewGroups\n controlElements={{\n // Use our custom components\n removeRuleAction: RemoveRule,\n combinatorSelector: CombinatorSelector,\n }}\n translations={{\n addGroup: { label: '+' },\n addRule: { label: '+' },\n }}\n />\n );\n};\n```"
|
|
2687
2782
|
},
|
|
2688
2783
|
{
|
|
2689
|
-
"page":
|
|
2784
|
+
"page": 44,
|
|
2690
2785
|
"heading": "",
|
|
2691
2786
|
"anchor": "",
|
|
2692
2787
|
"text": "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.\"\n\n { name: 'nestedStringArray', label: 'Nested String Array', matchModes: true },\n {\n name: 'nestedNumberArray',\n label: 'Nested Number Array',\n matchModes: [\n { name: 'all', label: 'Every' },\n { name: 'none', label: 'Not one' },\n { name: 'some', label: 'Several' },\n ],\n },\n {\n name: 'nestedObjectArray',\n label: 'Nested Object Array',\n matchModes: ['all', 'none', 'some'],\n subproperties: [\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n ],\n },\n];\n\n combinator: 'and',\n rules: [\n {\n field: 'nestedStringArray',\n operator: '=',\n match: { mode: 'atMost', threshold: 2 },\n value: { combinator: 'and', rules: [{ field: '', operator: 'contains', value: 'abc' }] },\n },\n {\n field: 'nestedNumberArray',\n operator: '=',\n match: { mode: 'some' },\n value: { combinator: 'and', rules: [{ field: '', operator: 'between', value: [12, 14] }] },\n },\n {\n field: 'nestedObjectArray',\n operator: '=',\n match: { mode: 'all' },\n value: {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: 'beginsWith', value: 'S' },\n { field: 'lastName', operator: 'doesNotEndWith', value: 's' },\n ],\n },\n },\n ],\n};"
|
|
2693
2788
|
},
|
|
2694
2789
|
{
|
|
2695
|
-
"page":
|
|
2790
|
+
"page": 44,
|
|
2696
2791
|
"heading": "Configuring subqueries",
|
|
2697
2792
|
"anchor": "configuring-subqueries",
|
|
2698
2793
|
"text": "## Configuring subqueries\n\nEnable 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."
|
|
2699
2794
|
},
|
|
2700
2795
|
{
|
|
2701
|
-
"page":
|
|
2796
|
+
"page": 44,
|
|
2702
2797
|
"heading": "Configuring subqueries › Match modes configuration",
|
|
2703
2798
|
"anchor": "match-modes-configuration",
|
|
2704
2799
|
"text": "### Match modes configuration\n\nThe `matchModes` property accepts several formats:\n\n- **`true`** - Enables all available match modes with default labels\n- **`MatchMode[]`** - Array of match mode names (e.g., `['all', 'some', 'none']`)\n- **`Option<MatchMode>[]`** - Array of objects with custom labels (e.g., `[{ name: 'all', label: 'Every' }]`)\n\n```ts\nconst fields: Field[] = [\n {\n name: 'nestedStringArray',\n label: 'Nested String Array',\n // Enable all match modes with default labels\n matchModes: true,\n },\n {\n name: 'nestedNumberArray',\n label: 'Nested Number Array',\n // Enable specific match modes with custom labels\n matchModes: [\n { name: 'all', label: 'Every' },\n { name: 'none', label: 'Not one' },\n { name: 'some', label: 'Several' },\n ],\n },\n {\n name: 'nestedObjectArray',\n label: 'Nested Object Array',\n // Enable specific match modes with default labels\n matchModes: ['all', 'none', 'some'],\n // Define properties of objects in the nested array\n subproperties: [\n { name: 'firstName', label: 'First Name' },\n { name: 'lastName', label: 'Last Name' },\n ],\n },\n];\n```"
|
|
2705
2800
|
},
|
|
2706
2801
|
{
|
|
2707
|
-
"page":
|
|
2802
|
+
"page": 44,
|
|
2708
2803
|
"heading": "Configuring subqueries › Dynamic match mode configuration",
|
|
2709
2804
|
"anchor": "dynamic-match-mode-configuration",
|
|
2710
2805
|
"text": "### Dynamic match mode configuration\n\nConfigure 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:\n\n```ts\nconst getMatchModes = (field: string, misc: { fieldData: Field }) => {\n // Return true to enable all match modes for any field\n if (field === 'flexibleArray') return true;\n\n // Return specific match modes based on field type\n if (misc.fieldData.datatype === 'array') {\n return ['all', 'some', 'none'];\n }\n\n // Return false or undefined to disable subqueries for this field\n return false;\n};\n\n<QueryBuilder\n fields={fields}\n getMatchModes={getMatchModes}\n // ... other props\n/>;\n```"
|
|
2711
2806
|
},
|
|
2712
2807
|
{
|
|
2713
|
-
"page":
|
|
2808
|
+
"page": 44,
|
|
2714
2809
|
"heading": "Configuring subqueries › Customizing subquery builder props",
|
|
2715
2810
|
"anchor": "customizing-subquery-builder-props",
|
|
2716
2811
|
"text": "### Customizing subquery builder props\n\nThe `getSubQueryBuilderProps` prop customizes individual subquery builder configurations. Use this to provide different field sets, operators, or other settings for nested queries:\n\n```ts\nconst getSubQueryBuilderProps = (field: string, misc: { fieldData: Field }) => {\n // Return props that should override the parent query builder's configuration\n if (field === 'nestedObjectArray') {\n return {\n fields: misc.fieldData.subproperties || [],\n operators: [\n { name: '=', label: 'equals' },\n { name: 'contains', label: 'contains' },\n { name: 'beginsWith', label: 'begins with' },\n ],\n // Disable certain features for subqueries\n showCloneButtons: false,\n showLockButtons: false,\n };\n }\n\n // For primitive arrays, don't show field selector\n if (field === 'nestedStringArray') {\n return {\n fields: [{ name: '', label: '' }],\n autoSelectField: true,\n };\n }\n\n return {};\n};\n\n<QueryBuilder\n fields={fields}\n getSubQueryBuilderProps={getSubQueryBuilderProps}\n // ... other props\n/>;\n```\n\n**Note:** Props like `query`, `onQueryChange`, and `enableDragAndDrop` are automatically managed and cannot be overridden for subquery builders."
|
|
2717
2812
|
},
|
|
2718
2813
|
{
|
|
2719
|
-
"page":
|
|
2814
|
+
"page": 44,
|
|
2720
2815
|
"heading": "Configuring subqueries › Available match modes",
|
|
2721
2816
|
"anchor": "available-match-modes",
|
|
2722
2817
|
"text": "### Available match modes\n\nReact Query Builder supports six match modes:\n\n| Mode | Type | Description | Requires threshold |\n| --------- | --------- | --------------------------------------------------- | ------------------ |\n| `all` | Unary | Every item in the array matches the subquery | No |\n| `some` | Unary | At least one item in the array matches the subquery | No |\n| `none` | Unary | No items in the array match the subquery | No |\n| `atLeast` | Threshold | At least N items match the subquery | Yes |\n| `atMost` | Threshold | At most N items match the subquery | Yes |\n| `exactly` | Threshold | Exactly N items match the subquery | Yes |"
|
|
2723
2818
|
},
|
|
2724
2819
|
{
|
|
2725
|
-
"page":
|
|
2820
|
+
"page": 44,
|
|
2726
2821
|
"heading": "Working with object properties",
|
|
2727
2822
|
"anchor": "working-with-object-properties",
|
|
2728
2823
|
"text": "## Working with object properties\n\nFor 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.\n\nWhen `subproperties` is undefined, subquery rules don't render a field selector, and the `field` property should remain an empty string."
|
|
2729
2824
|
},
|
|
2730
2825
|
{
|
|
2731
|
-
"page":
|
|
2826
|
+
"page": 44,
|
|
2732
2827
|
"heading": "Query structure",
|
|
2733
2828
|
"anchor": "query-structure",
|
|
2734
2829
|
"text": "## Query structure\n\nSubqueries store as nested `RuleGroupType` objects in the rule's `value` property. The `match` property contains the mode and optional threshold:\n\n```ts\nconst exampleQuery: RuleGroupType = {\n combinator: 'and',\n rules: [\n {\n field: 'nestedStringArray',\n operator: '=', // Ignored when match is present\n match: { mode: 'atMost', threshold: 2 },\n value: {\n combinator: 'and',\n rules: [{ field: '', operator: 'contains', value: 'abc' }],\n },\n },\n {\n field: 'nestedObjectArray',\n operator: '=',\n match: { mode: 'all' },\n value: {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: 'beginsWith', value: 'S' },\n { field: 'lastName', operator: 'doesNotEndWith', value: 's' },\n ],\n },\n },\n ],\n};\n```\n\n**Info:**\n\nWhen a rule has a valid `match` property, the `operator` property is ignored. The match mode determines subquery evaluation logic."
|
|
2735
2830
|
},
|
|
2736
2831
|
{
|
|
2737
|
-
"page":
|
|
2832
|
+
"page": 44,
|
|
2738
2833
|
"heading": "Export format support",
|
|
2739
2834
|
"anchor": "export-format-support",
|
|
2740
2835
|
"text": "## Export format support\n\nExport format support for subqueries varies by implementation:\n\n| Support level | Formats |\n| ------------- | --------------------------------------------------------------------------------------------- |\n| Full | \"jsonlogic\"[^1], \"jsonata\", \"cel\", \"spel\", \"natural_language\", ~~\"mongodb\"~~, \"mongodb_query\" |\n| Partial | \"sql\"[^2], \"parameterized\"[^2], \"drizzle\"[^2], \"elasticsearch\"[^3] |\n| None | \"parameterized_named\"[^4], \"prisma\", \"sequelize\", \"tanstack_db\", \"ldap\" |\n\n**Caution:**\n\nIn unsupported formats, rules with valid `match` properties are treated as invalid and ignored during export.\n\n[^1]: JsonLogic was the original inspiration for this feature.\n\n[^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.\n\n[^3]: ElasticSearch nested queries only support `\"some\"` and `\"none\"` match modes.\n\n[^4]: PostgreSQL does not support named parameters"
|
|
2741
2836
|
},
|
|
2742
2837
|
{
|
|
2743
|
-
"page":
|
|
2838
|
+
"page": 45,
|
|
2744
2839
|
"heading": "",
|
|
2745
2840
|
"anchor": "",
|
|
2746
2841
|
"text": "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.\n\n```bash npm2yarn\nnpm install react-querybuilder\n```\n\n```tsx\nimport { QueryBuilder } from 'react-querybuilder';\nimport { QueryBuilderHistory } from 'react-querybuilder/history';\n\nconst App = () => (\n <QueryBuilderHistory>\n <QueryBuilder fields={fields} />\n </QueryBuilderHistory>\n);\n```\n\nThat's the whole setup. `QueryBuilderHistory` records changes for descendant query builders and defaults their [`showUndoRedo`](../components/querybuilder#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.\n\n**Tip:**\n\nUnlike most undo/redo recipes, this works in [uncontrolled mode](../components/querybuilder#defaultquery)—you don't need to lift the query into your own state."
|
|
2747
2842
|
},
|
|
2748
2843
|
{
|
|
2749
|
-
"page":
|
|
2844
|
+
"page": 45,
|
|
2750
2845
|
"heading": "How it works",
|
|
2751
2846
|
"anchor": "how-it-works",
|
|
2752
2847
|
"text": "## How it works\n\nEvery 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.\n\nBecause 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."
|
|
2753
2848
|
},
|
|
2754
2849
|
{
|
|
2755
|
-
"page":
|
|
2850
|
+
"page": 45,
|
|
2756
2851
|
"heading": "Options",
|
|
2757
2852
|
"anchor": "options",
|
|
2758
2853
|
"text": "## Options\n\nBoth options can be set on `QueryBuilderHistory`:\n\n```tsx\n<QueryBuilderHistory maxHistory={50} coalesceMs={500}>\n <QueryBuilder fields={fields} />\n</QueryBuilderHistory>\n```"
|
|
2759
2854
|
},
|
|
2760
2855
|
{
|
|
2761
|
-
"page":
|
|
2856
|
+
"page": 45,
|
|
2762
2857
|
"heading": "Options › maxHistory",
|
|
2763
2858
|
"anchor": "maxhistory",
|
|
2764
2859
|
"text": "### `maxHistory`\n\n`number` (default `50`)\n\nMaximum number of undo steps to retain. Older entries are discarded."
|
|
2765
2860
|
},
|
|
2766
2861
|
{
|
|
2767
|
-
"page":
|
|
2862
|
+
"page": 45,
|
|
2768
2863
|
"heading": "Options › coalesceMs",
|
|
2769
2864
|
"anchor": "coalescems",
|
|
2770
2865
|
"text": "### `coalesceMs`\n\n`number` (default `500`)\n\nConsecutive 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.\n\nSet to `0` to record every change separately.\n\nStructural changes—adding, removing, moving, or reordering rules and groups—never merge with each other, so each one is always its own undo step.\n\n**Note:**\n\nCoalescing 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."
|
|
2771
2866
|
},
|
|
2772
2867
|
{
|
|
2773
|
-
"page":
|
|
2868
|
+
"page": 45,
|
|
2774
2869
|
"heading": "Custom controls",
|
|
2775
2870
|
"anchor": "custom-controls",
|
|
2776
2871
|
"text": "## Custom controls\n\n`useQueryBuilderHistory` gives you the same controls that power the default buttons. It takes a query builder's [`qbId`](../components/querybuilder#qbid) and can be used anywhere—including _outside_ the query builder's component tree, which is what makes external toolbars possible.\n\n```tsx\nimport { QueryBuilder } from 'react-querybuilder';\nimport { QueryBuilderHistory, useQueryBuilderHistory } from 'react-querybuilder/history';\n\nconst Toolbar = ({ qbId }: { qbId: string }) => {\n const { undo, redo, clear, canUndo, canRedo, past, future } = useQueryBuilderHistory(qbId);\n\n return (\n <div>\n <button type=\"button\" onClick={undo} disabled={!canUndo}>\n Undo\n </button>\n <button type=\"button\" onClick={redo} disabled={!canRedo}>\n Redo\n </button>\n <button type=\"button\" onClick={clear} disabled={!canUndo && !canRedo}>\n Clear history\n </button>\n <span>\n {past.length} undo / {future.length} redo\n </span>\n </div>\n );\n};\n\nconst App = () => (\n <QueryBuilderHistory>\n <Toolbar qbId=\"main\" />\n <QueryBuilder qbId=\"main\" fields={fields} />\n </QueryBuilderHistory>\n);\n```\n\nNote the explicit `qbId` on both components. Without it, the query builder generates an identifier internally that outside code has no way to discover."
|
|
2777
2872
|
},
|
|
2778
2873
|
{
|
|
2779
|
-
"page":
|
|
2874
|
+
"page": 45,
|
|
2780
2875
|
"heading": "Custom controls › useQueryBuilderHistory",
|
|
2781
2876
|
"anchor": "usequerybuilderhistory",
|
|
2782
2877
|
"text": "### `useQueryBuilderHistory`\n\n```ts\nuseQueryBuilderHistory(qbId: string, options?: { maxHistory?: number; coalesceMs?: number })\n```\n\n| Property | Type | Description |\n| --------- | -------------------- | ------------------------------------------------------------- |\n| `undo` | `() => void` | Restores the previous query. No-op when `canUndo` is `false`. |\n| `redo` | `() => void` | Restores the most recently undone query. |\n| `clear` | `() => void` | Discards all history without changing the current query. |\n| `canUndo` | `boolean` | Whether there is anything to undo. |\n| `canRedo` | `boolean` | Whether there is anything to redo. |\n| `past` | `RuleGroupTypeAny[]` | Previous queries, oldest first. |\n| `future` | `RuleGroupTypeAny[]` | Undone queries, newest first. |\n\nOptions passed here override those from the nearest `QueryBuilderHistory` ancestor.\n\n**Info:**\n\nRendering 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."
|
|
2783
2878
|
},
|
|
2784
2879
|
{
|
|
2785
|
-
"page":
|
|
2880
|
+
"page": 45,
|
|
2786
2881
|
"heading": "Keyboard shortcuts",
|
|
2787
2882
|
"anchor": "keyboard-shortcuts",
|
|
2788
2883
|
"text": "## Keyboard shortcuts\n\nKeyboard 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:\n\n```tsx\nimport { useEffect } from 'react';\nimport { useQueryBuilderHistory } from 'react-querybuilder/history';\n\nconst useUndoRedoShortcuts = (qbId: string) => {\n const { undo, redo } = useQueryBuilderHistory(qbId);\n\n useEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'z') return;\n // Leave text inputs alone so native undo keeps working while editing a value\n const target = event.target as HTMLElement | null;\n if (target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA') return;\n event.preventDefault();\n if (event.shiftKey) {\n redo();\n } else {\n undo();\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [redo, undo]);\n};\n```"
|
|
2789
2884
|
},
|
|
2790
2885
|
{
|
|
2791
|
-
"page":
|
|
2886
|
+
"page": 45,
|
|
2792
2887
|
"heading": "React Native",
|
|
2793
2888
|
"anchor": "react-native",
|
|
2794
2889
|
"text": "## React Native\n\n`QueryBuilderNative` from [`@react-querybuilder/native`](../compat) works the same way—wrap it in `QueryBuilderHistory` and the undo/redo controls render as React Native components (`NativeUndoRedoActions`, styled with the `undoRedoActions` key of the `styles` prop).\n\n```tsx\nimport { QueryBuilderNative } from '@react-querybuilder/native';\nimport { QueryBuilderHistory } from 'react-querybuilder/history';\n\nconst App = () => (\n <QueryBuilderHistory>\n <QueryBuilderNative fields={fields} />\n </QueryBuilderHistory>\n);\n```\n\nWithout a `QueryBuilderHistory` ancestor, `showUndoRedo` still renders working undo/redo controls in React Native, since `QueryBuilderNative` assigns `NativeUndoRedoActions` to the `undoRedoActions` control element by default. Wrapping in `QueryBuilderHistory` is still the better option: it enables `showUndoRedo` automatically and lets you configure `maxHistory` and `coalesceMs`."
|
|
2795
2890
|
},
|
|
2796
2891
|
{
|
|
2797
|
-
"page":
|
|
2892
|
+
"page": 45,
|
|
2798
2893
|
"heading": "Multiple query builders",
|
|
2799
2894
|
"anchor": "multiple-query-builders",
|
|
2800
2895
|
"text": "## Multiple query builders\n\nEach query builder has its own independent history, keyed by `qbId`. Undoing in one has no effect on any other."
|
|
2801
2896
|
},
|
|
2802
2897
|
{
|
|
2803
|
-
"page":
|
|
2898
|
+
"page": 45,
|
|
2804
2899
|
"heading": "History lifetime",
|
|
2805
2900
|
"anchor": "history-lifetime",
|
|
2806
2901
|
"text": "## History lifetime\n\nHistory 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.\n\nWhen 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`](../components/querybuilder#preservequerystateonunmount), which retains the history along with the query."
|
|
2807
2902
|
},
|
|
2808
2903
|
{
|
|
2809
|
-
"page":
|
|
2904
|
+
"page": 45,
|
|
2810
2905
|
"heading": "Alternative: controlled mode",
|
|
2811
2906
|
"anchor": "alternative-controlled-mode",
|
|
2812
2907
|
"text": "## Alternative: controlled mode\n\nIf 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.\n\nThis approach requires [controlled mode](../components/querybuilder#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."
|
|
2813
2908
|
},
|
|
2814
2909
|
{
|
|
2815
|
-
"page":
|
|
2910
|
+
"page": 46,
|
|
2816
2911
|
"heading": "",
|
|
2817
2912
|
"anchor": "",
|
|
2818
2913
|
"text": "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.\n\n**Note:**\n\nSome definitions below have been simplified from their actual implementations for legibility and ease of comprehension.\n\nThe **[API documentation](/api)** is generated from source code and provides complete type definitions with repository links for detailed reference."
|
|
2819
2914
|
},
|
|
2820
2915
|
{
|
|
2821
|
-
"page":
|
|
2916
|
+
"page": 46,
|
|
2822
2917
|
"heading": "Fields",
|
|
2823
2918
|
"anchor": "fields",
|
|
2824
2919
|
"text": "## Fields\n\n```ts\ninterface Field {\n id?: string; // The field identifier (if not provided, `name` will be used)\n name: string; // The field name (REQUIRED)\n label: string; // The field label (REQUIRED)\n operators?: OptionList<Operator>[]; // Array of operators (if not provided, `getOperators()` will be used)\n valueEditorType?: ValueEditorType; // Value editor type for this field (if not provided, `getValueEditorType()` will be used)\n inputType?: string | null; // @type attribute for the <input /> rendered by ValueEditor, e.g. 'text', 'number', or 'date' (if not provided, `getInputType()` will be used)\n values?: OptionList; // Array of value options, applicable when valueEditorType is 'select', 'radio', or 'multiselect' (if not provided, `getValues()` will be used)\n defaultOperator?: string; // Default operator for this field (if not provided, `getDefaultOperator()` will be used)\n defaultValue?: any; // Default value for this field (if not provided, `getDefaultValue()` will be used)\n placeholder?: string; // Placeholder text for the value editor when this field is selected\n validator?: RuleValidator; // Validation function for rules that specify this field\n valueSources?: ValueSources | ((operator: string) => ValueSources); // List of allowed value sources (must contain \"value\", \"field\", or both)\n comparator?: string | ((f: Field, operator: string) => boolean); // Determines which (other) fields to include in the list when the rule's valueSource is \"field\"\n className?: Classname; // Assigned to rules where this field is selected\n separator?: ReactNode; // Rendered between multiple value editors, e.g. when the operator is \"between\" or \"notBetween\"\n}\n```\n\nNotes:\n\n- `Field` extends the `Option` interface, described [below](#option-lists).\n- More information on `valueEditorType` is available [below](#value-editor) and in the [`ValueEditor` component documentation](./components/valueeditor)."
|
|
2825
2920
|
},
|
|
2826
2921
|
{
|
|
2827
|
-
"page":
|
|
2922
|
+
"page": 46,
|
|
2828
2923
|
"heading": "Rules and groups",
|
|
2829
2924
|
"anchor": "rules-and-groups",
|
|
2830
2925
|
"text": "## Rules and groups\n\n_For more information about the `Path` type, see [Path concepts](./tips/path)._\n\n```ts\ntype Path = number[];\n\ntype RuleType = {\n path?: Path;\n id?: string;\n disabled?: boolean;\n field: string;\n operator: string;\n value: any;\n valueSource?: ValueSource;\n};\n\ntype RuleGroupType = {\n path?: Path;\n id?: string;\n disabled?: boolean;\n combinator: string;\n rules: (RuleType | RuleGroupType)[];\n not?: boolean;\n};\n\ntype RuleGroupTypeIC = {\n path?: Path;\n id?: string;\n disabled?: boolean;\n rules: (RuleType | RuleGroupTypeIC | string)[]; // see note below\n not?: boolean;\n};\n\ntype RuleGroupTypeAny = RuleGroupType | RuleGroupTypeIC;\n\ntype RuleOrGroupArray = RuleGroupType['rules'] | RuleGroupTypeIC['rules'];\n```\n\n**Info:**\n\n`RuleGroupTypeIC` (see [independent combinators](./components/querybuilder#independent-combinators)) is _greatly_ simplified here for brevity. In reality, TypeScript enforces the following conditions:\n\n- All even indexes in the `rules` array must be of type `RuleType` or `RuleGroupTypeIC`\n- All odd indexes in the `rules` array must be of type `string`\n- 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`\n\nFor 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).\n\n```ts\nconst ruleGroupInvalid: RuleGroupTypeIC = {\n rules: ['and', { field: 'firstName', operator: '=', value: 'Steve' }],\n};\n```\n\nWe can resolve this by either removing the first element or inserting another rule before it:\n\n```ts\nconst ruleGroupValid1: RuleGroupTypeIC = {\n rules: [{ field: 'firstName', operator: '=', value: 'Steve' }],\n};\n\n// OR\n\nconst ruleGroupValid2: RuleGroupTypeIC = {\n rules: [\n { field: 'lastName', operator: '=', value: 'Vai' },\n 'and',\n { field: 'firstName', operator: '=', value: 'Steve' },\n ],\n};\n```"
|
|
2831
2926
|
},
|
|
2832
2927
|
{
|
|
2833
|
-
"page":
|
|
2928
|
+
"page": 46,
|
|
2834
2929
|
"heading": "Export",
|
|
2835
2930
|
"anchor": "export",
|
|
2836
2931
|
"text": "## Export\n\n```ts\ntype ExportFormat =\n | 'json'\n | 'sql'\n | 'json_without_ids'\n | 'parameterized'\n | 'parameterized_named'\n | 'mongodb'\n | 'mongodb_query'\n | 'cel'\n | 'jsonlogic'\n | 'spel'\n | 'elasticsearch'\n | 'jsonata'\n | 'natural_language'\n | 'ldap'\n | 'drizzle'\n | 'tanstack_db'\n | 'prisma'\n | 'sequelize'\n | 'cypher'\n | 'gql'\n | 'sparql'\n | 'gremlin'\n | 'diagnostics';\n\ninterface FormatQueryOptions {\n format?: ExportFormat;\n valueProcessor?: ValueProcessor;\n ruleProcessor?: RuleProcessor;\n quoteFieldNamesWith?: string | [string, string];\n fieldIdentifierSeparator?: string;\n quoteValuesWith?: string;\n validator?: QueryValidator;\n fields?: OptionList<Field>;\n getOperators?: (\n field: string,\n misc: { fieldData: FullField }\n ) => FlexibleOptionList<FullOperator> | null;\n fallbackExpression?: string;\n paramPrefix?: string;\n paramsKeepPrefix?: boolean;\n numberedParams?: boolean;\n parseNumbers?: ParseNumberMethod;\n placeholderFieldName?: string;\n placeholderOperatorName?: string;\n concatOperator?: string;\n preset?: SQLPreset;\n context?: Record<string, any>;\n}\n\ntype RuleProcessor = (rule: RuleType, options?: ValueProcessorOptions) => any;\n\ntype ValueProcessor = (field: string, operator: string, value: any) => string;\n\ninterface ValueProcessorOptions extends FormatQueryOptions {\n escapeQuotes?: boolean;\n fieldData?: Field;\n fieldParamNames?: Record<string, string[]>;\n getNextNamedParam?: (field: string) => string;\n wrapValueWith?: [string, string];\n}\n\ninterface ParameterizedSQL {\n sql: string;\n params: any[];\n}\n\ninterface ParameterizedNamedSQL {\n sql: string;\n params: { [p: string]: any };\n}\n\ntype ParseNumberMethod = boolean | 'enhanced' | 'native' | 'strict';\n```"
|
|
2837
2932
|
},
|
|
2838
2933
|
{
|
|
2839
|
-
"page":
|
|
2934
|
+
"page": 46,
|
|
2840
2935
|
"heading": "Import",
|
|
2841
2936
|
"anchor": "import",
|
|
2842
2937
|
"text": "## Import\n\n```ts\ninterface ParserCommonOptions {\n fields?: OptionList<Field>[] | Record<string, Field>;\n getValueSources?: (field: string, operator: string) => ValueSources;\n listsAsArrays?: boolean;\n independentCombinators?: boolean;\n}\n\ninterface ParseSQLOptions extends ParserCommonOptions {\n paramPrefix?: string;\n params?: any[] | Record<string, any>;\n}\n\ninterface ParseCELOptions extends ParserCommonOptions {\n customExpressionHandler?: (expr: CELExpression) => RuleType | RuleGroupType | null;\n}\n\ntype ParseSpELOptions = ParserCommonOptions;\n\ntype ParseJsonLogicOptions = ParserCommonOptions;\n\ninterface ParseMongoDbOptions extends ParserCommonOptions {\n preventOperatorNegation?: boolean;\n additionalOperators?: Record<\n string,\n (operator: string, value: any, otherOptions: ParserCommonOptions) => RuleType | RuleGroupType\n >;\n}\n```"
|
|
2843
2938
|
},
|
|
2844
2939
|
{
|
|
2845
|
-
"page":
|
|
2940
|
+
"page": 46,
|
|
2846
2941
|
"heading": "Validation",
|
|
2847
2942
|
"anchor": "validation",
|
|
2848
2943
|
"text": "## Validation\n\n```ts\ninterface ValidationResult {\n valid: boolean;\n reasons?: any[];\n}\n\ninterface ValidationMap {\n [id: string]: boolean | ValidationResult;\n}\n\ntype QueryValidator = (query: RuleGroupTypeAny) => boolean | ValidationMap;\n\ntype RuleValidator = (rule: RuleType) => boolean | ValidationResult;\n```"
|
|
2849
2944
|
},
|
|
2850
2945
|
{
|
|
2851
|
-
"page":
|
|
2946
|
+
"page": 46,
|
|
2852
2947
|
"heading": "Option lists",
|
|
2853
2948
|
"anchor": "option-lists",
|
|
2854
2949
|
"text": "## Option lists\n\n_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](./tips/option-lists)._\n\n```ts\ninterface Option {\n name: string;\n label: string;\n [x: string]: any;\n}\n\ninterface OptionGroup {\n label: string;\n options: Option[];\n}\n\ntype OptionList = Option[] | OptionGroup[];\n\ninterface Combinator extends Option {\n className?: Classname; // Assigned to groups where this combinator is selected\n}\n\ninterface Operator extends Option {\n arity?: number | 'unary' | 'binary' | 'ternary';\n className?: Classname; // Assigned to rules where this operator is selected\n}\n```"
|
|
2855
2950
|
},
|
|
2856
2951
|
{
|
|
2857
|
-
"page":
|
|
2952
|
+
"page": 46,
|
|
2858
2953
|
"heading": "Value editor",
|
|
2859
2954
|
"anchor": "value-editor",
|
|
2860
2955
|
"text": "## Value editor\n\nSee [`ValueEditor` component documentation here](./components/valueeditor).\n\n```ts\ntype ValueEditorType =\n | 'text'\n | 'select'\n | 'checkbox'\n | 'radio'\n | 'textarea'\n | 'multiselect'\n | 'date'\n | 'datetime-local'\n | 'time'\n | null;\n\ntype ValueSource = 'value' | 'field';\n\ntype ValueSources = ['value'] | ['value', 'field'] | ['field', 'value'] | ['field'];\n```"
|
|
2861
2956
|
},
|
|
2862
2957
|
{
|
|
2863
|
-
"page":
|
|
2958
|
+
"page": 46,
|
|
2864
2959
|
"heading": "Miscellaneous",
|
|
2865
2960
|
"anchor": "miscellaneous",
|
|
2866
2961
|
"text": "## Miscellaneous\n\n```ts\ninterface Schema {\n qbId: string;\n fields: OptionList<Field>;\n fieldMap: Record<string, Field>;\n classNames: Classnames;\n combinators: OptionList<Combinator>;\n controls: Controls;\n createRule(): RuleType;\n createRuleGroup(): RuleGroupTypeAny;\n dispatchQuery(query: RuleGroupTypeAny): void;\n getQuery(): RuleGroupTypeAny | undefined;\n getOperators(field: string): OptionList<Operator>;\n getValueEditorType(field: string, operator: string): ValueEditorType;\n getValueEditorSeparator(field: string, operator: string): ReactNode;\n getValueSources(field: string, operator: string): ValueSources;\n getInputType(field: string, operator: string): string | null;\n getValues(field: string, operator: string): OptionList;\n getRuleClassname(rule: RuleType): Classname;\n getRuleGroupClassname(ruleGroup: RuleGroupTypeAny): Classname;\n showCombinatorsBetweenRules: boolean;\n showNotToggle: boolean;\n showShiftActions: boolean;\n showCloneButtons: boolean;\n showLockButtons: boolean;\n autoSelectField: boolean;\n autoSelectOperator: boolean;\n addRuleToNewGroups: boolean;\n enableDragAndDrop: boolean;\n validationMap: ValidationMap;\n independentCombinators: boolean;\n listsAsArrays: boolean;\n parseNumbers: ParseNumbersMethod;\n disabledPaths: Path[];\n}\n\ninterface QueryActions {\n onGroupAdd(group: RuleGroupTypeAny, parentPath: Path, context?: any): void;\n onGroupRemove(path: Path): void;\n onPropChange(\n prop: Exclude<keyof RuleType | keyof RuleGroupType, 'id' | 'path'>,\n value: any,\n path: Path\n ): void;\n onRuleAdd(rule: RuleType, parentPath: Path, context?: any): void;\n onRuleRemove(path: Path): void;\n moveRule(oldPath: Path, newPath: Path, clone?: boolean): void;\n}\n```"
|
|
2867
2962
|
},
|
|
2868
2963
|
{
|
|
2869
|
-
"page":
|
|
2964
|
+
"page": 48,
|
|
2870
2965
|
"heading": "",
|
|
2871
2966
|
"anchor": "",
|
|
2872
|
-
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nUse the `formatQuery` function to export queries in various formats. The function has this signature:\n\n```ts\nfunction formatQuery(\n query: RuleGroupTypeAny,\n options?: ExportFormat | FormatQueryOptions\n): string | ParameterizedSQL | ParameterizedNamedSQL | RQBJsonLogic | Record<string, any>;\n```\n\n`formatQuery` converts query objects to these formats:\n\n- Formatted `JSON.stringify` result\n- Unformatted `JSON.stringify` result with all `id` and `path` properties removed\n- SQL `WHERE` clause\n - Parameterized with anonymous parameters\n - Parameterized with named parameters\n- ORM query objects for Drizzle, Prisma, Sequelize, and TanStack DB\n- MongoDB query object\n- ~~MongoDB query object as string~~ [_(deprecated)_](#mongodb)\n- Common Expression Language (CEL)\n- Spring Expression Language (SpEL)\n- JsonLogic\n- ElasticSearch\n- JSONata\n- LDAP\n- Natural language\n\nThe following sections use this example `query`:\n\n```ts\nconst query: RuleGroupType = {\n id: 'root',\n combinator: 'and',\n not: false,\n rules: [\n {\n id: 'rule1',\n field: 'firstName',\n operator: '=',\n value: 'Steve',\n },\n {\n id: 'rule2',\n field: 'lastName',\n operator: '=',\n value: 'Vai',\n },\n ],\n};\n```\n\n**Tip:**\n\nFor best results, use [default combinators and operators](./misc#defaults) or map custom ones to defaults with [`transformQuery`](./misc#transformquery).\n\n<details>\n<summary>More information...</summary>\n\n`formatQuery` accepts `RuleGroupTypeAny` queries but only guarantees correct processing of `DefaultRuleGroupTypeAny` queries.\n\nAll query `combinator` and `operator` properties must match [`defaultCombinators` or `defaultOperators`](./misc#defaults) names (case-insensitive). Use [`transformQuery`](./misc#transformquery) to map custom names to defaults before calling `formatQuery`.\n\nFor example, replacing the default \"between\" operator with `{ name: \"b/w\", label: \"b/w\" }` creates rules with `operator: \"b/w\"`. For this query:\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [{ \"field\": \"someNumber\", \"operator\": \"b/w\", \"value\": \"12,14\" }]\n}\n```\n\nTransform it using `transformQuery` with `operatorMap`:\n\n```ts\nconst newQuery = transformQuery(query, { operatorMap: { 'b/w': 'between' } });\n/*\n{\n \"combinator\": \"and\",\n \"rules\": [{ \"field\": \"someNumber\", \"operator\": \"between\", \"value\": \"12,14\" }]\n}\n*/\n```\n\nThe `newQuery` is ready for `formatQuery`, including special \"between\" operator handling.\n\n</details>"
|
|
2967
|
+
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nUse the `formatQuery` function to export queries in various formats. The function has this signature:\n\n```ts\nfunction formatQuery(\n query: RuleGroupTypeAny,\n options?: ExportFormat | FormatQueryOptions\n): string | ParameterizedSQL | ParameterizedNamedSQL | RQBJsonLogic | Record<string, any>;\n```\n\n`formatQuery` converts query objects to these formats:\n\n- Formatted `JSON.stringify` result\n- Unformatted `JSON.stringify` result with all `id` and `path` properties removed\n- SQL `WHERE` clause\n - Parameterized with anonymous parameters\n - Parameterized with named parameters\n- ORM query objects for Drizzle, Prisma, Sequelize, and TanStack DB\n- MongoDB query object\n- ~~MongoDB query object as string~~ [_(deprecated)_](#mongodb)\n- Common Expression Language (CEL)\n- Spring Expression Language (SpEL)\n- JsonLogic\n- ElasticSearch\n- Vector database metadata filters (Pinecone, Chroma, Qdrant, Weaviate, Milvus)\n- JSONata\n- LDAP\n- Natural language\n\nThe following sections use this example `query`:\n\n```ts\nconst query: RuleGroupType = {\n id: 'root',\n combinator: 'and',\n not: false,\n rules: [\n {\n id: 'rule1',\n field: 'firstName',\n operator: '=',\n value: 'Steve',\n },\n {\n id: 'rule2',\n field: 'lastName',\n operator: '=',\n value: 'Vai',\n },\n ],\n};\n```\n\n**Tip:**\n\nFor best results, use [default combinators and operators](./misc#defaults) or map custom ones to defaults with [`transformQuery`](./misc#transformquery).\n\n<details>\n<summary>More information...</summary>\n\n`formatQuery` accepts `RuleGroupTypeAny` queries but only guarantees correct processing of `DefaultRuleGroupTypeAny` queries.\n\nAll query `combinator` and `operator` properties must match [`defaultCombinators` or `defaultOperators`](./misc#defaults) names (case-insensitive). Use [`transformQuery`](./misc#transformquery) to map custom names to defaults before calling `formatQuery`.\n\nFor example, replacing the default \"between\" operator with `{ name: \"b/w\", label: \"b/w\" }` creates rules with `operator: \"b/w\"`. For this query:\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [{ \"field\": \"someNumber\", \"operator\": \"b/w\", \"value\": \"12,14\" }]\n}\n```\n\nTransform it using `transformQuery` with `operatorMap`:\n\n```ts\nconst newQuery = transformQuery(query, { operatorMap: { 'b/w': 'between' } });\n/*\n{\n \"combinator\": \"and\",\n \"rules\": [{ \"field\": \"someNumber\", \"operator\": \"between\", \"value\": \"12,14\" }]\n}\n*/\n```\n\nThe `newQuery` is ready for `formatQuery`, including special \"between\" operator handling.\n\n</details>"
|
|
2873
2968
|
},
|
|
2874
2969
|
{
|
|
2875
|
-
"page":
|
|
2970
|
+
"page": 48,
|
|
2876
2971
|
"heading": "Basic usage",
|
|
2877
2972
|
"anchor": "basic-usage",
|
|
2878
2973
|
"text": "## Basic usage"
|
|
2879
2974
|
},
|
|
2880
2975
|
{
|
|
2881
|
-
"page":
|
|
2976
|
+
"page": 48,
|
|
2882
2977
|
"heading": "Basic usage › JSON",
|
|
2883
2978
|
"anchor": "json",
|
|
2884
2979
|
"text": "### JSON\n\nExport the internal query representation (from `onQueryChange` callback) as formatted JSON:\n\n```ts\nformatQuery(query);\n// or\nformatQuery(query, 'json');\n```\n\nOutput is multi-line JSON with 2-space indentation:\n\n```ts\n`{\n \"id\": \"root\",\n \"combinator\": \"and\",\n \"not\": false,\n \"rules\": [\n {\n \"id\": \"rule1\",\n \"field\": \"firstName\",\n \"value\": \"Steve\",\n \"operator\": \"=\"\n },\n {\n \"id\": \"rule2\",\n \"field\": \"lastName\",\n \"value\": \"Vai\",\n \"operator\": \"=\"\n }\n ]\n}`;\n```"
|
|
2885
2980
|
},
|
|
2886
2981
|
{
|
|
2887
|
-
"page":
|
|
2982
|
+
"page": 48,
|
|
2888
2983
|
"heading": "Basic usage › JSON without IDs",
|
|
2889
2984
|
"anchor": "json-without-ids",
|
|
2890
2985
|
"text": "### JSON without IDs\n\nExport unformatted (single-line) JSON without `id` or `path` attributes using \"json_without_ids\". This format is useful for persistent storage:\n\n```ts\nformatQuery(query, 'json_without_ids');\n```\n\nOutput (string):\n\n```\n{\"combinator\":\"and\",\"not\":false,\"rules\":[{\"field\":\"firstName\",\"value\":\"Steve\",\"operator\":\"=\"},{\"field\":\"lastName\",\"value\":\"Vai\",\"operator\":\"=\"}]}\n```"
|
|
2891
2986
|
},
|
|
2892
2987
|
{
|
|
2893
|
-
"page":
|
|
2988
|
+
"page": 48,
|
|
2894
2989
|
"heading": "Basic usage › SQL",
|
|
2895
2990
|
"anchor": "sql",
|
|
2896
2991
|
"text": "### SQL\n\nExport 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.\n\n```ts\nformatQuery(query, 'sql');\n```\n\nOutput (string):\n\n```\n(firstName = 'Steve' and lastName = 'Vai')\n```\n\n#### Parameterized SQL\n\nExport SQL with bind variables instead of inline values using the \"parameterized\" format. This returns an object with `sql` and `params` properties:\n\n```ts\nformatQuery(query, 'parameterized');\n```\n\nOutput (JSON object):\n\n```json\n{\n \"sql\": \"(firstName = ? and lastName = ?)\",\n \"params\": [\"Steve\", \"Vai\"]\n}\n```\n\n#### Named parameters\n\nWhen 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:\n\n```ts\nformatQuery(query, 'parameterized_named');\n```\n\nOutput (JSON object):\n\n```json\n{\n \"sql\": \"(firstName = :firstName_1 and lastName = :lastName_1)\",\n \"params\": {\n \"firstName_1\": \"Steve\",\n \"lastName_1\": \"Vai\"\n }\n}\n```\n\nSee also: [`paramPrefix`](#parameter-prefix) and [generating parameter names](#generating-parameter-names)."
|
|
2897
2992
|
},
|
|
2898
2993
|
{
|
|
2899
|
-
"page":
|
|
2994
|
+
"page": 48,
|
|
2900
2995
|
"heading": "Basic usage › ORMs",
|
|
2901
2996
|
"anchor": "orms",
|
|
2902
2997
|
"text": "### ORMs\n\n#### Prisma ORM\n\nGenerate objects for Prisma ORM `where` properties using the \"prisma\" format:\n\n> _Note: Prisma does not support field-to-field comparisons, so rules with `valueSource: \"field\"` will always be invalid._\n\n```ts\nconst where = formatQuery(query, 'prisma');\n\nconsole.log(where);\n// { AND: [{ firstName: 'Steve' }, { lastName: 'Vai' }] }\n\nconst users = await prisma.users.findMany({ where });\n```\n\n#### Drizzle ORM\n\n##### Relational Queries API\n\nGenerate functions for Drizzle's [relational queries API](https://orm.drizzle.team/docs/rqb) `where` property:\n\n```ts\nconst where = formatQuery(query, 'drizzle');\n// typeof where === 'function'\n// where.length === 2\nconst results = db.query.users.findMany({ where });\n```\n\n##### Query Builder API\n\nFor Drizzle's [query builder API](https://orm.drizzle.team/docs/select), pass table definition and operators to the `formatQuery`-generated function:\n\n```ts\nimport { getOperators } from 'drizzle-orm';\n\nconst whereFn = formatQuery(query, 'drizzle');\nconst whereObj = whereFn(table, getOperators());\n\nconst query = db.select().from(table).where(whereObj);\n```\n\n**Tip:**\n\nQuery builder API objects work with other Drizzle operators, letting you add conditions not in the original query:\n\n```ts\nimport { and, ne, getOperators } from 'drizzle-orm';\n\n// Conditions from the React Query Builder query object:\nconst whereFn = formatQuery(query, 'drizzle');\nconst whereObj = whereFn(table, getOperators());\n\n// All conditions from the original query object _and_ `id != 123`:\nconst augmentedWhere = and(whereObj, ne(table.id, 123));\nconst query = db.select().from(table).where(augmentedWhere);\n```\n\n<details>\n\n<summary>`@react-querybuilder/drizzle` _(deprecated)_</summary>\n\nThe [`@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.\n\nTo achieve the same result, inline the following functions in your project:\n\n```ts\nimport type { RuleGroupProcessor, RuleProcessor } from '@react-querybuilder/core';\nimport {\n defaultRuleGroupProcessorDrizzle,\n defaultRuleProcessorDrizzle,\n} from '@react-querybuilder/core';\nimport type { Column, SQL, Table } from 'drizzle-orm';\nimport * as drizzleOperators from 'drizzle-orm';\nimport { getOperators } from 'drizzle-orm';\n\nexport const generateDrizzleRuleGroupProcessor =\n (columns: Record<string, Column> | Table): RuleGroupProcessor<SQL | undefined> =>\n (ruleGroup, options) =>\n defaultRuleGroupProcessorDrizzle(ruleGroup, options)(\n columns as Record<string, Column>,\n getOperators()\n );\n\nexport const generateDrizzleRuleProcessor =\n (table: Table | Record<string, Column>): RuleProcessor =>\n (rule, options) =>\n defaultRuleProcessorDrizzle(rule, { ...options, context: { table, drizzleOperators } });\n```\n\nUsage:\n\n```ts\nimport { sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { formatQuery } from 'react-querybuilder';\n\nconst db = drizzle(process.env.DB_FILE_NAME!);\n\nconst table = sqliteTable('musicians', {\n firstName: text(),\n lastName: text(),\n});\n\nconst ruleGroupProcessor = generateDrizzleRuleGroupProcessor(table);\n\n// Tip: `format` is not required when `ruleGroupProcessor` is provided\nconst where = formatQuery(query, { ruleGroupProcessor });\n\nconst query = db.select().from(table).where(where);\nconsole.log(query.toSQL());\n// {\n// sql: 'select \"firstName\", \"lastName\" from \"musicians\" where (\"musicians\".\"firstName\" = ? and \"musicians\".\"lastName\" = ?)',\n// params: ['Steve', 'Vai']\n// }\n\nconsole.log(query.all());\n// [{ firstName: 'Steve', lastName: 'Vai' }]\n```\n\n</details>\n\n#### Sequelize\n\nGenerate objects for Sequelize `findAll` `where` properties using the \"sequelize\" format. Requirements:\n\n- Sequelize uses `Symbol`s for operator keys, so they must be provided through the `context` option as `sequelizeOperators` (see example below).\n- If any rules have `valueSource: \"field\"`, then the Sequelize `col` function must be provided as `sequelizeCol`.\n- If any rules have `valueSource: \"field\"` and use one of the `doesNot*` operators, then the Sequelize `fn` function must be provided as `sequelizeFn`.\n\n```ts\nimport { col, fn, Op } from 'sequelize';\nconst where = formatQuery(query, {\n format: 'sequelize',\n context: { sequelizeOperators: Op, sequelizeCol: col, sequelizeFn: fn },\n});\n\nconst users = await Users.findAll({ where });\n```\n\n#### TanStack DB\n\nGenerate 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.\n\nPass the full `@tanstack/db` module or individual operators as `tanStackDbOperators`:\n\n```ts\nimport * as tsdb from '@tanstack/db';\n\nconst where = formatQuery(query, {\n format: 'tanstack_db',\n context: { tanStackDbOperators: tsdb },\n});\n\nconst results = useLiveQuery(q => q.from({ users: usersCollection }).where(where));\n```\n\nOr with cherry-picked operators:\n\n```ts\nimport { eq, gt, gte, lt, lte, like, inArray, isNull, not, and, or } from '@tanstack/db';\n\nconst where = formatQuery(query, {\n format: 'tanstack_db',\n context: {\n tanStackDbOperators: { eq, gt, gte, lt, lte, like, inArray, isNull, not, and, or },\n },\n});\n```\n\n**Tip:**\n\nTanStack 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(...))`.\n\n##### Joins (multi-collection queries)\n\nWhen 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).\n\n```ts\nimport * as tsdb from '@tanstack/db';\n\nconst query = {\n combinator: 'and',\n rules: [\n // Bare field → resolves to the primary collection (su)\n { field: 'firstName', operator: '=', value: 'Bruce' },\n // Dotted field → resolves to the nicknames collection (nn)\n { field: 'nn.nickname', operator: 'contains', value: 'Dark' },\n ],\n};\n\nconst where = formatQuery(query, {\n format: 'tanstack_db',\n context: { tanStackDbOperators: tsdb },\n});\n\nconst results = useLiveQuery(q =>\n q\n .from({ su: superUsersCollection })\n .leftJoin({ nn: nicknamesCollection }, refs => eq(refs.su.id, refs.nn.userId))\n .where(where)\n);\n```\n\n**Caution:**\n\nBare 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."
|
|
2903
2998
|
},
|
|
2904
2999
|
{
|
|
2905
|
-
"page":
|
|
3000
|
+
"page": 48,
|
|
2906
3001
|
"heading": "Basic usage › MongoDB",
|
|
2907
3002
|
"anchor": "mongodb",
|
|
2908
3003
|
"text": "### MongoDB\n\nGenerate MongoDB queries as JSON objects or strings. Use the \"mongodb_query\" format (recommended) for JSON objects. The \"mongodb\" format is the stringified version.\n\n**Info:**\n\nThe \"mongodb\" format was deprecated when the \"mongodb_query\" export format was introduced in version 8.1.0.\n\n```ts\nformatQuery(query, 'mongodb_query');\n```\n\nOutput (JSON object):\n\n```json\n{ \"$and\": [{ \"firstName\": \"Steve\" }, { \"lastName\": \"Vai\" }] }\n```"
|
|
2909
3004
|
},
|
|
2910
3005
|
{
|
|
2911
|
-
"page":
|
|
3006
|
+
"page": 48,
|
|
2912
3007
|
"heading": "Basic usage › Common Expression Language",
|
|
2913
3008
|
"anchor": "common-expression-language",
|
|
2914
3009
|
"text": "### Common Expression Language\n\nFor [Common Expression Language (CEL)](https://cel.dev) output, use the \"cel\" format.\n\n```ts\nformatQuery(query, 'cel');\n```\n\nOutput (string):\n\n```\nfirstName = \"Steve\" && lastName = \"Vai\"\n```"
|
|
2915
3010
|
},
|
|
2916
3011
|
{
|
|
2917
|
-
"page":
|
|
3012
|
+
"page": 48,
|
|
2918
3013
|
"heading": "Basic usage › Spring Expression Language",
|
|
2919
3014
|
"anchor": "spring-expression-language",
|
|
2920
3015
|
"text": "### Spring Expression Language\n\nFor [Spring Expression Language (SpEL)](https://docs.spring.io/spring-framework/reference/core/expressions.html) output, use the \"spel\" format.\n\n```ts\nformatQuery(query, 'spel');\n```\n\nOutput (string):\n\n```\nfirstName == 'Steve' and lastName == 'Vai'\n```"
|
|
2921
3016
|
},
|
|
2922
3017
|
{
|
|
2923
|
-
"page":
|
|
3018
|
+
"page": 48,
|
|
2924
3019
|
"heading": "Basic usage › JsonLogic",
|
|
2925
3020
|
"anchor": "jsonlogic",
|
|
2926
3021
|
"text": "### JsonLogic\n\nGenerate objects for JsonLogic `apply` function (see https://jsonlogic.com/):\n\n```ts\nformatQuery(query, 'jsonlogic');\n```\n\nOutput (JSON object):\n\n```json\n{ \"and\": [{ \"==\": [{ \"var\": \"firstName\" }, \"Steve\"] }, { \"==\": [{ \"var\": \"lastName\" }, \"Vai\"] }] }\n```\n\n**Tip:**\n\nRegister 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.\n\nLoop through `jsonLogicAdditionalOperators` entries for future-proof registration of any new custom operators:\n\n```ts\nimport { add_operation, apply } from 'json-logic-js';\nimport { jsonLogicAdditionalOperators } from 'react-querybuilder';\n\nfor (const [op, func] of Object.entries(jsonLogicAdditionalOperators)) {\n add_operation(op, func);\n}\n\napply({ startsWith: [{ var: 'firstName' }, 'Stev'] }, data);\n```"
|
|
2927
3022
|
},
|
|
2928
3023
|
{
|
|
2929
|
-
"page":
|
|
3024
|
+
"page": 48,
|
|
2930
3025
|
"heading": "Basic usage › ElasticSearch",
|
|
2931
3026
|
"anchor": "elasticsearch",
|
|
2932
3027
|
"text": "### ElasticSearch\n\nGenerate objects for [ElasticSearch](https://www.elastic.co/) processing:\n\n```ts\nformatQuery(query, 'elasticsearch');\n```\n\nOutput (JSON object):\n\n```json\n{ \"bool\": { \"must\": [{ \"term\": { \"firstName\": \"Steve\" } }, { \"term\": { \"lastName\": \"Vai\" } }] } }\n```"
|
|
2933
3028
|
},
|
|
2934
3029
|
{
|
|
2935
|
-
"page":
|
|
3030
|
+
"page": 48,
|
|
3031
|
+
"heading": "Basic usage › Vector databases",
|
|
3032
|
+
"anchor": "vector-databases",
|
|
3033
|
+
"text": "### Vector databases\n\n**Experimental:**\n\nSupport for vector database formats is experimental.\n\nThe \"pinecone\", \"chroma\", \"qdrant\", \"weaviate\", and \"milvus\" formats generate metadata filters for vector searches (e.g. RAG pipelines). Rules a format can't express are skipped. Inspect `formatCapabilities[format]` at runtime (e.g. to limit `operators` per target), or run the [\"diagnostics\" format](#diagnostics) with `diagnosticsTargetFormat` to list skipped rules as warnings.\n\n| Feature | [Pinecone](#pinecone) | [Chroma](#chroma) | [Qdrant](#qdrant) | [Weaviate](#weaviate) | [Milvus](#milvus) |\n| ---------------------- | --------------------- | ----------------- | ----------------- | --------------------- | ------------------------------------------------------------- |\n| `=` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `!=` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `<` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `>` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `<=` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `>=` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `contains` | – | – | ✓ | ✓ | ✓ |\n| `beginsWith` | – | – | ✓ | ✓ | ✓ |\n| `endsWith` | – | – | – | ✓ | ✓ |\n| `doesNotContain` | – | – | ✓ | ✓ | ✓ |\n| `doesNotBeginWith` | – | – | ✓ | ✓ | ✓ |\n| `doesNotEndWith` | – | – | – | ✓ | ✓ |\n| `null` | ✓ | – | ✓ | ✓ | ✓ |\n| `notNull` | ✓ | – | ✓ | ✓ | ✓ |\n| `in` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `notIn` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `between` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `notBetween` | ✓ | ✓ | ✓ | ✓ | ✓ |\n| `valueSource: \"field\"` | – | – | – | – | Only `=`, `!=`, `<`, `>`, `<=`, `>=`, `between`, `notBetween` |\n| Match modes | – | – | some, none | – | – |\n| `not` groups | De Morgan | De Morgan | native | native | native |\n\nCommon behavior:\n\n- Object formats return `{}` when no rules can be expressed (or the query is invalid); omit the filter param in that case. \"milvus\" returns `\"\"`.\n- \"pinecone\" and \"chroma\" have no group-level negation, so `not` groups are rewritten with De Morgan's laws (e.g. `NOT (a = 1 AND b > 2)` → `a != 1 OR b <= 2`). Records missing a field may match differently than with a true `NOT`.\n- Field names aren't escaped. Object formats emit them as plain keys/values (inert data), and \"milvus\" emits them verbatim. Whitelist field names either way.\n- `bigint` values are converted to `number` (precision loss beyond `Number.MAX_SAFE_INTEGER`), except in \"milvus\" which emits them as-is.\n- Fields with `valueEditorType` `\"checkbox\"`/`\"switch\"` emit boolean values.\n- Dates: \"qdrant\" and \"weaviate\" emit RFC 3339 strings for `date`/`datetime-local` fields. This requires field metadata via the `fields` option (e.g. `formatQuery(query, { format: \"qdrant\", fields: [{ name: \"createdAt\", label: \"Created\", inputType: \"date\" }] })`); without it, Qdrant skips non-numeric range rules and Weaviate emits `valueText` instead of `valueDate`. \"pinecone\", \"chroma\", and \"milvus\" have no date type; store dates as epoch milliseconds and use the `datetimeRuleProcessor<Format>` processors from [`@react-querybuilder/datetime`](../datetime), which convert date values (including relative dates) to epoch ms.\n\n#### Pinecone\n\nGenerate [Pinecone](https://docs.pinecone.io/guides/search/filter-by-metadata) metadata filters:\n\n```ts\nformatQuery(query, 'pinecone');\n```\n\nOutput (JSON object):\n\n```json\n{ \"$and\": [{ \"firstName\": { \"$eq\": \"Steve\" } }, { \"lastName\": { \"$eq\": \"Vai\" } }] }\n```\n\n- Pass the result as the `filter` param of a query.\n- `null`/`notNull` → `$exists: false`/`$exists: true`.\n- Range operators (incl. `between`) require numeric values; others are skipped. `in`/`notIn` accept strings and numbers only.\n- String operators, `valueSource: \"field\"`, and match modes are skipped.\n\n#### Chroma\n\nGenerate [Chroma](https://docs.trychroma.com/docs/querying-collections/metadata-filtering) `where` filters:\n\n```ts\nformatQuery(query, 'chroma');\n```\n\nOutput (JSON object):\n\n```json\n{ \"$and\": [{ \"firstName\": { \"$eq\": \"Steve\" } }, { \"lastName\": { \"$eq\": \"Vai\" } }] }\n```\n\n- Pass the result as the `where` param. Chroma rejects `where: {}`, so omit the param when the result is empty.\n- Each object holds one field and one operator, so `between` becomes `$and` of `$gte`/`$lte`. Single-child groups are unwrapped.\n- `in`/`notIn` values must share one type (string, integer, float, or boolean); mixed lists are skipped.\n- `null`/`notNull` are skipped (Chroma has no `$exists`). String operators are skipped; full-text `where_document` filters are out of scope.\n\n#### Qdrant\n\nGenerate [Qdrant](https://qdrant.tech/documentation/concepts/filtering/) `Filter` objects for metadata filtering in vector searches (e.g. RAG pipelines):\n\n```ts\nformatQuery(query, 'qdrant');\n```\n\nOutput (JSON object):\n\n```json\n{\n \"must\": [\n { \"key\": \"firstName\", \"match\": { \"value\": \"Steve\" } },\n { \"key\": \"lastName\", \"match\": { \"value\": \"Vai\" } }\n ]\n}\n```\n\nPass the result as the `filter` param of a search/query/scroll request. An empty object (`{}`) is returned when no rules can be expressed; omit the `filter` param in that case.\n\nRules Qdrant can't express are skipped. Inspect `formatCapabilities.qdrant` at runtime, or run the [\"diagnostics\" format](#diagnostics) with `diagnosticsTargetFormat: 'qdrant'` to list skipped rules as warnings.\n\n| Operator(s) | Output |\n| ------------------------------------------ | ------------------------------------------------------------------------- |\n| `=` / `!=` | `match.value` (`must_not` for `!=`); floats use `range: { gte, lte }` |\n| `<` `<=` `>` `>=` / `between` `notBetween` | `range` (numbers, or RFC 3339 strings for `date`/`datetime-local`) |\n| `in` / `notIn` | `match.any` / `match.except` (all strings or all integers), else `should` |\n| `null` / `notNull` | `is_null` / `must_not` `is_null` |\n| `contains` / `doesNotContain` | `match.text` (requires full-text index; token-based, **not** substring) |\n| `beginsWith` / `doesNotBeginWith` | `match.prefix` (Qdrant v1.19+; case-sensitive, untokenized, keywords) |\n| `endsWith` / `doesNotEndWith` | skipped |\n| `valueSource: \"field\"` | skipped |\n| match modes `some` / `none` | `nested` / `must_not` `nested` (arrays of objects only); others skipped |\n| groups | `and` → `must`, `or` → `should`, `not` → `must_not` |\n\nNotes:\n\n- Field names are emitted verbatim as `key` (Qdrant supports `a.b` and `a[].b` paths). No escaping is applied, so whitelist field names.\n- `bigint` values are converted to `number` (precision loss beyond `Number.MAX_SAFE_INTEGER`).\n- Fields with `valueEditorType` `\"checkbox\"`/`\"switch\"` emit boolean values.\n\n#### Weaviate\n\nGenerate [Weaviate](https://docs.weaviate.io/weaviate/api/graphql/filters) `where` filters (GraphQL/REST shape):\n\n```ts\nformatQuery(query, 'weaviate');\n```\n\nOutput (JSON object):\n\n```json\n{\n \"operator\": \"And\",\n \"operands\": [\n { \"operator\": \"Equal\", \"path\": [\"firstName\"], \"valueText\": \"Steve\" },\n { \"operator\": \"Equal\", \"path\": [\"lastName\"], \"valueText\": \"Vai\" }\n ]\n}\n```\n\n| Operator(s) | Output |\n| -------------------------------------- | ---------------------------------------------------------------------------- |\n| `=` `!=` `<` `<=` `>` `>=` | `Equal`, `NotEqual`, `LessThan`, … |\n| `contains` / `beginsWith` / `endsWith` | `Like` with `*v*` / `v*` / `*v`; negations wrap `Like` in `Not` |\n| `in` / `notIn` | `ContainsAny` / `ContainsNone` (array under the singular value key) |\n| `between` / `notBetween` | `And`(`GreaterThanEqual`, `LessThanEqual`) / `Or`(`LessThan`, `GreaterThan`) |\n| `null` / `notNull` | `IsNull` with `valueBoolean: true` / `false` |\n| groups | `And` / `Or` / `Not`; single-operand groups unwrapped |\n\n- The value key comes from the field: `date`/`datetime-local` → `valueDate` (RFC 3339), checkbox/switch → `valueBoolean`, numbers (`number`/`range`/`bigint` input types or `parseNumbers`) → `valueInt`/`valueNumber`, otherwise `valueText`.\n- `path` is `[field]`. Set `fieldIdentifierSeparator` to split cross-reference paths (e.g. `\"inPublication.Publication.name\"` with `\".\"` → `[\"inPublication\", \"Publication\", \"name\"]`).\n- `*` and `?` in `Like` values are wildcards. Weaviate has no escape syntax, so they pass through unchanged.\n- `IsNull` requires `invertedIndexConfig.indexNullState: true` on the collection.\n- `Not` and `ContainsNone` require Weaviate v1.33 or later.\n- For GraphQL queries, serialize the object as a GraphQL input literal (unquoted keys, `operator` values as enums).\n\n#### Milvus\n\nGenerate [Milvus](https://milvus.io/docs/boolean.md) boolean filter expressions:\n\n```ts\nformatQuery(query, 'milvus');\n```\n\nOutput:\n\n```text\nfirstName == \"Steve\" and lastName == \"Vai\"\n```\n\n- Pass the result as the `filter`/`expr` param. An empty string means no filter.\n- Strings are double-quoted with `\\` and `\"` escaped. String operators use `like` with `%`, `_`, and `\\` in values escaped; negations use `not (...)`.\n- `null`/`notNull` emit `is null`/`is not null`, which require Milvus 2.5 or later. On older versions, omit those operators.\n- `valueSource: \"field\"` is supported for comparison operators and `between`/`notBetween` (e.g. `a > b`). Milvus doesn't support field-to-field comparisons on JSON or ARRAY fields.\n- Field names are emitted verbatim, including JSON paths like `meta[\"k\"]`. Whitelist them.\n\n#### pgvector and LanceDB\n\nThese don't need a dedicated format:\n\n- [pgvector](https://github.com/pgvector/pgvector): use \"parameterized\" with `preset: 'postgresql'` and add the vector ordering yourself, e.g. `SELECT * FROM docs WHERE ${sql} ORDER BY embedding <-> $${params.length + 1} LIMIT 10`.\n- [LanceDB](https://lancedb.com/): pass \"sql\" output (DataFusion SQL) to `.where()`. Quote field names that need it with `quoteFieldNamesWith: '\"'`."
|
|
3034
|
+
},
|
|
3035
|
+
{
|
|
3036
|
+
"page": 48,
|
|
2936
3037
|
"heading": "Basic usage › JSONata",
|
|
2937
3038
|
"anchor": "jsonata",
|
|
2938
3039
|
"text": "### JSONata\n\nGenerate [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:\n\n```ts\nformatQuery(query, { format: 'jsonata', parseNumbers: true });\n```\n\nOutput (string):\n\n```\nfirstName = \"Steve\" and lastName = \"Vai\"\n```\n\n**Handling date values in JSONata:**\n\nReact Query Builder lacks standard date detection, so use `datetimeRuleProcessorJSONata` from [`@react-querybuilder/datetime`](../datetime#jsonata).\n\nFor more control, implement a custom rule processor (example below lacks error checking but provides a starting point):\n\n```ts\nconst customRuleProcessor: RuleProcessor = (rule, options) => {\n // `datatype` is a non-standard property of the field, used for this example only.\n // Replace this condition with your own logic to determine if the value is a date.\n if (options?.fieldData?.datatype === 'date') {\n return `$toMillis(${rule.field}) ${rule.operator} $toMillis(\"${rule.value}\")`;\n }\n return defaultRuleProcessorJSONata(rule, options);\n};\n```"
|
|
2939
3040
|
},
|
|
2940
3041
|
{
|
|
2941
|
-
"page":
|
|
3042
|
+
"page": 48,
|
|
2942
3043
|
"heading": "Basic usage › LDAP",
|
|
2943
3044
|
"anchor": "ldap",
|
|
2944
3045
|
"text": "### LDAP\n\nGenerate [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) filters:\n\n> _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._\n\n```ts\nformatQuery(query, 'ldap');\n```\n\nOutput (string):\n\n```\n(&(givenName=Steve)(sn=Vai))\n```"
|
|
2945
3046
|
},
|
|
2946
3047
|
{
|
|
2947
|
-
"page":
|
|
3048
|
+
"page": 48,
|
|
2948
3049
|
"heading": "Basic usage › Natural language",
|
|
2949
3050
|
"anchor": "natural-language",
|
|
2950
3051
|
"text": "### Natural language\n\nGenerate natural language queries using \"natural_language\" format. Use `getOperators` and `fields` options to render labels instead of values. See [i18n options](#internationalization):\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n parseNumbers: true,\n getOperators: () => defaultOperators,\n fields: [\n { value: 'firstName', label: 'First Name' },\n { value: 'lastName', label: 'Last Name' },\n { value: 'age', label: 'Age' },\n ],\n});\n```\n\nOutput (string):\n\n```\nFirst Name is 'Steve', and Last Name is \"Vai\", and Age is between 26 and 52\n```"
|
|
2951
3052
|
},
|
|
2952
3053
|
{
|
|
2953
|
-
"page":
|
|
3054
|
+
"page": 48,
|
|
2954
3055
|
"heading": "Basic usage › Cypher",
|
|
2955
3056
|
"anchor": "cypher",
|
|
2956
3057
|
"text": "### Cypher\n\nGenerate [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.\n\n```ts\nformatQuery(query, 'cypher');\n// or\nformatQuery(query, 'gql');\n```\n\nOutput (string):\n\n```\nn.firstName = 'Steve' AND n.lastName = 'Vai'\n```"
|
|
2957
3058
|
},
|
|
2958
3059
|
{
|
|
2959
|
-
"page":
|
|
3060
|
+
"page": 48,
|
|
2960
3061
|
"heading": "Basic usage › SPARQL",
|
|
2961
3062
|
"anchor": "sparql",
|
|
2962
3063
|
"text": "### SPARQL\n\nGenerate [SPARQL](https://www.w3.org/TR/sparql11-query/) `FILTER` expressions using the \"sparql\" format:\n\n```ts\nformatQuery(query, 'sparql');\n```\n\nOutput (string):\n\n```\n?firstName = \"Steve\" && ?lastName = \"Vai\"\n```"
|
|
2963
3064
|
},
|
|
2964
3065
|
{
|
|
2965
|
-
"page":
|
|
3066
|
+
"page": 48,
|
|
2966
3067
|
"heading": "Basic usage › Gremlin",
|
|
2967
3068
|
"anchor": "gremlin",
|
|
2968
3069
|
"text": "### Gremlin\n\nGenerate [Apache TinkerPop Gremlin](https://tinkerpop.apache.org/) `.has()` steps using the \"gremlin\" format:\n\n```ts\nformatQuery(query, 'gremlin');\n```\n\nOutput (string):\n\n```\n.has('firstName', 'Steve').has('lastName', 'Vai')\n```"
|
|
2969
3070
|
},
|
|
2970
3071
|
{
|
|
2971
|
-
"page":
|
|
3072
|
+
"page": 48,
|
|
2972
3073
|
"heading": "Basic usage › react-awesome-query-builder",
|
|
2973
3074
|
"anchor": "react-awesome-query-builder",
|
|
2974
3075
|
"text": "### react-awesome-query-builder\n\nGenerate 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()`.\n\nThis 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.\n\n```bash npm2yarn\nnpm i @react-querybuilder/migrate-raqb\n```\n\n```ts\nimport { Utils } from '@react-awesome-query-builder/core';\nimport { formatRAQB } from '@react-querybuilder/migrate-raqb';\n\nconst jsonTree = formatRAQB(query, { fields });\nconst immutableTree = Utils.checkTree(Utils.loadTree(jsonTree), config);\n```\n\nThis is the inverse of [`parseRAQB`](./import#react-awesome-query-builder). See [Migrating from react-awesome-query-builder](../tips/migrate-from-raqb) for the full concept mapping.\n\nOutput (object):\n\n```json\n{\n \"type\": \"group\",\n \"properties\": { \"conjunction\": \"AND\", \"not\": false },\n \"children1\": [\n {\n \"type\": \"rule\",\n \"properties\": {\n \"field\": \"firstName\",\n \"operator\": \"equal\",\n \"value\": [\"Steve\"],\n \"valueSrc\": [\"value\"]\n }\n },\n {\n \"type\": \"rule\",\n \"properties\": {\n \"field\": \"lastName\",\n \"operator\": \"equal\",\n \"value\": [\"Vai\"],\n \"valueSrc\": [\"value\"]\n }\n }\n ]\n}\n```\n\nA companion function, `formatRAQBFields`, converts an RQB `fields` array to the `fields` section of an RAQB `Config`:\n\n```ts\nimport { formatRAQBFields } from '@react-querybuilder/migrate-raqb';\n\nconst config = { ...BasicConfig, fields: formatRAQBFields(fields) };\n```\n\n`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).\n\n```ts\nconst jsonTree = formatRAQB(query, { fields, raqbFieldSeparator: '.', raqbValueTypes: true });\n```\n\n| Option | Default | Description |\n| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------ |\n| `raqbOperatorMap` | `{}` | Additional/overriding RQB-to-RAQB operator mappings, keyed by RQB operator name. |\n| `raqbFunctionMap` | `{}` | Additional/overriding expression-function-to-RAQB function name mappings. |\n| `raqbFuncArgOrder` | `{}` | Argument names per RAQB function name. Defaults cover RAQB's built-ins; others get `arg0`, `arg1`, ... |\n| `raqbFieldSeparator` | `\".\"` | Separator used to qualify sub-query rule fields with their parent `!group` field name. |\n| `raqbRelativeDateTimes` | `true` | Convert relative date/time values to RAQB's built-in date/time functions. |\n| `raqbValueTypes` | `false` | Emit `valueType` entries inferred from each field's `inputType`/`valueEditorType`. |\n\nTo 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.\n\n```ts\nimport { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';\n\nconst jsonTree = formatQuery(query, {\n ruleGroupProcessor: defaultRuleGroupProcessorRAQB,\n fields,\n context: { raqbValueTypes: true },\n fallbackExpression: raqbFallback as unknown as string,\n});\n```\n\n**Note:**\n\nRAQB'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.\n\nRAQB 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](../tips/migrate-from-raqb#converting-back-to-raqb) for the full list."
|
|
2975
3076
|
},
|
|
2976
3077
|
{
|
|
2977
|
-
"page":
|
|
3078
|
+
"page": 48,
|
|
2978
3079
|
"heading": "Basic usage › Diagnostics",
|
|
2979
3080
|
"anchor": "diagnostics",
|
|
2980
|
-
"text": "### Diagnostics\n\nGenerate 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.\n\n```ts\nconst result = formatQuery(query, {\n format: 'diagnostics',\n fields: [\n {\n name: 'firstName',\n label: 'First Name',\n validator: r => (r.value ? true : { valid: false, reasons: ['Value is required'] }),\n },\n { name: 'age', label: 'Age', inputType: 'number' },\n ],\n});\n```\n\nOutput (object):\n\n```json\n{\n \"query\": {\n \"combinator\": \"and\",\n \"valid\": false,\n \"path\": [],\n \"level\": 0,\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"\",\n \"valid\": false,\n \"reasons\": [\"Value is required\"],\n \"path\": [0],\n \"level\": 1\n },\n {\n \"field\": \"age\",\n \"operator\": \">\",\n \"value\": 26,\n \"valid\": true,\n \"path\": [1],\n \"level\": 1\n }\n ]\n },\n \"diagnostics\": [\n {\n \"id\": \"r-1\",\n \"path\": [0],\n \"code\": \"CUSTOM_VALIDATOR\",\n \"message\": \"Invalid: Value is required\",\n \"source\": \"field-validator\"\n }\n ],\n \"stats\": {\n \"totalRules\": 2,\n \"totalGroups\": 1,\n \"validRules\": 1,\n \"invalidRules\": 1,\n \"validGroups\": 0,\n \"invalidGroups\": 1\n },\n \"fieldSummary\": {\n \"firstName\": { \"ruleCount\": 1, \"invalidCount\": 1 },\n \"age\": { \"ruleCount\": 1, \"invalidCount\": 0 }\n }\n}\n```\n\n#### Annotated query tree\n\nEvery rule and group in `result.query` includes:\n\n- `valid` — whether the node passed all checks\n- `reasons` — optional array of reasons (from validators)\n- `path` — the position of the node in the tree (e.g., `[1, 0]`)\n- `level` — the nesting depth (`path.length`)\n\nA rule is considered invalid if any of the following are true:\n\n- The rule is `muted`\n- The rule fails validation via the `validator` option or a field-level `validator`\n- The `field`, `operator`, or `value` matches its respective placeholder name\n\nThe root-level `valid` property is `true` only when the group itself is valid _and_ all descendant rules and groups are valid. Some diagnostics (e.g. `INVALID_OPERATOR`) don't affect `valid`, so before executing a query, reject any `diagnostics` you consider actionable and enforce a server-side whitelist of fields and operators.\n\n#### Flat diagnostics array\n\n`result.diagnostics` is a flat array of `DiagnosticEntry` objects, each with an `id`, `path`, `code`, `message`, and `source`. Diagnostic codes include:\n\n| Code | Source | Description |\n| ---------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| `PLACEHOLDER_FIELD` | `placeholder` | Rule has a placeholder field name |\n| `PLACEHOLDER_OPERATOR` | `placeholder` | Rule has a placeholder operator name |\n| `PLACEHOLDER_VALUE` | `placeholder` | Rule has a placeholder value |\n| `MUTED` | `muted` | Rule or group is muted |\n| `CUSTOM_VALIDATOR` | `query-validator` / `field-validator` | Failed a custom validator |\n| `UNDEFINED_FIELD` | `field-check` | Rule references a field not in the `fields` config |\n| `UNREFERENCED_FIELD` | `field-check` | A field in the config is not used by any rule |\n| `VALUE_TYPE_MISMATCH` | `type-check` | Value is incompatible with the field's `inputType`, or isn't a boolean for a `checkbox`/`switch` `valueEditorType` |\n| `INVALID_OPERATOR` | `field-check` | Operator isn't in the field's `operators` (only checked when the field defines `operators`) |\n| `INVALID_VALUE_ARITY` | `type-check` | `between`/`notBetween` value doesn't have exactly 2 items, or `in`/`notIn` value is empty |\n| `VALUE_NOT_IN_OPTIONS` | `type-check` | Value isn't in the field's `values` (unless `valueEditorType` is `text`/`textarea`) |\n| `INVALID_STRUCTURE` | `structure-check` | An independent combinator group's `rules` don't alternate rule/group and combinator, or a standard group's `rules` contain a combinator string |\n\nAll diagnostics except `INVALID_STRUCTURE` and the placeholder, muted, and validator checks are only produced when a `fields` config is provided. The `INVALID_OPERATOR`, `INVALID_VALUE_ARITY`, `VALUE_NOT_IN_OPTIONS`, `VALUE_TYPE_MISMATCH`, and `INVALID_STRUCTURE` diagnostics don't affect `valid` or `stats`. Operator, arity, `values`, and boolean checks are skipped for muted rules. Arity, `values`, and boolean checks are also skipped for unary operators (`null`/`notNull`, or `arity: 'unary'` in `field.operators`) and placeholder values, and `values`/boolean checks are skipped for `valueSource: 'field'`.\n\n**Tip:**\n\n`diagnostics` covers what a [JSON Schema from `getQueryJsonSchema`](./json-schema) can't express, so use it to check LLM-generated queries.\n\n#### Stats and field summary\n\n`result.stats` provides aggregate counts: `totalRules`, `totalGroups`, `validRules`, `invalidRules`, `validGroups`, `invalidGroups`.\n\n`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)."
|
|
3081
|
+
"text": "### Diagnostics\n\nGenerate 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.\n\n```ts\nconst result = formatQuery(query, {\n format: 'diagnostics',\n fields: [\n {\n name: 'firstName',\n label: 'First Name',\n validator: r => (r.value ? true : { valid: false, reasons: ['Value is required'] }),\n },\n { name: 'age', label: 'Age', inputType: 'number' },\n ],\n});\n```\n\nOutput (object):\n\n```json\n{\n \"query\": {\n \"combinator\": \"and\",\n \"valid\": false,\n \"path\": [],\n \"level\": 0,\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"\",\n \"valid\": false,\n \"reasons\": [\"Value is required\"],\n \"path\": [0],\n \"level\": 1\n },\n {\n \"field\": \"age\",\n \"operator\": \">\",\n \"value\": 26,\n \"valid\": true,\n \"path\": [1],\n \"level\": 1\n }\n ]\n },\n \"diagnostics\": [\n {\n \"id\": \"r-1\",\n \"path\": [0],\n \"code\": \"CUSTOM_VALIDATOR\",\n \"message\": \"Invalid: Value is required\",\n \"source\": \"field-validator\"\n }\n ],\n \"stats\": {\n \"totalRules\": 2,\n \"totalGroups\": 1,\n \"validRules\": 1,\n \"invalidRules\": 1,\n \"validGroups\": 0,\n \"invalidGroups\": 1\n },\n \"fieldSummary\": {\n \"firstName\": { \"ruleCount\": 1, \"invalidCount\": 1 },\n \"age\": { \"ruleCount\": 1, \"invalidCount\": 0 }\n }\n}\n```\n\n#### Annotated query tree\n\nEvery rule and group in `result.query` includes:\n\n- `valid` — whether the node passed all checks\n- `reasons` — optional array of reasons (from validators)\n- `path` — the position of the node in the tree (e.g., `[1, 0]`)\n- `level` — the nesting depth (`path.length`)\n\nA rule is considered invalid if any of the following are true:\n\n- The rule is `muted`\n- The rule fails validation via the `validator` option or a field-level `validator`\n- The `field`, `operator`, or `value` matches its respective placeholder name\n\nThe root-level `valid` property is `true` only when the group itself is valid _and_ all descendant rules and groups are valid. Some diagnostics (e.g. `INVALID_OPERATOR`) don't affect `valid`, so before executing a query, reject any `diagnostics` you consider actionable and enforce a server-side whitelist of fields and operators.\n\n#### Flat diagnostics array\n\n`result.diagnostics` is a flat array of `DiagnosticEntry` objects, each with an `id`, `path`, `code`, `message`, and `source`. Diagnostic codes include:\n\n| Code | Source | Description |\n| ---------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n| `PLACEHOLDER_FIELD` | `placeholder` | Rule has a placeholder field name |\n| `PLACEHOLDER_OPERATOR` | `placeholder` | Rule has a placeholder operator name |\n| `PLACEHOLDER_VALUE` | `placeholder` | Rule has a placeholder value |\n| `MUTED` | `muted` | Rule or group is muted |\n| `CUSTOM_VALIDATOR` | `query-validator` / `field-validator` | Failed a custom validator |\n| `UNDEFINED_FIELD` | `field-check` | Rule references a field not in the `fields` config |\n| `UNREFERENCED_FIELD` | `field-check` | A field in the config is not used by any rule |\n| `VALUE_TYPE_MISMATCH` | `type-check` | Value is incompatible with the field's `inputType`, or isn't a boolean for a `checkbox`/`switch` `valueEditorType` |\n| `INVALID_OPERATOR` | `field-check` | Operator isn't in the field's `operators` (only checked when the field defines `operators`) |\n| `INVALID_VALUE_ARITY` | `type-check` | `between`/`notBetween` value doesn't have exactly 2 items, or `in`/`notIn` value is empty |\n| `INVALID_VALUE` | `type-check` | `matches`/`doesNotMatch` value isn't a valid regular expression (rule is invalid and skipped by all formats) |\n| `VALUE_NOT_IN_OPTIONS` | `type-check` | Value isn't in the field's `values` (unless `valueEditorType` is `text`/`textarea`) |\n| `INVALID_STRUCTURE` | `structure-check` | An independent combinator group's `rules` don't alternate rule/group and combinator, or a standard group's `rules` contain a combinator string |\n\nAll diagnostics except `INVALID_STRUCTURE` and the placeholder, muted, and validator checks are only produced when a `fields` config is provided. The `INVALID_OPERATOR`, `INVALID_VALUE_ARITY`, `VALUE_NOT_IN_OPTIONS`, `VALUE_TYPE_MISMATCH`, and `INVALID_STRUCTURE` diagnostics don't affect `valid` or `stats`. Operator, arity, `values`, and boolean checks are skipped for muted rules. Arity, `values`, and boolean checks are also skipped for unary operators (`null`/`notNull`, or `arity: 'unary'` in `field.operators`) and placeholder values, and `values`/boolean checks are skipped for `valueSource: 'field'`.\n\n**Tip:**\n\n`diagnostics` covers what a [JSON Schema from `getQueryJsonSchema`](./json-schema) can't express, so use it to check LLM-generated queries.\n\n#### Target format check\n\nSet `diagnosticsTargetFormat` to a format with a `formatCapabilities` entry (currently `\"pinecone\"`, `\"chroma\"`, `\"qdrant\"`, `\"weaviate\"`, and `\"milvus\"`) to report rules that format would skip. These entries have `source: \"format-check\"` and `severity: \"warning\"`, and never affect `valid` or `stats`.\n\n| Code | Description |\n| ------------------------------------- | -------------------------------------------- |\n| `UNSUPPORTED_OPERATOR_FOR_FORMAT` | Operator can't be expressed in the target |\n| `UNSUPPORTED_VALUE_SOURCE_FOR_FORMAT` | `valueSource: \"field\"` unsupported by target |\n| `UNSUPPORTED_MATCH_MODE_FOR_FORMAT` | Match mode unsupported by target |\n\nFor targets without native group negation, rules under an odd number of `not` groups are checked after De Morgan push-down (e.g. `beginsWith` → `doesNotBeginWith`), and the message notes the original operator.\n\n#### Stats and field summary\n\n`result.stats` provides aggregate counts: `totalRules`, `totalGroups`, `validRules`, `invalidRules`, `validGroups`, `invalidGroups`.\n\n`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)."
|
|
2981
3082
|
},
|
|
2982
3083
|
{
|
|
2983
|
-
"page":
|
|
3084
|
+
"page": 48,
|
|
3085
|
+
"heading": "Basic usage › Regular expressions",
|
|
3086
|
+
"anchor": "regular-expressions",
|
|
3087
|
+
"text": "### Regular expressions\n\nThe opt-in `matches`/`doesNotMatch` operators (see [Regular expression operators](../tips/managing-operators#regular-expression-operators)) use unanchored \"find\" semantics in every format: a rule matches if the pattern is found anywhere in the field value. Formats whose native regex operator requires a full-string match wrap the pattern as `.*(?:pattern).*`. Flags come from the rule's `regexFlags` property; flags a format can't express are dropped, and `g`/`y` are always ignored. Rules with invalid patterns (per JavaScript's `RegExp`) are skipped.\n\n| Format | `matches` output | Flags |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------- |\n| `jsonata` | `$contains(f, /pattern/flags)` | `i`, `m` |\n| `mongodb_query`/`mongodb` | `{ f: { $regex: 'pattern', $options: 'flags' } }` | native |\n| `spel` | `f matches '(?i).*(?:pattern).*'` | inline |\n| `cel` | `f.matches(\"(?i)pattern\")` (RE2) | inline |\n| `elasticsearch` | `regexp` query (Lucene syntax), wrapped in `.*(…).*` | `case_insensitive` for `i` |\n| `sql`/`parameterized*` | Per `preset`: PostgreSQL `~`/`~*`, MySQL/Oracle `REGEXP_LIKE`, SQLite `REGEXP` | per dialect |\n| `sequelize` | `Op.regexp`/`Op.iRegexp` | `i` |\n| `jsonlogic` | custom `regex` operation (register `jsonLogicRegexOperator`) | third argument |\n| `cypher`/`gql` | `f =~ '(?i).*(?:pattern).*'` | inline |\n| `gremlin` | `regex('(?i)pattern')` (`TextP.regex`) | inline |\n| `sparql` | `REGEX(?f, \"pattern\", \"flags\")` | native |\n| `natural_language` | `f matches regular expression 'pattern' with flags 'i'` | described |\n| others (LDAP, Prisma, TanStack DB, vector DBs, ANSI/MSSQL SQL) | falls back to `contains`/`doesNotContain` with the pattern as a literal | n/a |\n\n**Caution:**\n\nPatterns are passed through as-is. Regex dialects differ between targets (JavaScript, PCRE, POSIX, Java, RE2, Lucene, XPath), so a pattern that works in one may fail or behave differently in another. Notes:\n\n- SQLite has no built-in `REGEXP` function; register one (e.g. the sqlean `regexp` extension) before running the output. Flags are inlined as `(?i)`.\n- MySQL's plain `REGEXP` (used when there are no flags) follows the column collation, so it's case-insensitive on `_ci` collations.\n- PostgreSQL only gets the `i` flag (`~*`/`!~*`); `m`/`s` are dropped.\n- ElasticSearch uses Lucene regex syntax, which has no `\\d`-style classes or inline flags.\n\nSee the exact output for each format in the tests and the `defaultRuleProcessor*` source; some details (e.g. negation style) vary by format."
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
"page": 48,
|
|
2984
3091
|
"heading": "Configuration",
|
|
2985
3092
|
"anchor": "configuration",
|
|
2986
3093
|
"text": "## Configuration\n\nPass an object as the second argument for fine-grained output control:"
|
|
2987
3094
|
},
|
|
2988
3095
|
{
|
|
2989
|
-
"page":
|
|
3096
|
+
"page": 48,
|
|
2990
3097
|
"heading": "Configuration › Parse numbers",
|
|
2991
3098
|
"anchor": "parse-numbers",
|
|
2992
3099
|
"text": "### Parse numbers\n\nRender values as numbers instead of quoted strings using `parseNumbers: true`. See [Number parsing](./misc#number-parsing) for details.\n\n#### Preserve value order\n\n`formatQuery` sorts \"between\"/\"notBetween\" values in ascending order when `parseNumbers` renders them as numbers. Disable with `preserveValueOrder`:\n\n```ts\nconst query = {\n rules: [{ field: 'age', operator: 'between', value: [30, 20] }],\n};\n\nformatQuery(query, { format: 'sql', parseNumbers: true });\n/*\n\"(age between 20 and 30)\"\n*/\n\nformatQuery(query, { format: 'sql', parseNumbers: true, preserveValueOrder: true });\n/*\n\"(age between 30 and 20)\"\n*/\n```\n\n**Caution:**\n\nThis 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.\n\n`formatQuery` assumes users mean \"X is between points Y and Z\" regardless of direction."
|
|
2993
3100
|
},
|
|
2994
3101
|
{
|
|
2995
|
-
"page":
|
|
3102
|
+
"page": 48,
|
|
2996
3103
|
"heading": "Configuration › Rule processor",
|
|
2997
3104
|
"anchor": "rule-processor",
|
|
2998
3105
|
"text": "### Rule processor\n\nCustomize individual rule output using `ruleProcessor`. Only validated rules reach this function:\n\n```ts\nruleProcessor(rule, { escapeQuotes, fieldData, ...otherOptions });\n```\n\nArguments: `RuleType` object and `ValueProcessorOptions` object with `escapeQuotes` (true for string values, false for field names), `fieldData` (corresponding `Field` object), and other `formatQuery` options.\n\nThe default rule processors for each format are available as exports from `react-querybuilder`:\n\n- `defaultRuleProcessorCEL`\n- `defaultRuleProcessorElasticSearch`\n- `defaultRuleProcessorJSONata`\n- `defaultRuleProcessorJsonLogic`\n- `defaultRuleProcessorMongoDB`\n- `defaultRuleProcessorMongoDBQuery`\n- `defaultRuleProcessorNL`\n- `defaultRuleProcessorSpEL`\n- `defaultRuleProcessorSQL`\n- `defaultRuleProcessorParameterized`\n- `defaultRuleProcessorTanStackDB`\n\nRefer to the source code to determine the appropriate return type for custom rule processors.\n\nUse the appropriate default rule processor as a fallback so your custom processor doesn't cover all cases:\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n not: false,\n rules: [\n { field: 'firstName', operator: 'has', value: 'S' },\n // non-standard operator ^^^^^\n { field: 'lastName', operator: '=', value: 'Vai' },\n ],\n};\n\nconst customRuleProcessor: RuleProcessor = (rule, options) => {\n // The \"has\" operator is not handled by the default processor\n if (rule.operator === 'has') {\n return { in: [rule.value, { var: rule.field }] };\n }\n\n // Defer to the default processor for all other operators\n return defaultRuleProcessorJsonLogic(rule, options);\n};\n\nformatQuery(query, { format: 'jsonlogic', ruleProcessor: customRuleProcessor });\n/*\n{\n and: [\n { in: [\"S\", { var: \"firstName\" }] },\n { \"==\": [{ var: \"lastName\" }, \"Vai\"] }\n ]\n}\n*/\n```\n\nThis SQL example (using Oracle syntax) demonstrates the generation of a case-insensitive condition:\n\n```ts\n// `query` is the same as in the previous example\n\nconst customRuleProcessor: RuleProcessor = (rule, options) => {\n if (rule.operator === 'has') {\n return `UPPER(${rule.field}) LIKE UPPER('%${rule.value}%')`;\n }\n\n return defaultRuleProcessorSQL(rule, options);\n};\n\nformatQuery(query, { format: 'sql', ruleProcessor: customRuleProcessor });\n/*\n\"(UPPER(firstName) LIKE UPPER('%S%') and lastName = 'Vai')\"\n ^------------custom--------------^ ^------default-----^\n*/\n```\n\n#### Generating parameter names\n\nThe \"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.\n\n```ts\nconst customRuleProcessor: RuleProcessor = (rule, options) => {\n if (rule.operator === 'has') {\n // TIP: `getNextNamedParam` can be called multiple times in case your SQL\n // requires multiple unique parameters (e.g., in a \"between\" condition).\n // Each call will generate a new name.\n const paramName = options.getNextNamedParam!(rule.field);\n return {\n sql: `UPPER(${rule.field}) LIKE UPPER('%' || ${options.paramPrefix}${paramName} || '%')`,\n params: { [paramName]: rule.value },\n };\n }\n\n return defaultRuleProcessorSQLParameterized(rule, options);\n};\n\nformatQuery(query, { format: 'parameterized_named', ruleProcessor: customRuleProcessor });\n/*\n{\n sql: \"(UPPER(firstName) LIKE UPPER('%' || :firstName_1 || '%') and lastName = :lastName_1)\",\n params: {\n firstName_1: \"S\",\n lastName_1: \"Vai\"\n }\n}\n*/\n```"
|
|
2999
3106
|
},
|
|
3000
3107
|
{
|
|
3001
|
-
"page":
|
|
3108
|
+
"page": 48,
|
|
3002
3109
|
"heading": "Configuration › Value processor",
|
|
3003
3110
|
"anchor": "value-processor",
|
|
3004
3111
|
"text": "### Value processor\n\n`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.\n\n**Tip:**\n\nFor all formats except \"sql\", `valueProcessor` is a synonym for `ruleProcessor`. Use `ruleProcessor` unless exporting SQL and only customizing the value portion.\n\n```ts\n// `query` is the same as in the previous example\n\nconst customValueProcessor: ValueProcessorByRule = (rule, options) => {\n if (rule.operator === 'has') {\n return `'%${rule.value}%'`;\n }\n\n return defaultValueProcessorByRule(rule, options);\n};\n\nformatQuery(query, { format: 'sql', valueProcessor: customValueProcessor });\n/*\n\"(firstName like '%S%' and lastName = 'Vai')\"\n ^---default---^ ^---^-custom ^--default--^\n*/\n```\n\n#### Legacy `valueProcessor` behavior\n\n**Caution:**\n\nThe 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.\n\nIf the `valueProcessor` function accepts three or more arguments (excluding those with default values), it's called like this:\n\n```ts\nvalueProcessor(field, operator, value, valueSource);\n```\n\nNo options or additional rule properties are passed as arguments. This prevents `formatQuery` from setting the `escapeQuotes` option, among other problems.\n\nThis legacy behavior is documented for completeness but not recommended.\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n not: false,\n rules: [\n { field: 'instrument', operator: 'in', value: ['Guitar', 'Vocals'] },\n { field: 'lastName', operator: '=', value: 'Vai' },\n ],\n};\n\nconst customValueProcessor = (field, operator, value) => {\n if (operator === 'in') {\n // Assuming `value` is an array, such as from a multi-select\n return `(${value.map(v => `'${v.trim()}'`).join(',')})`;\n }\n\n return defaultValueProcessor(field, operator, value);\n};\n\nformatQuery(query, { format: 'sql', valueProcessor: customValueProcessor });\n/*\n\"(instrument in ('Guitar','Vocals') and lastName = 'Vai')\"\n*/\n```\n\nDefault value processors using the legacy signature are available for some query language formats.\n\n| Format | Current signature (recommended) | Legacy signature (not recommended) |\n| --------------------- | ------------------------------------ | ---------------------------------- |\n| \"sql\" | `defaultValueProcessorByRule` | `defaultValueProcessor` |\n| \"parameterized\" | `defaultValueProcessorByRule` | `defaultValueProcessor` |\n| \"parameterized_named\" | `defaultValueProcessorByRule` | `defaultValueProcessor` |\n| \"cel\" | `defaultValueProcessorCELByRule` | `defaultCELValueProcessor` |\n| \"mongodb\" | `defaultValueProcessorMongoDBByRule` | `defaultMongoDBValueProcessor` |\n| \"spel\" | `defaultValueProcessorSpELByRule` | `defaultSpELValueProcessor` |"
|
|
3005
3112
|
},
|
|
3006
3113
|
{
|
|
3007
|
-
"page":
|
|
3114
|
+
"page": 48,
|
|
3008
3115
|
"heading": "Configuration › Operator processor",
|
|
3009
3116
|
"anchor": "operator-processor",
|
|
3010
3117
|
"text": "### Operator processor\n\n`operatorProcessor` accepts the same arguments as `ruleProcessor`, but only affects the \"operator\" portion for \"sql\", \"parameterized\", \"parameterized_named\", and \"natural_language\" formats.\n\n```ts\nformatQuery(query, {\n format: 'sql',\n // Convert all operators to uppercase\n operatorProcessor: (rule, options) => defaultOperatorProcessorSQL(rule, options).toUpperCase(),\n});\n/*\n\"(firstName LIKE 'Stev%' and lastName IN ('Vai', 'Vaughan'))\"\n*/\n```"
|
|
3011
3118
|
},
|
|
3012
3119
|
{
|
|
3013
|
-
"page":
|
|
3120
|
+
"page": 48,
|
|
3014
3121
|
"heading": "Configuration › Quote field names",
|
|
3015
3122
|
"anchor": "quote-field-names",
|
|
3016
|
-
"text": "### Quote field names\n\nSome database engines wrap field names in backticks (`` ` ``) or square brackets (`[]`). Configure this with the `quoteFieldNamesWith` option (string or array of two strings).\n\n```ts\nformatQuery(query, { format: 'sql', quoteFieldNamesWith: '`' });\n/*\n\"(`firstName` = 'Steve' and `lastName` = 'Vai')\"\n*/\n\nformatQuery(query, { format: 'sql', quoteFieldNamesWith: ['[', ']'] });\n/*\n\"([firstName] = 'Steve' and [lastName] = 'Vai')\"\n*/\n```\n\nThe closing quote character is escaped within field names by doubling it (`a\"b` → `\"a\"\"b\"`, `a]b` → `[a]]b]`). This applies to the SQL-based formats (\"sql\", \"parameterized\", \"parameterized_named\"), but not \"jsonata\" or \"natural_language\".\n\n**Untrusted queries:**\n\nUnquoted field names (the default) can't be escaped, and operators and combinators are always emitted verbatim. When formatting queries from untrusted sources, quote field names _and_ validate fields, operators, and combinators against a whitelist on the server before calling `formatQuery`.\n\nOther formats escape field names where the target language has an escape mechanism:\n\n- \"cypher\"/\"gql\": `.`-delimited segments that aren't plain identifiers are backtick-quoted (`n.first name` → ``n.`first name` ``).\n- \"gremlin\": property keys are escaped within the `'…'` string literal.\n- \"elasticsearch\": field names inside Painless scripts (`doc['…']`) are escaped.\n- \"mongodb_query\"/\"mongodb\": field names inside `$where` JavaScript use bracket notation when they aren't plain identifiers (`this[\"first name\"]`). Object keys and `$field` references are emitted as-is (a leading `$` or embedded `.` keeps its MongoDB meaning).\n\n\"cel\", \"spel\", \"sparql\", \"ldap\", and \"
|
|
3123
|
+
"text": "### Quote field names\n\nSome database engines wrap field names in backticks (`` ` ``) or square brackets (`[]`). Configure this with the `quoteFieldNamesWith` option (string or array of two strings).\n\n```ts\nformatQuery(query, { format: 'sql', quoteFieldNamesWith: '`' });\n/*\n\"(`firstName` = 'Steve' and `lastName` = 'Vai')\"\n*/\n\nformatQuery(query, { format: 'sql', quoteFieldNamesWith: ['[', ']'] });\n/*\n\"([firstName] = 'Steve' and [lastName] = 'Vai')\"\n*/\n```\n\nThe closing quote character is escaped within field names by doubling it (`a\"b` → `\"a\"\"b\"`, `a]b` → `[a]]b]`). This applies to the SQL-based formats (\"sql\", \"parameterized\", \"parameterized_named\"), but not \"jsonata\" or \"natural_language\".\n\n**Untrusted queries:**\n\nUnquoted field names (the default) can't be escaped, and operators and combinators are always emitted verbatim. When formatting queries from untrusted sources, quote field names _and_ validate fields, operators, and combinators against a whitelist on the server before calling `formatQuery`.\n\nOther formats escape field names where the target language has an escape mechanism:\n\n- \"cypher\"/\"gql\": `.`-delimited segments that aren't plain identifiers are backtick-quoted (`n.first name` → ``n.`first name` ``).\n- \"gremlin\": property keys are escaped within the `'…'` string literal.\n- \"elasticsearch\": field names inside Painless scripts (`doc['…']`) are escaped.\n- \"mongodb_query\"/\"mongodb\": field names inside `$where` JavaScript use bracket notation when they aren't plain identifiers (`this[\"first name\"]`). Object keys and `$field` references are emitted as-is (a leading `$` or embedded `.` keeps its MongoDB meaning).\n\n\"cel\", \"spel\", \"sparql\", \"ldap\", \"jsonata\", and \"milvus\" have no identifier escape syntax, so field names are emitted verbatim. Validate them against a whitelist. The \"pinecone\", \"chroma\", \"qdrant\", and \"weaviate\" formats emit field names as plain object keys/values, which can't break out of the output structure, but should still be whitelisted.\n\n#### Field identifier chains\n\nTo quote members of field identifier chains independently, use `fieldIdentifierSeparator`. A common value is `\".\"`.\n\nIn this example, assume the field names are `musicians.firstName` and `musicians.lastName`.\n\n```ts\nformatQuery(query, {\n format: 'sql',\n quoteFieldNamesWith: ['[', ']'],\n fieldIdentifierSeparator: '.',\n});\n/*\n\"([musicians].[firstName] = 'Steve' and [musicians].[lastName] = 'Vai')\"\n*/\n```"
|
|
3017
3124
|
},
|
|
3018
3125
|
{
|
|
3019
|
-
"page":
|
|
3126
|
+
"page": 48,
|
|
3020
3127
|
"heading": "Configuration › Quote values",
|
|
3021
3128
|
"anchor": "quote-values",
|
|
3022
3129
|
"text": "### Quote values\n\nSome 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.\n\n```ts\nformatQuery(query, { format: 'sql', quoteValuesWith: '\"' });\n/*\n\"(firstName = \"Steve\" and lastName = \"Vai\")\"\n*/\n```"
|
|
3023
3130
|
},
|
|
3024
3131
|
{
|
|
3025
|
-
"page":
|
|
3132
|
+
"page": 48,
|
|
3026
3133
|
"heading": "Configuration › Parameter prefix",
|
|
3027
3134
|
"anchor": "parameter-prefix",
|
|
3028
3135
|
"text": "### Parameter prefix\n\nIf the \"parameterized_named\" format is used, configure the parameter prefix used in the `sql` string with the `paramPrefix` option, should the default `\":\"` be inappropriate.\n\n```ts\nconst p = formatQuery(query, {\n format: 'parameterized_named',\n paramPrefix: '$',\n});\n/*\np.sql === \"(firstName = $firstName_1 and lastName = $lastName_1)\"\n// ^^^ ^^^\n*/\n```"
|
|
3029
3136
|
},
|
|
3030
3137
|
{
|
|
3031
|
-
"page":
|
|
3138
|
+
"page": 48,
|
|
3032
3139
|
"heading": "Configuration › Retain parameter prefixes",
|
|
3033
3140
|
"anchor": "retain-parameter-prefixes",
|
|
3034
3141
|
"text": "### Retain parameter prefixes\n\n`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\" }`)."
|
|
3035
3142
|
},
|
|
3036
3143
|
{
|
|
3037
|
-
"page":
|
|
3144
|
+
"page": 48,
|
|
3038
3145
|
"heading": "Configuration › Numbered parameters",
|
|
3039
3146
|
"anchor": "numbered-parameters",
|
|
3040
3147
|
"text": "### Numbered parameters\n\nFor \"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 `\":\"`).\n\n```ts\nconst p = formatQuery(query, {\n format: 'parameterized',\n paramPrefix: '$',\n numberedParams: true,\n});\n/*\np.sql === \"(firstName = $1 and lastName = $2)\"\n*/\n```\n\nPreviously, [manual post-processing](../tips/custom-bind-variables) was necessary for this effect."
|
|
3041
3148
|
},
|
|
3042
3149
|
{
|
|
3043
|
-
"page":
|
|
3150
|
+
"page": 48,
|
|
3044
3151
|
"heading": "Configuration › Named parameters (value source)",
|
|
3045
3152
|
"anchor": "named-parameters-value-source",
|
|
3046
3153
|
"text": "### Named parameters (value source)\n\nThe `getParameters` option supports rules whose `valueSource` is [`\"parameter\"`](../components/valueeditor#the-parameter-value-source). Provide the same function passed to the [`getParameters` prop](../components/querybuilder#getparameters) (names without a prefix):\n\n```ts\nformatQuery(query, {\n format: 'sql',\n getParameters: () => [{ name: 'p1', label: 'Param 1' }],\n});\n```\n\nBehavior by format:\n\n- **`sql`** — the prefixed name is emitted inline (e.g. `f1 = :p1`).\n- **`parameterized`** — the name is emitted inline; positional placeholders are _not_ pushed to `params`.\n- **`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`.)\n- **`cel`, `spel`, `jsonlogic`** — the name is treated as an identifier/variable reference.\n- Other formats emit the name as a literal.\n\nWhen `getParameters` is supplied, rules referencing a name not in the list are treated as invalid (dropped or handled per your validation options).\n\n**Tip:**\n\nThe [external parameter manager](../tips/parameter-manager) example demonstrates merging user-supplied values over the `null` placeholders produced by the `\"parameterized_named\"` format."
|
|
3047
3154
|
},
|
|
3048
3155
|
{
|
|
3049
|
-
"page":
|
|
3156
|
+
"page": 48,
|
|
3050
3157
|
"heading": "Configuration › Concatenation operator",
|
|
3051
3158
|
"anchor": "concatenation-operator",
|
|
3052
3159
|
"text": "### Concatenation operator\n\nMost SQL database dialects use the `||` operator to concatenate strings. SQL Server uses `+`, and MySQL uses the `CONCAT` function instead.\n\nConfigure 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.\n\nIf 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.)\n\n```ts\nconst query = {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: '=', value: 'Kris' },\n { field: 'lastName', operator: 'beginswith', value: 'firstName', valueSource: 'field' },\n ],\n};\n\nformatQuery(query, { format: 'sql', concatOperator: '+' });\n/*\n\"(firstName = 'Kris' and lastName like firstName + '%')\"\n*/\n\nformatQuery(query, { format: 'sql', concatOperator: 'CONCAT' });\n/*\n\"(firstName = 'Kris' and lastName like CONCAT(firstName, '%'))\"\n*/\n```"
|
|
3053
3160
|
},
|
|
3054
3161
|
{
|
|
3055
|
-
"page":
|
|
3162
|
+
"page": 48,
|
|
3056
3163
|
"heading": "Configuration › Presets",
|
|
3057
3164
|
"anchor": "presets",
|
|
3058
|
-
"text": "### Presets\n\nThe `preset` option configures options known to enable or improve compatibility with particular query language dialects. Individual options override their respective preset values. Available presets:\n\n**Info:**\n\nIf `preset` is from `sqlDialectPresets`, it only applies if `format` is undefined or one of the SQL-based formats.\n\n<table>\n <thead>\n <tr><th>Dialect</th><th>Preset options</th></tr>\n </thead>\n <tbody>\n <tr><td>\n\n`'ansi'`\n\n</td><td>\n\n```json\n{}\n```\n\n</td></tr>\n <tr><td>\n\n`'sqlite'`\n\n</td><td>\n\n```json\n{ \"paramsKeepPrefix\": true }\n```\n\n</td></tr>\n <tr><td>\n\n`'oracle'`\n\n</td><td>\n\n```json\n{}\n```\n\n</td></tr>\n <tr><td>\n\n`'mssql'`\n\n</td><td>\n\n```json\n{\n \"quoteFieldNamesWith\": [\"[\", \"]\"],\n \"concatOperator\": \"+\",\n \"fieldIdentifierSeparator\": \".\",\n \"paramPrefix\": \"@\"\n}\n```\n\n</td></tr>\n <tr><td>\n\n`'mysql'`\n\n</td><td>\n\n```json\n{
|
|
3165
|
+
"text": "### Presets\n\nThe `preset` option configures options known to enable or improve compatibility with particular query language dialects. Individual options override their respective preset values. Available presets:\n\n**Info:**\n\nIf `preset` is from `sqlDialectPresets`, it only applies if `format` is undefined or one of the SQL-based formats.\n\n<table>\n <thead>\n <tr><th>Dialect</th><th>Preset options</th></tr>\n </thead>\n <tbody>\n <tr><td>\n\n`'ansi'`\n\n</td><td>\n\n```json\n{}\n```\n\n</td></tr>\n <tr><td>\n\n`'sqlite'`\n\n</td><td>\n\n```json\n{ \"paramsKeepPrefix\": true }\n```\n\n</td></tr>\n <tr><td>\n\n`'oracle'`\n\n</td><td>\n\n```json\n{ \"numberedParams\": true, \"paramPrefix\": \":\" }\n```\n\n</td></tr>\n <tr><td>\n\n`'mssql'`\n\n</td><td>\n\n```json\n{\n \"quoteFieldNamesWith\": [\"[\", \"]\"],\n \"concatOperator\": \"+\",\n \"fieldIdentifierSeparator\": \".\",\n \"numberedParams\": true,\n \"paramPrefix\": \"@\"\n}\n```\n\nBoolean values render as `1`/`0` (T-SQL has no `TRUE`/`FALSE` literals).\n\n</td></tr>\n <tr><td>\n\n`'mysql'`\n\n</td><td>\n\n```json\n{\n \"concatOperator\": \"CONCAT\",\n \"quoteFieldNamesWith\": \"`\",\n \"fieldIdentifierSeparator\": \".\"\n}\n```\n\nBackslashes in string values are doubled when `escapeQuotes` is on (MySQL/MariaDB treat `\\` as an escape character in string literals).\n\n</td></tr>\n <tr><td>\n\n`'postgresql'`\n\n</td><td>\n\n```json\n{ \"quoteFieldNamesWith\": \"\\\"\", \"numberedParams\": true, \"paramPrefix\": \"$\" }\n```\n\n</td></tr>\n </tbody>\n</table>\n\nExamples:\n\n```ts\nformatQuery(query, { format: 'parameterized', preset: 'postgresql' });\n/*\n{\n sql: `(\"firstName\" like $1 and \"lastName\" in ($2, $3))`,\n params: ['Stev%', 'Vai', 'Vaughan']\n}\n*/\n\nformatQuery(query, { format: 'sql', preset: 'mssql' });\n/*\n\"([musicians].[firstName] = 'Kris' and [musicians].[lastName] like [musicians].[firstName] + '%')\"\n*/\n```"
|
|
3059
3166
|
},
|
|
3060
3167
|
{
|
|
3061
|
-
"page":
|
|
3168
|
+
"page": 48,
|
|
3062
3169
|
"heading": "Configuration › Fallback expression",
|
|
3063
3170
|
"anchor": "fallback-expression",
|
|
3064
|
-
"text": "### Fallback expression\n\n`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:\n\n| Format | Default `fallbackExpression` |\n| ----------------------- | ----------------------------- |\n| `'sql'` | `'(1 = 1)'` |\n| `'parameterized'` | `'(1 = 1)'` |\n| `'parameterized_named'` | `'(1 = 1)'` |\n| `'cypher'` / `'gql'` | `'(1 = 1)'` |\n| `'sparql'` | `'1 = 1'` |\n| `'gremlin'` | `''` |\n| `'ldap'` | `''` |\n| `'mongodb'` | `'{\"$and\":[{\"$expr\":true}]}'` |\n| `'mongodb_query'` | `{\"$and\":[{\"$expr\":true}]}` |\n| `'natural_language'` | `'1 is 1'` |\n| `'cel'` | `'1 == 1'` |\n| `'spel'` | `'1 == 1'` |\n| `'jsonata'` | `'(1 = 1)'` |\n| `'jsonlogic'` | `false` |\n| `'elasticsearch'` | `{}` |\n| `'drizzle'` | `undefined` |\n| `'prisma'` | `{}` |\n| `'sequelize'` | `{}` |\n| `'tanstack_db'` | `eq(1, 1)` |"
|
|
3171
|
+
"text": "### Fallback expression\n\n`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:\n\n| Format | Default `fallbackExpression` |\n| ----------------------- | ----------------------------- |\n| `'sql'` | `'(1 = 1)'` |\n| `'parameterized'` | `'(1 = 1)'` |\n| `'parameterized_named'` | `'(1 = 1)'` |\n| `'cypher'` / `'gql'` | `'(1 = 1)'` |\n| `'sparql'` | `'1 = 1'` |\n| `'gremlin'` | `''` |\n| `'ldap'` | `''` |\n| `'mongodb'` | `'{\"$and\":[{\"$expr\":true}]}'` |\n| `'mongodb_query'` | `{\"$and\":[{\"$expr\":true}]}` |\n| `'natural_language'` | `'1 is 1'` |\n| `'cel'` | `'1 == 1'` |\n| `'spel'` | `'1 == 1'` |\n| `'jsonata'` | `'(1 = 1)'` |\n| `'jsonlogic'` | `false` |\n| `'elasticsearch'` | `{}` |\n| `'pinecone'` | `{}` |\n| `'chroma'` | `{}` |\n| `'qdrant'` | `{}` |\n| `'weaviate'` | `{}` |\n| `'milvus'` | `''` |\n| `'drizzle'` | `undefined` |\n| `'prisma'` | `{}` |\n| `'sequelize'` | `{}` |\n| `'tanstack_db'` | `eq(1, 1)` |"
|
|
3065
3172
|
},
|
|
3066
3173
|
{
|
|
3067
|
-
"page":
|
|
3174
|
+
"page": 48,
|
|
3068
3175
|
"heading": "Configuration › Value sources",
|
|
3069
3176
|
"anchor": "value-sources",
|
|
3070
3177
|
"text": "### Value sources\n\nWhen a rule's `valueSource` property is \"field\", no parameters are generated.\n\n```ts\nconst pf = formatQuery(\n {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: '=', value: 'lastName', valueSource: 'field' },\n { field: 'firstName', operator: 'beginsWith', value: 'middleName', valueSource: 'field' },\n ],\n },\n 'parameterized_named'\n);\n```\n\nOutput (JSON object):\n\n```json\n{\n \"sql\": \"(firstName = lastName and firstName like middleName || '%')\",\n \"params\": {}\n}\n```"
|
|
3071
3178
|
},
|
|
3072
3179
|
{
|
|
3073
|
-
"page":
|
|
3180
|
+
"page": 48,
|
|
3074
3181
|
"heading": "Configuration › Placeholder values",
|
|
3075
3182
|
"anchor": "placeholder-values",
|
|
3076
3183
|
"text": "### Placeholder values\n\nRules 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](../components/querybuilder#translations) object. This behavior for the `value` property only applies if `placeholderValueName` is explicitly set. The others use their defaults if undefined."
|
|
3077
3184
|
},
|
|
3078
3185
|
{
|
|
3079
|
-
"page":
|
|
3186
|
+
"page": 48,
|
|
3080
3187
|
"heading": "Configuration › Internationalization",
|
|
3081
3188
|
"anchor": "internationalization",
|
|
3082
3189
|
"text": "### Internationalization\n\nThese i18n options are specific to [\"natural_language\"](#natural-language) format.\n\n#### Word order\n\nBased 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).\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n wordOrder: 'SOV',\n});\n// `First Name 'Steve' is`\n```\n\n#### Translations\n\nMap \"and\", \"or\", \"true\", and \"false\" to their translated equivalents, plus prefix and suffix options for rule groups.\n\nThe 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\"`).\n\nFor 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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n translations: {\n groupSuffix: 'is def the truth',\n groupSuffix_not: 'is so not true',\n },\n});\n// Given the following query:\n// const query = {\n// rules: [\n// { rules: [{ field: 'firstName', operator: '=', value: 'Steve' }] },\n// 'and',\n// { not: true, rules: [{ field: 'firstName', operator: '=', value: 'Vai' }] },\n// ]\n// };\n// ...potential output could be:\n// `(First Name is 'Steve') is def the truth, and (Last Name is 'Vai') is so not true`\n```\n\nWhen `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).\n\n##### Rule separator\n\nBy 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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n translations: { ruleSeparator: '、', and: 'かつ' },\n});\n// `First Name is 'Steve'、かつ Last Name is 'Vai'`\n```\n\n##### Between conjunction\n\nThe 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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n translations: { and: 'かつ', betweenAnd: 'と' },\n});\n// `Age is between '12' と '14'`\n// Rules are still joined with 'かつ'\n```\n\n##### Constituent particles\n\nBy 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.\n\nThis is essential for languages that use grammatical particles between constituents — for example, Japanese requires `が` (subject marker) after the subject:\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n wordOrder: 'SOV',\n translations: { afterSubject: 'が' },\n operatorMap: { '=': 'である' },\n});\n// `First Nameが'Steve' である`\n```\n\n##### List separator\n\nThe 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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n translations: { listSeparator: '、', or: 'または' },\n});\n// `Color is one of the values ('Red'、'Green' または 'Blue')`\n```\n\n#### Operator map\n\n`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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n operatorMap: {\n '=': 'is most assuredly',\n '!=': ['is not', 'differs from'],\n },\n});\n// `First Name is most assuredly 'Steve', and Last Name differs from First Name`\n```\n\n#### Sample language configurations\n\nThese 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.\n\n##### Japanese (SOV with particles)\n\nJapanese 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.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n wordOrder: 'SOV',\n operatorMap: {\n '=': ['である', 'と同じ値である'],\n '>': ['より大きい', 'の値より大きい'],\n beginswith: ['で始まる', 'の値で始まる'],\n in: ['のいずれかである', 'と同じ値のいずれかである'],\n between: ['の間である', 'の値の間である'],\n // ... other operators\n },\n translations: {\n and: 'かつ',\n or: 'または',\n true: '真',\n false: '偽',\n ruleSeparator: '、',\n betweenAnd: 'から',\n afterSubject: 'が',\n afterObject: '',\n listSeparator: '、',\n groupSuffix: '',\n },\n});\n// First Nameが'Stev'で始まる、かつ Ageが'28'より大きい\n```\n\n##### Spanish (SVO with translations)\n\nSpanish uses the same SVO order as English, so only `operatorMap` and `translations` are needed — no `wordOrder` change.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n operatorMap: {\n '=': ['es', 'es igual al valor de'],\n '>': ['es mayor que', 'es mayor que el valor de'],\n beginswith: ['comienza con', 'comienza con el valor de'],\n in: ['es uno de los valores', 'es igual a un valor en'],\n between: ['está entre', 'está entre los valores de'],\n // ... other operators\n },\n translations: {\n and: 'y',\n or: 'o',\n true: 'verdadero',\n false: 'falso',\n groupSuffix: 'es verdadero',\n groupSuffix_not: 'no es verdadero',\n },\n});\n// First Name comienza con 'Stev', y Age es mayor que '28'\n```\n\n##### Welsh (VSO)\n\nWelsh uses Verb-Subject-Object order, placing the operator first.\n\n```ts\nformatQuery(query, {\n format: 'natural_language',\n wordOrder: 'VSO',\n operatorMap: {\n '=': ['yw', \"yr un fath â'r gwerth yn\"],\n '>': ['yn fwy na', \"yn fwy na'r gwerth yn\"],\n beginswith: ['yn dechrau gyda', \"yn dechrau gyda'r gwerth yn\"],\n in: [\"yn un o'r gwerthoedd\", \"yr un fath ag un o'r gwerthoedd yn\"],\n between: ['rhwng', 'rhwng y gwerthoedd yn'],\n // ... other operators\n },\n translations: {\n and: 'a',\n or: 'neu',\n true: 'gwir',\n false: 'gau',\n betweenAnd: 'a',\n groupSuffix: 'yn wir',\n groupSuffix_not: 'ddim yn wir',\n },\n});\n// yn dechrau gyda First Name 'Stev', a yn fwy na Age '28'\n```\n\n**Tip:**\n\nComplete 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."
|
|
3083
3190
|
},
|
|
3084
3191
|
{
|
|
3085
|
-
"page":
|
|
3192
|
+
"page": 48,
|
|
3086
3193
|
"heading": "Configuration › Rule group processor",
|
|
3087
3194
|
"anchor": "rule-group-processor",
|
|
3088
3195
|
"text": "### Rule group processor\n\n`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.\n\nTo 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:\n\n```ts\nruleGroupProcessor(ruleGroup, finalOptions);\n```\n\n> **_Note: The `ruleGroupProcessor` option overrides the `format` option._**\n\nThe default rule group processors for each format are available as exports from `react-querybuilder`:\n\n- `defaultRuleGroupProcessorCEL`\n- `defaultRuleGroupProcessorElasticSearch`\n- `defaultRuleGroupProcessorJSONata`\n- `defaultRuleGroupProcessorJsonLogic`\n- `defaultRuleGroupProcessorMongoDB`\n- `defaultRuleGroupProcessorMongoDBQuery`\n- `defaultRuleGroupProcessorNL`\n- `defaultRuleGroupProcessorSpEL`\n- `defaultRuleGroupProcessorSQL`\n- `defaultRuleGroupProcessorParameterized`\n- `defaultRuleGroupProcessorTanStackDB`\n\nUse the appropriate default rule group processor as a fallback so your custom processor doesn't need to cover all cases:\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n not: false,\n rules: [\n { combinator: 'and', rules: [] },\n // empty rules array ^^^^^^^^^\n { field: 'firstName', operator: 'beginsWith', value: 'S' },\n ],\n};\n\nconst customRuleGroupProcessor: RuleGroupProcessor<string> = (ruleGroup, options) => {\n if (ruleGroup.rules.length === 0) {\n // Normally, empty rule groups are ignored, but here they evaluate to false\n return '(1 = 0)';\n }\n\n // Defer to the default rule group processor for all other operators\n return defaultRuleGroupProcessorSQL(ruleGroup, options);\n};\n\nformatQuery(query, { ruleGroupProcessor: customRuleGroupProcessor });\n/*\n\"((1 = 0) and firstName LIKE 'S%')\"\n*/\n```"
|
|
3089
3196
|
},
|
|
3090
3197
|
{
|
|
3091
|
-
"page":
|
|
3198
|
+
"page": 48,
|
|
3092
3199
|
"heading": "Validation",
|
|
3093
3200
|
"anchor": "validation",
|
|
3094
3201
|
"text": "## Validation\n\nValidation options (`validator` and `fields` – see [Validation](./validation)) 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.\n\nExample:\n\n```ts\nconst query: RuleGroupType = {\n id: 'root',\n rules: [\n { id: 'r1', field: 'firstName', value: '', operator: '=' },\n { id: 'r2', field: 'lastName', value: 'Vai', operator: '=' },\n ],\n combinator: 'and',\n not: false,\n};\n\n// Example 1\n// Query is invalid based on the validator function\nformatQuery(query, {\n format: 'sql',\n validator: () => false,\n});\n/*\n\"(1 = 1)\" <-- see `fallbackExpression` option\n*/\n\n// Example 2\n// Rule \"r1\" is invalid based on the validation map\nformatQuery(query, {\n format: 'sql',\n validator: () => ({ r1: false }),\n});\n/*\n\"(lastName = 'Vai')\" <-- skipped `firstName` rule with `id === 'r1'`\n*/\n\n// Example 3\n// Rule \"r1\" is invalid based on the field validator for `firstName`\nformatQuery(query, {\n format: 'sql',\n fields: [{ name: 'firstName', validator: () => false }],\n});\n/*\n\"(lastName = 'Vai')\" <-- skipped `firstName` rule because field validator returned `false`\n*/\n```"
|
|
3095
3202
|
},
|
|
3096
3203
|
{
|
|
3097
|
-
"page":
|
|
3204
|
+
"page": 48,
|
|
3098
3205
|
"heading": "Validation › Muted rules and groups",
|
|
3099
3206
|
"anchor": "muted-rules-and-groups",
|
|
3100
3207
|
"text": "### Muted rules and groups\n\nRules 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.\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: '=', value: 'Steve' },\n { field: 'lastName', operator: '=', value: 'Vai', muted: true },\n ],\n};\n\nformatQuery(query, 'sql');\n// \"(firstName = 'Steve')\" - lastName rule is excluded\n```\n\nWhen a group is muted, it's replaced with the [fallback expression](#fallback-expression):\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'firstName', operator: '=', value: 'Steve' },\n {\n combinator: 'or',\n rules: [\n { field: 'lastName', operator: '=', value: 'Vai' },\n { field: 'instrument', operator: '=', value: 'Guitar' },\n ],\n muted: true,\n },\n ],\n};\n\nformatQuery(query, 'sql');\n// \"(firstName = 'Steve' and (1 = 1))\" - muted group becomes fallback\n```\n\n**Tip:**\n\nEnable mute functionality in the UI by setting [`showMuteButtons`](../components/querybuilder#showmutebuttons) to `true` on the main `QueryBuilder` component."
|
|
3101
3208
|
},
|
|
3102
3209
|
{
|
|
3103
|
-
"page":
|
|
3210
|
+
"page": 48,
|
|
3104
3211
|
"heading": "Validation › Automatic validation",
|
|
3105
3212
|
"anchor": "automatic-validation",
|
|
3106
3213
|
"text": "### Automatic validation\n\nTo 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.\n\n- 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.\n- 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).\n- Rules where `field`, `operator`, or `value` match their respective placeholder are invalid:\n ```ts\n field === placeholderFieldName ||\n operator === placeholderOperatorName ||\n (placeholderValueName !== undefined && value === placeholderValueName)\n ```"
|
|
3107
3214
|
},
|
|
3108
3215
|
{
|
|
3109
|
-
"page":
|
|
3216
|
+
"page": 49,
|
|
3110
3217
|
"heading": "",
|
|
3111
3218
|
"anchor": "",
|
|
3112
3219
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\n`@react-querybuilder/core` contains every derivation the `<QueryBuilder />` 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.\n\nOfficial ports built this way already exist for [Solid, Svelte, and Vue](../compat#other-frameworks).\n\n**Info:**\n\nThe 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.\n\nThe 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."
|
|
3113
3220
|
},
|
|
3114
3221
|
{
|
|
3115
|
-
"page":
|
|
3222
|
+
"page": 49,
|
|
3116
3223
|
"heading": "The /derivations subpath",
|
|
3117
3224
|
"anchor": "the-derivations-subpath",
|
|
3118
3225
|
"text": "## The `/derivations` subpath\n\nEverything on this page is available from the package root, and—except for `QueryManager`,\n`formatQuery`, and the parsers—also from `@react-querybuilder/core/derivations`:\n\n```ts\nimport { deriveRuleContext, shouldCoalesce } from '@react-querybuilder/core/derivations';\n```\n\nThe subpath contains the same bindings as the root, minus `QueryManager` and the query formatter.\nImporting from it guarantees that neither ends up in the bundle. That matters for adapters that own\ntheir own state and never construct a `QueryManager`: importing the same names from the root leaves\ntheir elimination up to the bundler, which is a property worth having as a contract rather than as\nan optimization that may or may not happen.\n\n`bun check-derivations-purity` gates the guarantee against the built output, so an import added to\na derivation module cannot quietly undo it.\n\nThe parsers (`parseSQL`, `parseCEL`, and the rest) have subpaths of their own and are not exported\nfrom the root, so they are excluded from `/derivations` as well."
|
|
3119
3226
|
},
|
|
3120
3227
|
{
|
|
3121
|
-
"page":
|
|
3228
|
+
"page": 49,
|
|
3122
3229
|
"heading": "Option list preparation",
|
|
3123
3230
|
"anchor": "option-list-preparation",
|
|
3124
3231
|
"text": "## Option list preparation\n\n| Function | Purpose |\n| ------------------------ | ---------------------------------------------------------------------------------- |\n| `prepareRuleGroup` | Normalizes a query object, assigning `id`s where missing. |\n| `prepareOptionList` | Normalizes any option list prop into a `FullOptionList`, applying placeholders. |\n| `toFlatOptionArray` | Flattens option groups into a single array. |\n| `resolveOperatorList` | The operator list for a field, applying the same precedence as `<QueryBuilder />`. |\n| `resolveDefaultOperator` | The default operator for a field. |\n| `resolveValueList` | The value list for a field/operator pair. |\n| `resolveValueEditorType` | The value editor type for a field/operator pair. |\n| `getValueSourcesUtil` | The value sources available for a field/operator pair. |\n| `getMatchModesUtil` | The match modes available for a field. |\n| `getRuleDefaultValue` | The default `value` for a rule. |\n| `getFieldData` | Looks a field up in a flattened field map, with a minimal fallback. |"
|
|
3125
3232
|
},
|
|
3126
3233
|
{
|
|
3127
|
-
"page":
|
|
3234
|
+
"page": 49,
|
|
3128
3235
|
"heading": "Rule and group derivations",
|
|
3129
3236
|
"anchor": "rule-and-group-derivations",
|
|
3130
3237
|
"text": "## Rule and group derivations\n\n| Function | Purpose |\n| ------------------------------- | ---------------------------------------------------------------------------- |\n| `deriveRuleContext` | Everything a rule's UI needs: field data, operators, value editor type, etc. |\n| `deriveRuleGroupContext` | The equivalent derivation for a group. |\n| `deriveQueryBuilderClassNames` | The resolved class names for every element. |\n| `generateAccessibleDescription` | The accessible description for a group. |\n| `resolveCandidateQuery` | Resolves a controlled/uncontrolled query prop against the current state. |"
|
|
3131
3238
|
},
|
|
3132
3239
|
{
|
|
3133
|
-
"page":
|
|
3240
|
+
"page": 49,
|
|
3134
3241
|
"heading": "Query manipulation",
|
|
3135
3242
|
"anchor": "query-manipulation",
|
|
3136
3243
|
"text": "## Query manipulation\n\n`createRule`, `createRuleGroup`, `createQueryActions`, and the query tools `add`, `remove`, `update`, `move`, `insert`, and `group`. See [Query management](./query-management) for full signatures.\n\nPath helpers: `findPath`, `findID`, `getPathOfID`, `pathIsDisabled`, `pathIsDisabledByPaths`, `exceedsMaxLevels`.\n\nEvery 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](./query-management#freezing-tools). `QueryManager` accepts an option of the same name, and `setAutoFreeze` is re-exported from immer as a process-wide switch."
|
|
3137
3244
|
},
|
|
3138
3245
|
{
|
|
3139
|
-
"page":
|
|
3246
|
+
"page": 49,
|
|
3140
3247
|
"heading": "History",
|
|
3141
3248
|
"anchor": "history",
|
|
3142
3249
|
"text": "## History\n\n| Export | Purpose |\n| -------------------------------------------- | ------------------------------------------------------------------- |\n| `signatureOf` | Describes what changed between two queries. |\n| `structuralSignature` / `unchangedSignature` | Sentinel signatures: shape changed / nothing observable changed. |\n| `shouldCoalesce` | Whether a change should be absorbed into the current history entry. |\n| `defaultCoalesceMs` / `defaultMaxHistory` | Defaults for the coalescing window and stack depth. |\n\n`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:\n\n```ts\nimport { shouldCoalesce, signatureOf, unchangedSignature } from '@react-querybuilder/core';\nimport type { RuleGroupTypeAny } from '@react-querybuilder/core';\n\nlet past: RuleGroupTypeAny[] = [];\nlet future: RuleGroupTypeAny[] = [];\nlet lastSig = '';\nlet lastAt = 0;\n\nconst record = (prev: RuleGroupTypeAny, next: RuleGroupTypeAny) => {\n const sig = signatureOf(prev, next);\n // A change with no observable difference is never recorded at all.\n if (sig === unchangedSignature) return;\n if (!shouldCoalesce(lastSig, sig, lastAt, Date.now())) {\n past.push(prev);\n future = [];\n }\n lastSig = sig;\n lastAt = Date.now();\n};\n```"
|
|
3143
3250
|
},
|
|
3144
3251
|
{
|
|
3145
|
-
"page":
|
|
3252
|
+
"page": 49,
|
|
3146
3253
|
"heading": "Configuration",
|
|
3147
3254
|
"anchor": "configuration",
|
|
3148
3255
|
"text": "## Configuration\n\n| Export | Purpose |\n| -------------------- | -------------------------------------------------------------------------- |\n| `optionsEqual` | Whether two `QueryManagerOptions` objects describe the same configuration. |\n| `valuesEqual` | The underlying comparison, for individual option values. |\n| `SubscriptionChange` | The payload passed to `QueryManager` subscribers (type-only). |\n\nBoth 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.\n\n[`QueryManager#reconfigure`](./query-management#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.\n\n```ts\nimport { optionsEqual } from '@react-querybuilder/core';\n\n// Only builds the merged object when something actually changed.\nif (!optionsEqual(manager.getOptions(), nextOptions)) manager.reconfigure(nextOptions);\n```"
|
|
3149
3256
|
},
|
|
3150
3257
|
{
|
|
3151
|
-
"page":
|
|
3258
|
+
"page": 49,
|
|
3152
3259
|
"heading": "Controls",
|
|
3153
3260
|
"anchor": "controls",
|
|
3154
3261
|
"text": "## Controls\n\n| Export | Purpose |\n| ----------------- | ---------------------------------------------------------------------------------------- |\n| `controlKeys` | The name of every query builder control. |\n| `controlPropKeys` | The complete set of prop names each control receives. |\n| `controlKind` | Which bulk override (`actionElement` / `valueSelector`) applies to each control, if any. |\n\n`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."
|
|
3155
3262
|
},
|
|
3156
3263
|
{
|
|
3157
|
-
"page":
|
|
3264
|
+
"page": 49,
|
|
3158
3265
|
"heading": "Validation and export",
|
|
3159
3266
|
"anchor": "validation-and-export",
|
|
3160
3267
|
"text": "## Validation and export\n\n`defaultValidator` and `formatQuery` are also framework-agnostic. See [Validation](./validation) and [Export](./export). `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`."
|
|
3161
3268
|
},
|
|
3162
3269
|
{
|
|
3163
|
-
"page":
|
|
3270
|
+
"page": 50,
|
|
3164
3271
|
"heading": "",
|
|
3165
3272
|
"anchor": "",
|
|
3166
3273
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nThese hooks are used internally by React Query Builder and exported for use in custom components."
|
|
3167
3274
|
},
|
|
3168
3275
|
{
|
|
3169
|
-
"page":
|
|
3276
|
+
"page": 50,
|
|
3170
3277
|
"heading": "State access",
|
|
3171
3278
|
"anchor": "state-access",
|
|
3172
3279
|
"text": "## State access\n\nReact Query Builder uses Redux within a custom React context to manage state without interfering with existing Redux stores."
|
|
3173
3280
|
},
|
|
3174
3281
|
{
|
|
3175
|
-
"page":
|
|
3282
|
+
"page": 50,
|
|
3176
3283
|
"heading": "State access › useQueryBuilderQuery",
|
|
3177
3284
|
"anchor": "usequerybuilderquery",
|
|
3178
3285
|
"text": "### `useQueryBuilderQuery`\n\nRetrieves the complete, current query object for the nearest ancestor `QueryBuilder` component.\n\nThe 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 } }`.\n\n```ts\nfunction useQueryBuilderQuery(props?: { schema: { qbId: string } }): RuleGroupTypeAny;\n```\n\n**Tip:**\n\nAs 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_.\n\n**To access the current query from an event handler, use `props.schema.getQuery()` instead.**"
|
|
3179
3286
|
},
|
|
3180
3287
|
{
|
|
3181
|
-
"page":
|
|
3288
|
+
"page": 50,
|
|
3182
3289
|
"heading": "State access › useQueryBuilderHistory",
|
|
3183
3290
|
"anchor": "usequerybuilderhistory",
|
|
3184
3291
|
"text": "### `useQueryBuilderHistory`\n\nRecords 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.\n\nUnlike 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.\n\n```ts\nfunction useQueryBuilderHistory(\n qbId: string,\n options?: { maxHistory?: number; coalesceMs?: number }\n): {\n undo: () => void;\n redo: () => void;\n clear: () => void;\n canUndo: boolean;\n canRedo: boolean;\n past: RuleGroupTypeAny[];\n future: RuleGroupTypeAny[];\n};\n```\n\nSee [Undo/redo](../tips/undo-redo) for the full guide."
|
|
3185
3292
|
},
|
|
3186
3293
|
{
|
|
3187
|
-
"page":
|
|
3294
|
+
"page": 50,
|
|
3188
3295
|
"heading": "State access › useQueryBuilderSelector",
|
|
3189
3296
|
"anchor": "usequerybuilderselector",
|
|
3190
3297
|
"text": "### `useQueryBuilderSelector`\n\n**Tip:**\n\nPrefer [`useQueryBuilderQuery`](#usequerybuilderquery) if you only need the query object for the nearest ancestor `QueryBuilder` component.\n\nReturns the current query from the Redux store when used with `getQuerySelectorById` (see example below).\n\n```ts\nfunction useQueryBuilderSelector(selector: (state: RqbState) => RuleGroupTypeAny): RuleGroupTypeAny;\n```\n\nExample:\n\n```ts\nconst CustomValueEditor = (props: ValueEditorProps) => {\n const fullQuery = useQueryBuilderSelector(getQuerySelectorById(props.schema.qbId));\n // Here you can use utilities like `findPath(getParentPath(props.path), fullQuery)`.\n // This allows you to, for example, inspect the parent group of the current rule.\n // You can then count sibling rules, check for unique `field` selections, etc.\n // That information can be used for validation, information to the user, or\n // anything else in your render function.\n};\n```"
|
|
3191
3298
|
},
|
|
3192
3299
|
{
|
|
3193
|
-
"page":
|
|
3300
|
+
"page": 50,
|
|
3194
3301
|
"heading": "State access › getDispatchQueryById",
|
|
3195
3302
|
"anchor": "getdispatchquerybyid",
|
|
3196
3303
|
"text": "### `getDispatchQueryById`\n\nNot 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.\n\n```ts\nfunction getDispatchQueryById(qbId: string): DispatchQueryFn | undefined;\n```\n\nUpdating 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](../components/querybuilder#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`](../tips/undo-redo), which use it to apply restored queries.\n\nRequires an explicit [`qbId`](../components/querybuilder#qbid) prop, since automatically generated identifiers are not discoverable from outside the component.\n\n```ts\nconst dispatchQuery = getDispatchQueryById('main');\ndispatchQuery?.(add(currentQuery, { field: 'firstName', operator: '=', value: '' }, []));\n```"
|
|
3197
3304
|
},
|
|
3198
3305
|
{
|
|
3199
|
-
"page":
|
|
3306
|
+
"page": 50,
|
|
3200
3307
|
"heading": "State access › useQueryManager",
|
|
3201
3308
|
"anchor": "usequerymanager",
|
|
3202
3309
|
"text": "### `useQueryManager`\n\nSubscribes to a [`QueryManager`](./query-management#query-manager) and returns its current query alongside the manager itself, re-rendering whenever the query changes. Returns a `[query, manager]` tuple.\n\n```ts\nfunction useQueryManager<RG, F, O, C>(\n manager: QueryManager<RG, F, O, C>\n): [RG, QueryManager<RG, F, O, C>];\nfunction useQueryManager<RG, F, O, C>(\n query?: RG,\n options?: QueryManagerOptions<F, O, C>\n): [RG, QueryManager<RG, F, O, C>];\n```\n\nUnlike 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`](../components/querybuilder) component.\n\nPass an existing manager to control its lifetime yourself:\n\n```tsx\nconst qm = useMemo(() => new QueryManager(initialQuery, { fields }), []);\n\nconst CustomUI = () => {\n const [query] = useQueryManager(qm);\n // ...\n};\n```\n\nOr 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:\n\n```tsx\nconst CustomUI = () => {\n const [query, qm] = useQueryManager(initialQuery, { fields });\n\n return (\n <>\n <button onClick={() => qm.add(qm.createRule())}>Add rule</button>\n <pre>{JSON.stringify(query, null, 2)}</pre>\n </>\n );\n};\n```\n\nSince the manager is stable, its methods are safe to call from event handlers and to use in dependency arrays. A [batch](./query-management#batching) triggers a single re-render regardless of how many changes it contains, and mutations that resolve to a no-op trigger none.\n\nTo change the configuration after the first render, call [`reconfigure`](./query-management#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.\n\n```tsx\nuseEffect(() => {\n qm.reconfigure({ translations });\n}, [qm, translations]);\n```"
|
|
3203
3310
|
},
|
|
3204
3311
|
{
|
|
3205
|
-
"page":
|
|
3312
|
+
"page": 50,
|
|
3206
3313
|
"heading": "Component logic",
|
|
3207
3314
|
"anchor": "component-logic",
|
|
3208
3315
|
"text": "## Component logic\n\nThe 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.\n\n**Tip:**\n\nThe `@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."
|
|
3209
3316
|
},
|
|
3210
3317
|
{
|
|
3211
|
-
"page":
|
|
3318
|
+
"page": 50,
|
|
3212
3319
|
"heading": "Component logic › useRule",
|
|
3213
3320
|
"anchor": "userule",
|
|
3214
3321
|
"text": "### `useRule`\n\nCalled by the [`Rule`](../components/rule) component. See [source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/hooks/useRule.ts) for returned properties.\n\n```ts\nfunction useRule(props: RuleProps): {\n // See source code for returned properties\n};\n```\n\nThe 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()`](./query-management#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`](./query-management#classnames)."
|
|
3215
3322
|
},
|
|
3216
3323
|
{
|
|
3217
|
-
"page":
|
|
3324
|
+
"page": 50,
|
|
3218
3325
|
"heading": "Component logic › useRuleGroup",
|
|
3219
3326
|
"anchor": "userulegroup",
|
|
3220
3327
|
"text": "### `useRuleGroup`\n\nCalled by the [`RuleGroup`](../components/rulegroup) component. See [source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/hooks/useRuleGroup.ts) for returned properties.\n\n```ts\nfunction useRuleGroup(props: RuleGroupProps): {\n // See source code for returned properties\n};\n```\n\nIts `classNames` and `outerClassName` come from [`deriveRuleGroupClassNames`/`deriveRuleGroupOuterClassName`](./query-management#classnames), and its combinator resolution from `getRuleGroupCombinator` (see [`QueryManager.getRuleGroupContext()`](./query-management#rule-configuration))."
|
|
3221
3328
|
},
|
|
3222
3329
|
{
|
|
3223
|
-
"page":
|
|
3330
|
+
"page": 50,
|
|
3224
3331
|
"heading": "Component logic › useValueEditor",
|
|
3225
3332
|
"anchor": "usevalueeditor",
|
|
3226
3333
|
"text": "### `useValueEditor`\n\nCalled by the [`ValueEditor`](../components/valueeditor) 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.\n\n```ts\nfunction useValueEditor(props: ValueEditorProps): {\n valueAsArray: any[];\n multiValueHandler: (val: string, idx: number) => void;\n parseNumberMethod: ParseNumberMethod;\n valueListItemClassName: string;\n};\n```\n\nThis hook updates the `value` as a side effect when these conditions are true:\n\n- `skipHook` is `false` (the value editors in the [compatibility packages](../compat) set this to `true` to avoid infinite loops)\n- `inputType` is `\"number\"`\n- `operator` is something other than `\"between\"`, `\"notBetween\"`, `\"in\"`, or `\"notIn\"`\n- `valueEditorType` is not \"multiselect\"\n- `value` is an array or a string containing a comma (`,`) and at least one non-whitespace character on either side.\n\nIf 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.\n\nEverything except that side effect is derived by the framework-agnostic [value editor utilities](./query-management#value-editors) from `@react-querybuilder/core`, so an implementation in another framework can reuse the logic without reimplementing it."
|
|
3227
3334
|
},
|
|
3228
3335
|
{
|
|
3229
|
-
"page":
|
|
3336
|
+
"page": 50,
|
|
3230
3337
|
"heading": "Component logic › useValueSelector",
|
|
3231
3338
|
"anchor": "usevalueselector",
|
|
3232
3339
|
"text": "### `useValueSelector`\n\nCalled by the [`ValueSelector`](../components/valueselector) component. Returns the given value as an array (unchanged if already an array) and a memoized change handler.\n\n```ts\nfunction useValueSelector(\n props: Pick<ValueSelectorProps, 'handleOnChange' | 'listsAsArrays' | 'multiple' | 'value'>\n): {\n onChange: (v: string | string[]) => void;\n val?: string | any[];\n};\n```"
|
|
3233
3340
|
},
|
|
3234
3341
|
{
|
|
3235
|
-
"page":
|
|
3342
|
+
"page": 50,
|
|
3236
3343
|
"heading": "Component logic › useSelectElementChangeHandler",
|
|
3237
3344
|
"anchor": "useselectelementchangehandler",
|
|
3238
3345
|
"text": "### `useSelectElementChangeHandler`\n\nUsed by the [`ValueSelector`](../components/valueselector) component. Returns a memoized change handler designed specifically for HTML `<select />` elements.\n\n```ts\nfunction useSelectElementChangeHandler(props: {\n multiple?: boolean;\n onChange: (v: string | string[]) => void;\n}): (e: ChangeEvent<HTMLSelectElement>) => void;\n```"
|
|
3239
3346
|
},
|
|
3240
3347
|
{
|
|
3241
|
-
"page":
|
|
3348
|
+
"page": 50,
|
|
3242
3349
|
"heading": "Component logic › useShiftActions",
|
|
3243
3350
|
"anchor": "useshiftactions",
|
|
3244
3351
|
"text": "### `useShiftActions`\n\nUsed by the [`ShiftActions`](../components/shiftactions) component. Generates `shiftUp` and `shiftDown` methods to move a rule/group up or down in the query hierarchy, plus `shiftUpDisabled`/`shiftDownDisabled` to indicate whether either button should be disabled (`shiftUpDisabled` is `true` for the first rule/group in the root group; `shiftDownDisabled` is `true` for the last rule/group in the root group).\n\n```ts\nfunction useShiftActions(\n props: { path: Path } & Pick<Schema, 'combinators' | 'dispatchQuery' | 'getQuery'>\n): {\n shiftDown: () => void;\n shiftDownDisabled: boolean;\n shiftUp: () => void;\n shiftUpDisabled: boolean;\n};\n```"
|
|
3245
3352
|
},
|
|
3246
3353
|
{
|
|
3247
|
-
"page":
|
|
3354
|
+
"page": 50,
|
|
3248
3355
|
"heading": "Component logic › useStopEventPropagation",
|
|
3249
3356
|
"anchor": "usestopeventpropagation",
|
|
3250
3357
|
"text": "### `useStopEventPropagation`\n\nUsed by the default [`Rule`](../components/rule) and [`RuleGroup`](../components/rulegroup) components to prevent default behavior and stop event propagation (e.g., a `MouseEvent` after clicking a `<button>`). Takes a function that accepts a `MouseEvent` and context parameters, then returns a new function that calls `event.preventDefault()` and `event.stopPropagation()` before calling the original function with the same arguments.\n\n```ts\nfunction useStopEventPropagation(\n method: (event: React.MouseEvent, context: any) => void\n): (event: React.MouseEvent, context: any) => void;\n```\n\nThis hook is _not_ used in `RuleNative` and `RuleGroupNative`—the `Rule` and `RuleGroup` components for `@react-querybuilder/native`."
|
|
3251
3358
|
},
|
|
3252
3359
|
{
|
|
3253
|
-
"page":
|
|
3360
|
+
"page": 50,
|
|
3254
3361
|
"heading": "Component logic › useQueryBuilder",
|
|
3255
3362
|
"anchor": "usequerybuilder",
|
|
3256
3363
|
"text": "### `useQueryBuilder`\n\nReturns everything needed to render a [`QueryBuilder`](../components/querybuilder) component. Internally, this hook passes the result of [`useQueryBuilderSetup`](#usequerybuildersetup) to [`useQueryBuilderSchema`](#usequerybuilderschema) and returns the result of `useQueryBuilderSchema`. The returned object combines the results of `useQueryBuilderSchema` and `useQueryBuilderSetup` (see below).\n\nAs with `useQueryBuilderSchema`, this Hook must be called from a descendant component of `QueryBuilderStateProvider`. See [`QueryBuilder` source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/components/QueryBuilder.tsx) for an example.\n\n**Info:**\n\nThis hook is unlikely to be necessary unless you're reimplementing the _entire_ `QueryBuilder` component structure."
|
|
3257
3364
|
},
|
|
3258
3365
|
{
|
|
3259
|
-
"page":
|
|
3366
|
+
"page": 50,
|
|
3260
3367
|
"heading": "Component logic › useQueryBuilderSetup",
|
|
3261
3368
|
"anchor": "usequerybuildersetup",
|
|
3262
3369
|
"text": "### `useQueryBuilderSetup`\n\nCalled by the internal component rendered by [`QueryBuilder`](../components/querybuilder). Merges props and context values with the defaults and generates actions.\n\n**Info:**\n\nThis hook is unlikely to be necessary unless you're reimplementing the _entire_ `QueryBuilder` component structure.\n\n```ts\nfunction useQueryBuilderSetup(props: QueryBuilderProps): {\n qbId: qbId.current;\n rqbContext: ReturnType<typeof useMergedContext>;\n fields: OptionList<Field>;\n fieldMap: Record<string, Field>;\n combinators: OptionList<Combinator>;\n getOperatorsMain: (field: string) => OptionList<Operator>;\n getRuleDefaultOperator: (field: string) => string;\n getValueEditorTypeMain: (field: string, operator: string) => ValueEditorType;\n getValueSourcesMain: (field: string, operator: string) => ValueSources;\n getValuesMain: (field: string, operator: string) => OptionList;\n getRuleDefaultValue: (rule: RuleType) => any;\n getInputTypeMain: (field: string, operator: string) => string;\n createRule: () => RuleType;\n createRuleGroup: () => RuleGroupTypeAny;\n};\n```"
|
|
3263
3370
|
},
|
|
3264
3371
|
{
|
|
3265
|
-
"page":
|
|
3372
|
+
"page": 50,
|
|
3266
3373
|
"heading": "Component logic › useQueryBuilderSchema",
|
|
3267
3374
|
"anchor": "usequerybuilderschema",
|
|
3268
3375
|
"text": "### `useQueryBuilderSchema`\n\nCalled by the internal component rendered by [`QueryBuilder`](../components/querybuilder). Returns everything needed to render a wrapper element (e.g., `<div>`) and the root [`RuleGroup`](../components/rulegroup) element based on the provided props and the result from [`useQueryBuilderSetup`](#usequerybuildersetup).\n\nThis Hook must be called from a descendant component of `QueryBuilderStateProvider`. See [`QueryBuilder` source code](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/components/QueryBuilder.tsx) for an example.\n\n**Info:**\n\nThis hook is unlikely to be necessary unless you're reimplementing the _entire_ `QueryBuilder` component structure.\n\n```ts\nfunction useQueryBuilderSchema(\n props: QueryBuilderProps,\n setup: ReturnType<typeof useQueryBuilderSetup>\n): QueryBuilderProps & {\n actions: QueryActions;\n rootGroup: RuleGroupTypeAny;\n rootGroupDisabled: RuleGroupTypeAny;\n queryDisabled: boolean;\n rqbContext: ReturnType<typeof useMergedContext>;\n schema: Schema;\n translations: TranslationsFull;\n wrapperClassName: string;\n dndEnabledAttr: 'enabled' | 'disabled';\n inlineCombinatorsAttr: 'enabled' | 'disabled';\n combinatorPropObject: Pick<RuleGroupProps, 'combinator'>;\n};\n```"
|
|
3269
3376
|
},
|
|
3270
3377
|
{
|
|
3271
|
-
"page":
|
|
3378
|
+
"page": 50,
|
|
3272
3379
|
"heading": "Other utilities",
|
|
3273
3380
|
"anchor": "other-utilities",
|
|
3274
3381
|
"text": "## Other utilities"
|
|
3275
3382
|
},
|
|
3276
3383
|
{
|
|
3277
|
-
"page":
|
|
3384
|
+
"page": 50,
|
|
3278
3385
|
"heading": "Other utilities › useAsyncOptionList",
|
|
3279
3386
|
"anchor": "useasyncoptionlist",
|
|
3280
3387
|
"text": "### `useAsyncOptionList`\n\nAugments a `ValueSelectorProps` or `ValueEditorProps` object with [async option loading](../tips/async-option-lists).\n\n```tsx\nimport { type UseAsyncOptionListParams, useAsyncOptionList } from 'react-querybuilder';\n\nconst useAsyncOptionListParams: UseAsyncOptionListParams = {\n getCacheKey: 'field',\n loadOptionList: async (value, { ruleOrGroup }) => {\n const response = await fetch(`/api/operators?field=${ruleOrGroup.field}`);\n return response.json();\n },\n};\n\nconst AsyncOperatorSelector = (props: ValueSelectorProps) => {\n const asyncProps = useAsyncOptionList(props, useAsyncOptionListParams);\n\n return <props.schema.controls.valueSelector {...asyncProps} />;\n};\n\nconst App = () => <QueryBuilder controlElements={{ operatorSelector: AsyncOperatorSelector }} />;\n```"
|
|
3281
3388
|
},
|
|
3282
3389
|
{
|
|
3283
|
-
"page":
|
|
3390
|
+
"page": 50,
|
|
3284
3391
|
"heading": "Other utilities › useMergedContext",
|
|
3285
3392
|
"anchor": "usemergedcontext",
|
|
3286
3393
|
"text": "### `useMergedContext`\n\nMerges the values inherited from the nearest ancestor `QueryBuilderContext.Provider` with the current component's props. For `controlClassnames`, `controlElements`, and `translations`, options that are not defined through either context or props will fall back to the defaults.\n\n```ts\nfunction useMergedContext(props: QueryBuilderContextProps): QueryBuilderContextProps;\n```"
|
|
3287
3394
|
},
|
|
3288
3395
|
{
|
|
3289
|
-
"page":
|
|
3396
|
+
"page": 50,
|
|
3290
3397
|
"heading": "Other utilities › usePreferProp",
|
|
3291
3398
|
"anchor": "usepreferprop",
|
|
3292
3399
|
"text": "### `usePreferProp`\n\nGiven a default value, a prop value, and a context value (all `boolean` or `undefined`), returns the first one that is not `undefined` in the order of (1) prop, (2) context, (3) default.\n\n```ts\nfunction usePreferProp(default: boolean, prop?: boolean, context?: boolean): boolean;\n```"
|
|
3293
3400
|
},
|
|
3294
3401
|
{
|
|
3295
|
-
"page":
|
|
3402
|
+
"page": 50,
|
|
3296
3403
|
"heading": "Other utilities › usePrevious",
|
|
3297
3404
|
"anchor": "useprevious",
|
|
3298
3405
|
"text": "### `usePrevious`\n\nReturns the value of a prop or state variable from the previous render.\n\n```ts\nfunction usePrevious<T>(prop: T): T | null;\n```"
|
|
3299
3406
|
},
|
|
3300
3407
|
{
|
|
3301
|
-
"page":
|
|
3408
|
+
"page": 50,
|
|
3302
3409
|
"heading": "Internal",
|
|
3303
3410
|
"anchor": "internal",
|
|
3304
3411
|
"text": "## Internal\n\nThese hooks log error messages to the console in certain situations (only in \"development\" mode). They encourage correct usage of React Query Builder and aren't intended for use in custom components."
|
|
3305
3412
|
},
|
|
3306
3413
|
{
|
|
3307
|
-
"page":
|
|
3414
|
+
"page": 50,
|
|
3308
3415
|
"heading": "Internal › useControlledOrUncontrolled",
|
|
3309
3416
|
"anchor": "usecontrolledoruncontrolled",
|
|
3310
3417
|
"text": "### `useControlledOrUncontrolled`\n\nLogs an error to the console if any of the following are true:\n\n- Both `query` and `defaultQuery` props are defined.\n- The `query` prop is defined during one render and undefined in a subsequent render.\n- The `query` prop is undefined during one render and defined in a subsequent render."
|
|
3311
3418
|
},
|
|
3312
3419
|
{
|
|
3313
|
-
"page":
|
|
3420
|
+
"page": 50,
|
|
3314
3421
|
"heading": "Internal › useDeprecatedProps",
|
|
3315
3422
|
"anchor": "usedeprecatedprops",
|
|
3316
3423
|
"text": "### `useDeprecatedProps`\n\nLogs an error to the console if any of the following are true:\n\n- `QueryBuilder` is rendered with `independentCombinators` prop (see [Independent combinators](../components/querybuilder#independent-combinators))\n- `RuleGroup` is rendered with `combinator` or `rules` props (deprecated in favor of `ruleGroup`)\n- `Rule` is rendered with `field`, `operator`, or `value` props (deprecated in favor of `rule`)"
|
|
3317
3424
|
},
|
|
3318
3425
|
{
|
|
3319
|
-
"page":
|
|
3426
|
+
"page": 50,
|
|
3320
3427
|
"heading": "Internal › useReactDndWarning",
|
|
3321
3428
|
"anchor": "usereactdndwarning",
|
|
3322
3429
|
"text": "### `useReactDndWarning`\n\nLogs an error to the console if the `enableDragAndDrop` prop is `true` but the `react-dnd` and `react-dnd-html5-backend` dependencies are not loaded."
|
|
3323
3430
|
},
|
|
3324
3431
|
{
|
|
3325
|
-
"page":
|
|
3432
|
+
"page": 51,
|
|
3326
3433
|
"heading": "",
|
|
3327
3434
|
"anchor": "",
|
|
3328
3435
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nImport/parser functions convert query strings or objects from specific languages to query objects for `<QueryBuilder />` components.\n\nThe optional second parameter configures parsing behavior and query generation (see [Configuration](#configuration)).\n\n**Importing `parse*` functions:**\n\nSince `parse*` functions are used less frequently and rarely together, they were removed from the main export in version 7.\n\n```diff\n // Version 6 only\n-import { parseCEL } from '@react-querybuilder/core'\n-import { parseJsonLogic } from '@react-querybuilder/core'\n-import { parseMongoDB } from '@react-querybuilder/core'\n-import { parseSQL } from '@react-querybuilder/core'\n\n // Version 6 or 7\n+import { parseCEL } from '@react-querybuilder/core/parseCEL'\n+import { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic'\n+import { parseMongoDB } from '@react-querybuilder/core/parseMongoDB'\n+import { parseSQL } from '@react-querybuilder/core/parseSQL'\n // (New in version 7)\n+import { parseSpEL } from '@react-querybuilder/core/parseSpEL'\n+import { parseJSONata } from '@react-querybuilder/core/parseJSONata'\n // (New in version 8)\n+import { parseCypher } from '@react-querybuilder/core/parseCypher'\n+import { parseGQL } from '@react-querybuilder/core/parseCypher'\n+import { parseSPARQL } from '@react-querybuilder/core/parseSPARQL'\n+import { parseGremlin } from '@react-querybuilder/core/parseGremlin'\n```\n\nThese functions were available as separate exports in version 6 (along with [`formatQuery`](./export) and [`transformQuery`](./misc#transformquery)) but could also be imported from `\"react-querybuilder\"`. In version 7, they're _only_ available as separate exports. (This reduced the main bundle size by almost 50%.)"
|
|
3329
3436
|
},
|
|
3330
3437
|
{
|
|
3331
|
-
"page":
|
|
3438
|
+
"page": 51,
|
|
3332
3439
|
"heading": "SQL",
|
|
3333
3440
|
"anchor": "sql",
|
|
3334
3441
|
"text": "## SQL\n\n```ts\nimport { parseSQL } from '@react-querybuilder/core/parseSQL';\n\nfunction parseSQL(sql: string, options?: ParseSQLOptions): RuleGroupTypeAny;\n```\n\n`parseSQL` accepts either a SQL `SELECT` statement or `WHERE` clause.\n\nClick the \"Import SQL\" button in [the demo](/demo) to try it out."
|
|
3335
3442
|
},
|
|
3336
3443
|
{
|
|
3337
|
-
"page":
|
|
3444
|
+
"page": 51,
|
|
3338
3445
|
"heading": "SQL › Options",
|
|
3339
3446
|
"anchor": "options",
|
|
3340
3447
|
"text": "### Options\n\nBeyond standard [configuration](#configuration) options, `parseSQL` accepts these options for handling named or anonymous bind variables in SQL strings:\n\n- `params` (`any[] | Record<string, any>`): An array of parameter values or a parameter-to-value mapping object.\n- `paramPrefix` (`string`): Ignores this string at the beginning of parameter identifiers when matching to parameter names in the `params` object.\n- `parseParameters` (`boolean | { prefix?: string | string[]; positional?: boolean }`): Preserves unresolved bind variables as parameter rules (`valueSource: 'parameter'`) instead of resolving them via `params`. Pass `true` to accept the default named prefix `':'` and positional `?`, or an object to configure one or more named `prefix`es (e.g. `'@'`, `'$'`) and toggle `positional` (default enabled). Positional `?` placeholders are named by 1-based ordinal (`?` → `1`). `params` substitution takes precedence when both are set.\n- `getExpression` (`(node, ctx) => ExpressionNode | null`): A handler that converts an arithmetic/function operand subtree into an [expression](../expr) node (`valueSource: 'expression'`, or a `lhs` when on the left of a comparison). Returning `null` drops the rule. Use [`expressionParserSQL`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in SQL inverse, or `getExpressionParserSQL` to add custom functions/operators."
|
|
3341
3448
|
},
|
|
3342
3449
|
{
|
|
3343
|
-
"page":
|
|
3450
|
+
"page": 51,
|
|
3344
3451
|
"heading": "SQL › Usage",
|
|
3345
3452
|
"anchor": "usage",
|
|
3346
3453
|
"text": "### Usage\n\nAll these statements produce the same result:\n\n```ts\nparseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`);\n\nparseSQL(`SELECT * FROM t WHERE firstName = ? AND lastName = ?`, {\n params: ['Steve', 'Vai'],\n});\n\nparseSQL(`SELECT * FROM t WHERE firstName = :p1 AND lastName = :p2`, {\n params: { p1: 'Steve', p2: 'Vai' },\n});\n\nparseSQL(`SELECT * FROM t WHERE firstName = $p1 AND lastName = $p2`, {\n params: { p1: 'Steve', p2: 'Vai' },\n paramPrefix: '$',\n});\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```\n\n**Tip:**\n\nSince v5.0, `parseSQL` detects `XOR` operators and converts them to rule groups with the \"xor\" combinator. Since \"xor\" isn't in `defaultCombinators`, specify `defaultCombinatorsExtended` in your `<QueryBuilder />` props if the original SQL might contain `XOR` clauses.\n\n```ts\nimport { parseSQL } from '@react-querybuilder/core/parseSQL';\nimport { defaultCombinatorsExtended, QueryBuilder } from 'react-querybuilder';\n\nconst query = parseSQL(`SELECT * FROM tbl WHERE a = 'b' XOR c = 'd';`);\n\nconst App = () => {\n return (\n <QueryBuilder\n query={query}\n // highlight-start\n combinators={defaultCombinatorsExtended}\n // highlight-end\n />\n );\n};\n```"
|
|
3347
3454
|
},
|
|
3348
3455
|
{
|
|
3349
|
-
"page":
|
|
3456
|
+
"page": 51,
|
|
3350
3457
|
"heading": "MongoDB",
|
|
3351
3458
|
"anchor": "mongodb",
|
|
3352
3459
|
"text": "## MongoDB\n\n```ts\nimport { parseMongoDB } from '@react-querybuilder/core/parseMongoDB';\n\nfunction parseMongoDB(\n mongoDbQuery: string | Record<string, any>,\n options?: ParseMongoDbOptions\n): RuleGroupTypeAny;\n```\n\n`parseMongoDB` accepts a MongoDB query as either a JSON object or `JSON.parse`-able string.\n\nClick the \"Import MongoDB\" button in [the demo](/demo) to try it out."
|
|
3353
3460
|
},
|
|
3354
3461
|
{
|
|
3355
|
-
"page":
|
|
3462
|
+
"page": 51,
|
|
3356
3463
|
"heading": "MongoDB › Usage",
|
|
3357
3464
|
"anchor": "usage-1",
|
|
3358
3465
|
"text": "### Usage\n\n```ts\nparseMongoDB(`{ \"firstName\": \"Steve\", \"lastName\": { $eq: \"Vai\" } }`);\n// OR\nparseMongoDB({ firstName: 'Steve', lastName: { $eq: 'Vai' } });\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```"
|
|
3359
3466
|
},
|
|
3360
3467
|
{
|
|
3361
|
-
"page":
|
|
3468
|
+
"page": 51,
|
|
3362
3469
|
"heading": "MongoDB › Custom operators",
|
|
3363
3470
|
"anchor": "custom-operators",
|
|
3364
3471
|
"text": "### Custom operators\n\n`parseMongoDB` identifies and processes custom operators with the `additionalOperators` option. This option maps operators to their processing functions. Functions receive the operator, associated value, and other options, then should return `RuleType` or `RuleGroupType`. (Don't return `RuleGroupTypeIC`, even with [independent combinators](../components/querybuilder#independent-combinators). If `independentCombinators` is `true`, `parseMongoDB` converts the final query to `RuleGroupTypeIC` before returning.)\n\nExample:\n\n```ts\nparseMongoDB(\n {\n $myCustomOp: ['Vai', 'Vaughan'],\n },\n {\n additionalOperators: {\n $myCustomOp: (_op, val) => ({\n field: 'lastName',\n operator: 'in',\n value: val,\n }),\n },\n }\n);\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"lastName\",\n \"operator\": \"in\",\n \"value\": [\"Vai\", \"Vaughan\"]\n }\n ]\n}\n```\n\n**Tip:**\n\nValid MongoDB query strings may not strictly conform to JSON. To handle extended formats, pre-parse query strings with a library like [mongodb-query-parser](https://www.npmjs.com/package/mongodb-query-parser) before passing them to `parseMongoDB`."
|
|
3365
3472
|
},
|
|
3366
3473
|
{
|
|
3367
|
-
"page":
|
|
3474
|
+
"page": 51,
|
|
3368
3475
|
"heading": "MongoDB › Expressions",
|
|
3369
3476
|
"anchor": "expressions",
|
|
3370
3477
|
"text": "### Expressions\n\n`parseMongoDB` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts a MongoDB `$expr` aggregation-expression operand into an [expression](../expr) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Only `$expr` operands are routed through this handler; bare field references and literals fall through to the stock logic. Returning `null` drops the rule. Use [`expressionParserMongoDB`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in MongoDB inverse, or `getExpressionParserMongoDB` to add custom operations.\n\n```ts\nimport { parseMongoDB } from '@react-querybuilder/core/parseMongoDB';\nimport { expressionParserMongoDB } from '@react-querybuilder/expr';\n\nconst query = parseMongoDB(mongoDbQuery, { getExpression: expressionParserMongoDB });\n```"
|
|
3371
3478
|
},
|
|
3372
3479
|
{
|
|
3373
|
-
"page":
|
|
3480
|
+
"page": 51,
|
|
3374
3481
|
"heading": "JsonLogic",
|
|
3375
3482
|
"anchor": "jsonlogic",
|
|
3376
3483
|
"text": "## JsonLogic\n\n```ts\nimport { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic';\n\nfunction parseJsonLogic(\n jsonLogic: string | JsonLogic,\n options?: ParseJsonLogicOptions\n): RuleGroupTypeAny;\n```\n\n`parseJsonLogic` accepts a [JsonLogic](https://jsonlogic.com/) object or `JSON.parse`-able string.\n\nClick the \"Import JsonLogic\" button in [the demo](/demo) to try it out."
|
|
3377
3484
|
},
|
|
3378
3485
|
{
|
|
3379
|
-
"page":
|
|
3486
|
+
"page": 51,
|
|
3380
3487
|
"heading": "JsonLogic › Usage",
|
|
3381
3488
|
"anchor": "usage-2",
|
|
3382
3489
|
"text": "### Usage\n\n```ts\nparseJsonLogic(\n `{ \"and\": [{ \"===\": [{ \"var\": \"firstName\" }, \"Steve\"] }, { \"===\": [{ \"var\": \"lastName\" }, \"Vai\"] }] }`\n);\n// OR\nparseJsonLogic({\n and: [{ '===': [{ var: 'firstName' }, 'Steve'] }, { '===': [{ var: 'lastName' }, 'Vai'] }],\n});\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```"
|
|
3383
3490
|
},
|
|
3384
3491
|
{
|
|
3385
|
-
"page":
|
|
3492
|
+
"page": 51,
|
|
3386
3493
|
"heading": "JsonLogic › Expressions",
|
|
3387
3494
|
"anchor": "expressions-1",
|
|
3388
3495
|
"text": "### Expressions\n\n`parseJsonLogic` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](../expr) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserJsonLogic`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in JsonLogic inverse, or `getExpressionParserJsonLogic` to add custom operations.\n\n```ts\nimport { parseJsonLogic } from '@react-querybuilder/core/parseJsonLogic';\nimport { expressionParserJsonLogic } from '@react-querybuilder/expr';\n\nconst query = parseJsonLogic(jsonLogic, { getExpression: expressionParserJsonLogic });\n```"
|
|
3389
3496
|
},
|
|
3390
3497
|
{
|
|
3391
|
-
"page":
|
|
3498
|
+
"page": 51,
|
|
3392
3499
|
"heading": "JsonLogic › Custom operations",
|
|
3393
3500
|
"anchor": "custom-operations",
|
|
3394
3501
|
"text": "### Custom operations\n\nBy default, `parseJsonLogic` handles standard JsonLogic operations that correspond to default React Query Builder operators. Use the `jsonLogicOperations` option to handle custom operations.\n\n`jsonLogicOperations` is `Record<string, (val: any) => RuleType | RuleGroupTypeAny>`. Keys are custom operations; values are functions returning a rule or group.\n\n**Note:**\n\nIncluding standard JsonLogic operations as keys in `jsonLogicOperations` overrides the default `parseJsonLogic` behavior for those operations.\n\nThis example uses a custom \"regex\" operation to produce a rule with the \"contains\" operator, using the regular expression's `source` property as the `value`.\n\n```ts\nparseJsonLogic(\n { regex: [{ var: 'firstName' }, /^Stev/] },\n {\n jsonLogicOperations: {\n regex: val => ({ field: val[0].var, operator: 'contains', value: val[1].source }),\n },\n }\n);\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"contains\",\n \"value\": \"^Stev\"\n }\n ]\n}\n```"
|
|
3395
3502
|
},
|
|
3396
3503
|
{
|
|
3397
|
-
"page":
|
|
3504
|
+
"page": 51,
|
|
3398
3505
|
"heading": "Spring Expression Language (SpEL)",
|
|
3399
3506
|
"anchor": "spring-expression-language-spel",
|
|
3400
3507
|
"text": "## Spring Expression Language (SpEL)\n\n```ts\nimport { parseSpEL } from '@react-querybuilder/core/parseSpEL';\n\nfunction parseSpEL(spelQuery: string, options?: ParseSpELOptions): RuleGroupTypeAny;\n```\n\n`parseSpEL` accepts a [SpEL](https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html) string.\n\nClick the \"Import SpEL\" button in [the demo](/demo) to try it out."
|
|
3401
3508
|
},
|
|
3402
3509
|
{
|
|
3403
|
-
"page":
|
|
3510
|
+
"page": 51,
|
|
3404
3511
|
"heading": "Spring Expression Language (SpEL) › Usage",
|
|
3405
3512
|
"anchor": "usage-3",
|
|
3406
3513
|
"text": "### Usage\n\n```ts\nparseSpEL(`firstName == \"Steve\" && lastName == \"Vai\"`);\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```"
|
|
3407
3514
|
},
|
|
3408
3515
|
{
|
|
3409
|
-
"page":
|
|
3516
|
+
"page": 51,
|
|
3410
3517
|
"heading": "Spring Expression Language (SpEL) › Expressions",
|
|
3411
3518
|
"anchor": "expressions-2",
|
|
3412
3519
|
"text": "### Expressions\n\n`parseSpEL` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic, method call, or function call operand subtree into an [expression](../expr) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserSpEL`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in SpEL inverse, or `getExpressionParserSpEL` to add custom operators, functions, and methods. Infix arithmetic, `T(java.lang.Math).abs/min/max(...)` static calls, and `.toUpperCase()`/`.toLowerCase()` instance methods are all invertible; see the [expr docs](../expr#spel) for details.\n\n```ts\nimport { parseSpEL } from '@react-querybuilder/core/parseSpEL';\nimport { expressionParserSpEL } from '@react-querybuilder/expr';\n\nconst query = parseSpEL(spelQuery, { getExpression: expressionParserSpEL });\n```"
|
|
3413
3520
|
},
|
|
3414
3521
|
{
|
|
3415
|
-
"page":
|
|
3522
|
+
"page": 51,
|
|
3416
3523
|
"heading": "Common Expression Language (CEL)",
|
|
3417
3524
|
"anchor": "common-expression-language-cel",
|
|
3418
3525
|
"text": "## Common Expression Language (CEL)\n\n```ts\nimport { parseCEL } from '@react-querybuilder/core/parseCEL';\n\nfunction parseCEL(celQuery: string, options?: ParseCELOptions): RuleGroupTypeAny;\n```\n\n`parseCEL` accepts a [CEL](https://cel.dev) string.\n\nClick the \"Import CEL\" button in [the demo](/demo) to try it out."
|
|
3419
3526
|
},
|
|
3420
3527
|
{
|
|
3421
|
-
"page":
|
|
3528
|
+
"page": 51,
|
|
3422
3529
|
"heading": "Common Expression Language (CEL) › Usage",
|
|
3423
3530
|
"anchor": "usage-4",
|
|
3424
3531
|
"text": "### Usage\n\n```ts\nparseCEL(`firstName == \"Steve\" && lastName == \"Vai\"`);\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```"
|
|
3425
3532
|
},
|
|
3426
3533
|
{
|
|
3427
|
-
"page":
|
|
3534
|
+
"page": 51,
|
|
3428
3535
|
"heading": "Common Expression Language (CEL) › Custom expressions",
|
|
3429
3536
|
"anchor": "custom-expressions",
|
|
3430
3537
|
"text": "### Custom expressions\n\nProvide a `customExpressionHandler` function to process any AST fragments not recognized by the default parser.\n\nExample:\n\n```ts\nparseCEL('opted_in_at.isBirthday(-1)', {\n customExpressionHandler: expr => ({\n field: expr.left.value,\n operator: expr.right.value,\n value: expr.list.value[0].value,\n }),\n});\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"opted_in_at\",\n \"operator\": \"isBirthday\",\n \"value\": -1\n }\n ]\n}\n```\n\nTo assist with processing the AST fragments, all types, type guard functions, and other utilities used internally by `parseCEL` are exported."
|
|
3431
3538
|
},
|
|
3432
3539
|
{
|
|
3433
|
-
"page":
|
|
3540
|
+
"page": 51,
|
|
3434
3541
|
"heading": "Common Expression Language (CEL) › Expressions",
|
|
3435
3542
|
"anchor": "expressions-3",
|
|
3436
3543
|
"text": "### Expressions\n\n`parseCEL` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](../expr) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserCEL`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in CEL inverse, or `getExpressionParserCEL` to add custom functions. Note that `abs`/`upper`/`lower` are not invertible on the CEL side—see the [expr docs](../expr#cel) for details.\n\n```ts\nimport { parseCEL } from '@react-querybuilder/core/parseCEL';\nimport { expressionParserCEL } from '@react-querybuilder/expr';\n\nconst query = parseCEL(celQuery, { getExpression: expressionParserCEL });\n```"
|
|
3437
3544
|
},
|
|
3438
3545
|
{
|
|
3439
|
-
"page":
|
|
3546
|
+
"page": 51,
|
|
3440
3547
|
"heading": "JSONata",
|
|
3441
3548
|
"anchor": "jsonata",
|
|
3442
3549
|
"text": "## JSONata\n\n```ts\nimport { parseJSONata } from '@react-querybuilder/core/parseJSONata';\n\nfunction parseJSONata(jsonataQuery: string, options?: ParseJSONataOptions): RuleGroupTypeAny;\n```\n\n`parseJSONata` accepts a [JSONata](https://jsonata.org/) string.\n\nClick the \"Import JSONata\" button in [the demo](/demo) to try it out."
|
|
3443
3550
|
},
|
|
3444
3551
|
{
|
|
3445
|
-
"page":
|
|
3552
|
+
"page": 51,
|
|
3446
3553
|
"heading": "JSONata › Usage",
|
|
3447
3554
|
"anchor": "usage-5",
|
|
3448
3555
|
"text": "### Usage\n\n```ts\nparseJSONata(`firstName = \"Steve\" and lastName in [\"Vai\", \"Vaughan\"]`);\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n {\n \"field\": \"lastName\",\n \"operator\": \"in\",\n \"value\": [\"Vai\", \"Vaughan\"]\n }\n ]\n}\n```\n\nJSONata lists are always translated to arrays. The [`listsAsArrays` option](#lists-as-arrays) is ignored (effectively always `true`)."
|
|
3449
3556
|
},
|
|
3450
3557
|
{
|
|
3451
|
-
"page":
|
|
3558
|
+
"page": 51,
|
|
3452
3559
|
"heading": "JSONata › Expressions",
|
|
3453
3560
|
"anchor": "expressions-4",
|
|
3454
3561
|
"text": "### Expressions\n\n`parseJSONata` accepts a `getExpression` option (`(node, ctx) => ExpressionNode | null`) that converts an arithmetic/function operand subtree into an [expression](../expr) node. An expression on the right of a comparison becomes the rule's `value` with `valueSource: 'expression'`; an expression on the left sets `field: ''` and stores the tree in `lhs`; inclusive-`between`/`notBetween` bounds become a two-element `value`. Returning `null` drops the rule. Use [`expressionParserJSONata`](../expr#import-parsing) from `@react-querybuilder/expr` for the built-in JSONata inverse, or `getExpressionParserJSONata` to add custom functions.\n\n```ts\nimport { parseJSONata } from '@react-querybuilder/core/parseJSONata';\nimport { expressionParserJSONata } from '@react-querybuilder/expr';\n\nconst query = parseJSONata(jsonataQuery, { getExpression: expressionParserJSONata });\n```"
|
|
3455
3562
|
},
|
|
3456
3563
|
{
|
|
3457
|
-
"page":
|
|
3564
|
+
"page": 51,
|
|
3458
3565
|
"heading": "Cypher",
|
|
3459
3566
|
"anchor": "cypher",
|
|
3460
3567
|
"text": "## Cypher\n\n```ts\nimport { parseCypher } from '@react-querybuilder/core/parseCypher';\n\nfunction parseCypher(cypherQuery: string, options?: ParseCypherOptions): RuleGroupTypeAny;\n```\n\n`parseCypher` accepts a [Cypher](https://neo4j.com/docs/cypher-manual/) query string, a `WHERE` clause, or a bare boolean expression. MATCH and RETURN clauses are consumed but discarded — only WHERE conditions are returned.\n\nA `parseGQL` function is also exported since [GQL](https://www.iso.org/standard/76120.html) uses the same expression syntax.\n\n**Note:**\n\n`parseCypher` requires the `chevrotain` package (optional peer dependency)."
|
|
3461
3568
|
},
|
|
3462
3569
|
{
|
|
3463
|
-
"page":
|
|
3570
|
+
"page": 51,
|
|
3464
3571
|
"heading": "Cypher › Usage",
|
|
3465
3572
|
"anchor": "usage-6",
|
|
3466
3573
|
"text": "### Usage\n\n```ts\n// Full query — extracts WHERE conditions only\nparseCypher('MATCH (n:Person) WHERE n.age > 30 AND n.name CONTAINS \"Alice\" RETURN n');\n\n// WHERE clause only\nparseCypher('WHERE n.age > 30');\n\n// Bare expression\nparseCypher('n.age > 30 AND n.name CONTAINS \"Alice\"');\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"n.age\", \"operator\": \">\", \"value\": 30 },\n { \"field\": \"n.name\", \"operator\": \"contains\", \"value\": \"Alice\" }\n ]\n}\n```"
|
|
3467
3574
|
},
|
|
3468
3575
|
{
|
|
3469
|
-
"page":
|
|
3576
|
+
"page": 51,
|
|
3470
3577
|
"heading": "SPARQL",
|
|
3471
3578
|
"anchor": "sparql",
|
|
3472
3579
|
"text": "## SPARQL\n\n```ts\nimport { parseSPARQL } from '@react-querybuilder/core/parseSPARQL';\n\nfunction parseSPARQL(sparqlQuery: string, options?: ParseSPARQLOptions): RuleGroupTypeAny;\n```\n\n`parseSPARQL` accepts a [SPARQL](https://www.w3.org/TR/sparql11-query/) query string or a bare `FILTER` expression. Triple patterns (BGPs) are consumed but discarded — only FILTER conditions are returned.\n\n**Note:**\n\n`parseSPARQL` requires the `@traqula/parser-sparql-1-2` package (optional peer dependency)."
|
|
3473
3580
|
},
|
|
3474
3581
|
{
|
|
3475
|
-
"page":
|
|
3582
|
+
"page": 51,
|
|
3476
3583
|
"heading": "SPARQL › Usage",
|
|
3477
3584
|
"anchor": "usage-7",
|
|
3478
3585
|
"text": "### Usage\n\n```ts\n// Full query — extracts FILTER conditions only\nparseSPARQL('SELECT ?x WHERE { ?x foaf:name ?name . FILTER(?age > 30) }');\n\n// Bare FILTER expression (auto-wrapped in a stub query)\nparseSPARQL('?age > 30 && ?name != \"Alice\"');\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"?age\", \"operator\": \">\", \"value\": 30 },\n { \"field\": \"?name\", \"operator\": \"!=\", \"value\": \"Alice\" }\n ]\n}\n```"
|
|
3479
3586
|
},
|
|
3480
3587
|
{
|
|
3481
|
-
"page":
|
|
3588
|
+
"page": 51,
|
|
3482
3589
|
"heading": "Gremlin",
|
|
3483
3590
|
"anchor": "gremlin",
|
|
3484
3591
|
"text": "## Gremlin\n\n```ts\nimport { parseGremlin } from '@react-querybuilder/core/parseGremlin';\n\nfunction parseGremlin(gremlinQuery: string, options?: ParseGremlinOptions): RuleGroupTypeAny;\n```\n\n`parseGremlin` accepts a [Gremlin](https://tinkerpop.apache.org/) traversal string or a chain of `.has()` steps. Pattern steps (`.hasLabel()`, `.out()`, `.in()`, `.as()`) are consumed but discarded — only `.has()` filter predicates are returned."
|
|
3485
3592
|
},
|
|
3486
3593
|
{
|
|
3487
|
-
"page":
|
|
3594
|
+
"page": 51,
|
|
3488
3595
|
"heading": "Gremlin › Usage",
|
|
3489
3596
|
"anchor": "usage-8",
|
|
3490
3597
|
"text": "### Usage\n\n```ts\n// Full traversal — extracts .has() conditions only\nparseGremlin(\"g.V().hasLabel('Person').has('age', gt(30)).has('name', 'Alice')\");\n\n// Bare .has() chain\nparseGremlin(\".has('age', gt(30)).has('name', 'Alice')\");\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n { \"field\": \"age\", \"operator\": \">\", \"value\": 30 },\n { \"field\": \"name\", \"operator\": \"=\", \"value\": \"Alice\" }\n ]\n}\n```"
|
|
3491
3598
|
},
|
|
3492
3599
|
{
|
|
3493
|
-
"page":
|
|
3600
|
+
"page": 51,
|
|
3494
3601
|
"heading": "react-awesome-query-builder",
|
|
3495
3602
|
"anchor": "react-awesome-query-builder",
|
|
3496
3603
|
"text": "## react-awesome-query-builder\n\n`parseRAQB` converts a [react-awesome-query-builder](https://github.com/ukrbublik/react-awesome-query-builder) (RAQB) query tree to an RQB query. It is _not_ part of this package—since most projects need it exactly once, it lives in the separate [`@react-querybuilder/migrate-raqb`](https://github.com/react-querybuilder/migrate-raqb) package.\n\n```bash npm2yarn\nnpm i @react-querybuilder/migrate-raqb\n```\n\n```ts\nimport { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';\n\nconst fields = parseRAQBFields(raqbConfig.fields);\nconst query = parseRAQB(Utils.getTree(immutableTree), { fields });\n```\n\nOutput (`RuleGroupType`):\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [{ \"field\": \"price\", \"operator\": \">\", \"value\": 10 }]\n}\n```\n\nSee [Migrating from react-awesome-query-builder](../tips/migrate-from-raqb) for the full concept mapping, operator translation table, and options."
|
|
3497
3604
|
},
|
|
3498
3605
|
{
|
|
3499
|
-
"page":
|
|
3606
|
+
"page": 51,
|
|
3500
3607
|
"heading": "Configuration",
|
|
3501
3608
|
"anchor": "configuration",
|
|
3502
3609
|
"text": "## Configuration"
|
|
3503
3610
|
},
|
|
3504
3611
|
{
|
|
3505
|
-
"page":
|
|
3612
|
+
"page": 51,
|
|
3506
3613
|
"heading": "Configuration › Lists as arrays",
|
|
3507
3614
|
"anchor": "lists-as-arrays",
|
|
3508
3615
|
"text": "### Lists as arrays\n\nTo generate arrays instead of comma-separated strings for \"in\"- and \"between\"-type operator values, use the `listsAsArrays` option.\n\n```ts\nparseSQL(`SELECT * FROM t WHERE lastName IN ('Vai', 'Vaughan') AND age BETWEEN 20 AND 100`, {\n listsAsArrays: true;\n});\n```\n\nOutput:\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"lastName\",\n \"operator\": \"in\",\n // highlight-start\n \"value\": [\"Vai\", \"Vaughan\"]\n // highlight-end\n },\n {\n \"field\": \"age\",\n \"operator\": \"between\",\n // highlight-start\n \"value\": [20, 100]\n // highlight-end\n }\n ]\n}\n```"
|
|
3509
3616
|
},
|
|
3510
3617
|
{
|
|
3511
|
-
"page":
|
|
3618
|
+
"page": 51,
|
|
3512
3619
|
"heading": "Configuration › Independent combinators",
|
|
3513
3620
|
"anchor": "independent-combinators",
|
|
3514
3621
|
"text": "### Independent combinators\n\nWhen `independentCombinators` is `true`, `parse*` functions output queries with combinator identifiers _between_ sibling rules/groups instead of at the group level.\n\n```ts\nparseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`, {\n independentCombinators: true,\n});\n```\n\nOutput (`RuleGroupTypeIC`):\n\n```json\n{\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"Steve\"\n },\n // highlight-start\n \"and\",\n // highlight-end\n {\n \"field\": \"lastName\",\n \"operator\": \"=\",\n \"value\": \"Vai\"\n }\n ]\n}\n```"
|
|
3515
3622
|
},
|
|
3516
3623
|
{
|
|
3517
|
-
"page":
|
|
3624
|
+
"page": 51,
|
|
3625
|
+
"heading": "Configuration › Regular expressions",
|
|
3626
|
+
"anchor": "regular-expressions",
|
|
3627
|
+
"text": "### Regular expressions\n\nRegex syntax in the source (e.g. JSONata `/re/i`, MongoDB `$regex`, SpEL `matches`, CEL `.matches()`, SQL `REGEXP`/`~`/`REGEXP_LIKE`, Cypher `=~`, SPARQL `REGEX()`, Gremlin `TextP.regex`) is imported as a [`matches`/`doesNotMatch`](../tips/managing-operators#regular-expression-operators) rule, with flags in `regexFlags`, when either:\n\n- the `parseRegex` option is `true`, or\n- `parseRegex` is `undefined` and the field's `operators` (in the `fields` option) include `matches` or `doesNotMatch`.\n\nOtherwise, the previous behavior applies (e.g. `contains` with the pattern source as the value). `parseRegex: false` always disables regex import. The `g` flag is dropped.\n\n```ts\nparseJSONata('$contains(firstName, /^st.v/i)', { parseRegex: true });\n// { combinator: 'and', rules: [{ field: 'firstName', operator: 'matches', value: '^st.v', regexFlags: 'i' }] }\n```"
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
"page": 51,
|
|
3518
3631
|
"heading": "Configuration › Field validation",
|
|
3519
3632
|
"anchor": "field-validation",
|
|
3520
3633
|
"text": "### Field validation\n\nWhen the `fields` option is provided, `parse*` functions drop any rule whose field isn't in the list (or whose operator isn't valid for the field per `getValueSources`/`valueSources`), then drop any group left empty. Use this to whitelist fields when parsing untrusted input.\n\n`parseCypher`, `parseGremlin`, and `parseSPARQL` support `fields` and `getValueSources` for this purpose only (not for \"Fields as value source\" below). Field names are matched exactly as emitted, e.g. `n.age` for Cypher and `?age` for SPARQL.\n\n```ts\nparseCypher('n.age > 30 AND n.secret = 1', { fields: [{ name: 'n.age', label: 'Age' }] });\n// { combinator: 'and', rules: [{ field: 'n.age', operator: '>', value: 30 }] }\n```"
|
|
3521
3634
|
},
|
|
3522
3635
|
{
|
|
3523
|
-
"page":
|
|
3636
|
+
"page": 51,
|
|
3524
3637
|
"heading": "Configuration › Fields as value source",
|
|
3525
3638
|
"anchor": "fields-as-value-source",
|
|
3526
3639
|
"text": "### Fields as value source\n\nWhen the `fields` option is provided (accepting the same types as the [`fields` prop](../components/querybuilder#fields)), `parse*` functions validate clauses with field identifiers to the right of the operator instead of primitive values. A `getValueSources` function (same signature as the [prop](../components/querybuilder#getvaluesources)) can also help validate rules.\n\nFor such rules to be valid, one of these must be an array including \"field\": (1) the `getValueSources` return value, (2) the field's `valueSources` function return value, or (3) the field's `valueSources` property. The code below demonstrates all three methods.\n\n```ts\nparseSQL(`SELECT * FROM t WHERE firstName = lastName`, {\n fields: [\n { name: 'firstName', label: 'First Name', valueSources: ['value', 'field'] },\n { name: 'lastName', label: 'Last Name', valueSources: () => ['value', 'field'] },\n ],\n getValueSources: () => ['value', 'field'],\n});\n```\n\nOutput:\n\n```json\n{\n \"combinator\": \"and\",\n \"rules\": [\n {\n \"field\": \"firstName\",\n \"operator\": \"=\",\n \"value\": \"lastName\",\n // highlight-start\n \"valueSource\": \"field\"\n // highlight-end\n }\n ]\n}\n```"
|
|
3527
3640
|
},
|
|
3528
3641
|
{
|
|
3529
|
-
"page":
|
|
3642
|
+
"page": 51,
|
|
3530
3643
|
"heading": "Configuration › Generating ids",
|
|
3531
3644
|
"anchor": "generating-ids",
|
|
3532
3645
|
"text": "### Generating `id`s\n\nWhen `generateIDs` is `true`, `parse*` functions generate a unique `id` property for the output query object and each nested rule and group using `prepareRuleGroup`.\n\n**Note:**\n\n`parse*` functions only validate clauses where \"field\" is the _only_ detected value source. Operators like \"between\" and \"in\" must have either only field names or only scalar values to the right of the operator—not mixed. See examples below.\n\n#### Invalid clauses\n\n```ts\n// 1 is a scalar value and `iq` is a field name\nparseSQL(`SELECT * FROM tbl WHERE age between 1 and iq`);\n// List contains a mix of scalar values and field names\nparseSQL(`SELECT * FROM tbl WHERE firstName IN (lastName, 'Steve', 'Stevie')`);\n```\n\n#### Valid clauses\n\n```ts\n// Both are field names\nparseSQL(`SELECT * FROM tbl WHERE age between numChildren and iq`);\n// Both are scalar values\nparseSQL(`SELECT * FROM tbl WHERE age between 26 and 52`);\n// All items are field names\nparseSQL(`SELECT * FROM tbl WHERE firstName IN (lastName, middleName)`);\n// All items are scalar values\nparseSQL(`SELECT * FROM tbl WHERE firstName IN ('Steve', 'Stevie')`);\n```"
|
|
3533
3646
|
},
|
|
3534
3647
|
{
|
|
3535
|
-
"page":
|
|
3648
|
+
"page": 52,
|
|
3536
3649
|
"heading": "",
|
|
3537
3650
|
"anchor": "",
|
|
3538
3651
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._"
|
|
3539
3652
|
},
|
|
3540
3653
|
{
|
|
3541
|
-
"page":
|
|
3654
|
+
"page": 52,
|
|
3542
3655
|
"heading": "getQueryJsonSchema",
|
|
3543
3656
|
"anchor": "getqueryjsonschema",
|
|
3544
3657
|
"text": "## `getQueryJsonSchema`\n\n```ts\nfunction getQueryJsonSchema(\n fields: FlexibleOptionListProp<FullField> | BaseOptionMap<FullField>,\n options?: GetQueryJsonSchemaOptions\n): JSONSchema;\n```\n\n`getQueryJsonSchema` generates a JSON Schema that describes valid queries for a `fields` config. You can use it for:\n\n- LLM structured output (OpenAI `response_format`, the AI SDK's `generateObject`, etc.)\n- LLM tool input schemas (Anthropic `input_schema`, MCP `inputSchema`)\n- Validating query objects with a generic JSON Schema validator like [Ajv](https://ajv.js.org/)\n\nIt's available from `@react-querybuilder/core` and `react-querybuilder`, or from the `/getQueryJsonSchema` subpath of either package.\n\n```ts\nimport type { Field } from '@react-querybuilder/core';\nimport { getQueryJsonSchema } from '@react-querybuilder/core';\n// or: import { getQueryJsonSchema } from '@react-querybuilder/core/getQueryJsonSchema';\n\nconst fields: Field[] = [\n { name: 'firstName', label: 'First name', operators: ['=', 'beginsWith'] },\n { name: 'age', label: 'Age', inputType: 'number' },\n {\n name: 'color',\n label: 'Color',\n valueEditorType: 'select',\n values: [\n { name: 'red', label: 'Red' },\n { name: 'blue', label: 'Blue' },\n ],\n },\n];\n\nconst schema = getQueryJsonSchema(fields, { strict: true });\n```"
|
|
3545
3658
|
},
|
|
3546
3659
|
{
|
|
3547
|
-
"page":
|
|
3660
|
+
"page": 52,
|
|
3548
3661
|
"heading": "getQueryJsonSchema › Schema shape",
|
|
3549
3662
|
"anchor": "schema-shape",
|
|
3550
3663
|
"text": "### Schema shape\n\nBy default, the schema describes a `RuleGroupType` (set `queryType: 'ic'` for `RuleGroupTypeIC`). Groups and rules are defined in `$defs` and reference each other recursively:\n\n```json\n{\n \"$defs\": {\n \"group\": {\n \"type\": \"object\",\n \"properties\": {\n \"combinator\": { \"type\": \"string\", \"enum\": [\"and\", \"or\"] },\n \"rules\": {\n \"type\": \"array\",\n \"items\": { \"anyOf\": [{ \"$ref\": \"#/$defs/rule\" }, { \"$ref\": \"#/$defs/group\" }] }\n },\n \"not\": { \"type\": \"boolean\" }\n },\n \"required\": [\"combinator\", \"rules\"]\n },\n \"rule\": { \"anyOf\": [\"...one branch per field and operator class...\"] }\n },\n \"$ref\": \"#/$defs/group\"\n}\n```\n\nIn the default `'full'` mode, each field's rule schema is split into branches by operator class (unary, binary, `between`, `in`). Each branch restricts `field` to the field's name and `operator` to the matching operators (`field.operators`, falling back to the `operators` option). The `value` type comes from the field:\n\n| Field config | `value` schema |\n| ------------------------------------------------- | --------------------------------------------------- |\n| `values` (any editor except `text`/`textarea`) | `enum` of value names (option groups flattened) |\n| `valueEditorType: 'multiselect'` | Array of the above |\n| `valueEditorType: 'checkbox'` or `'switch'` | `boolean` |\n| `inputType: 'number'` | `number` |\n| `inputType: 'date'`, `'datetime-local'`, etc. | `string`, with the expected format in `description` |\n| `between`/`notBetween` operators | Array of exactly 2 items (`minItems`/`maxItems`) |\n| `in`/`notIn` operators | Array of items |\n| `null`/`notNull` (and other unary operators) | Ignored (optional) |\n| `valueSource: 'field'` (when allowed for a field) | `enum` of comparable field names (see `comparator`) |\n| Anything else | `string` |\n\nField `label`s are added as `description`s."
|
|
3551
3664
|
},
|
|
3552
3665
|
{
|
|
3553
|
-
"page":
|
|
3666
|
+
"page": 52,
|
|
3554
3667
|
"heading": "getQueryJsonSchema › Options",
|
|
3555
3668
|
"anchor": "options",
|
|
3556
3669
|
"text": "### Options\n\n| Option | Type | Default | Description |\n| -------------------------- | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `queryType` | `'standard' \\| 'ic'` | `'standard'` | `RuleGroupType` or `RuleGroupTypeIC` |\n| `mode` | `'full' \\| 'loose'` | `'full'` | `'loose'` only restricts `field` and `operator` names. Use it when the full schema gets too big (roughly 50+ fields). |\n| `strict` | `boolean` | `false` | Provider compatibility: `additionalProperties: false` everywhere, all properties required (optional ones become nullable), and the root is an object. |\n| `maxDepth` | `'ref' \\| number` | `'ref'` | `'ref'` uses recursive `$ref`s. A number inlines that many levels of nested groups with no `$ref`/`$defs`, for consumers that don't support recursion. |\n| `includeIds` | `boolean` | `false` | Include `id` and `path` properties. Off by default so LLMs don't invent ids. |\n| `allowFieldValueSource` | `boolean` | Derived from fields | `true` allows `valueSource: 'field'` for every field, `false` for none. By default, each field's `valueSources` decide. |\n| `includeNot` | `boolean` | `true` | Include `not` on groups |\n| `combinators` | `OptionList` | `defaultCombinators` | Allowed combinators |\n| `operators` | `OptionList` | `defaultOperators` | Operators for fields without their own `operators` |\n| `descriptions` | `boolean` | `true` | Add field `label`s as `description`s |\n| `listsAsArrays` | `boolean` | `true` | List values (`between`, `in`, multiselect) as arrays. `false` expects comma-separated strings. Should match `formatQuery`'s `listsAsArrays`. |\n| `includeSchemaDeclaration` | `boolean` | `false` | Add `\"$schema\"` (draft-07). Off by default because some providers reject it. |"
|
|
3557
3670
|
},
|
|
3558
3671
|
{
|
|
3559
|
-
"page":
|
|
3672
|
+
"page": 52,
|
|
3560
3673
|
"heading": "getQueryJsonSchema › Compatibility",
|
|
3561
3674
|
"anchor": "compatibility",
|
|
3562
3675
|
"text": "### Compatibility\n\nThe schema only uses keywords that mean the same thing in JSON Schema draft-07 and 2020-12: `type`, `properties`, `required`, `enum`, `anyOf`, `items` (single schema), `minItems`/`maxItems`, `additionalProperties`, `description`, `$ref`, and `$defs`. It never uses `oneOf`, `allOf`, `not`, `const`, `format`, `pattern`, or tuple keywords, since provider support for those is inconsistent.\n\n| Consumer | Notes |\n| ------------------------- | ----------------------------------------------------------- |\n| OpenAI structured outputs | Requires `strict: true`. Recursion via `$ref` is supported. |\n| Anthropic tools | `$ref`/`$defs` supported. Root must be an object. |\n| Gemini | Use a numeric `maxDepth` if `$ref` is rejected. |\n| AI SDK `jsonSchema()` | Passed through to the provider |\n| MCP `inputSchema` | Root must be an object, so use `strict` or `maxDepth`. |\n| Ajv (draft-07 and 2020) | All modes |\n\n**Tip:**\n\nWhen in doubt, use `strict: true`. It works with every consumer above."
|
|
3563
3676
|
},
|
|
3564
3677
|
{
|
|
3565
|
-
"page":
|
|
3678
|
+
"page": 52,
|
|
3566
3679
|
"heading": "getQueryJsonSchema › Usage with LLMs",
|
|
3567
3680
|
"anchor": "usage-with-llms",
|
|
3568
3681
|
"text": "### Usage with LLMs\n\nThese examples are illustrative. Check each provider's current docs for details.\n\n**AI SDK**\n\n```ts\nimport { generateObject, jsonSchema } from 'ai';\nimport { getQueryJsonSchema } from '@react-querybuilder/core';\n\nconst { object: query } = await generateObject({\n model,\n schema: jsonSchema<RuleGroupType>(getQueryJsonSchema(fields, { strict: true })),\n prompt: 'Musicians over 30 named Steve',\n});\n```\n\n**OpenAI (structured outputs)**\n\n```ts\nconst response = await openai.chat.completions.create({\n model,\n messages,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'query',\n strict: true,\n schema: getQueryJsonSchema(fields, { strict: true }),\n },\n },\n});\n```\n\n**Anthropic (tool input)**\n\n```ts\nconst message = await anthropic.messages.create({\n model,\n max_tokens: 1024,\n messages,\n tools: [\n {\n name: 'set_filter',\n description: 'Set the query builder filter',\n input_schema: getQueryJsonSchema(fields, { strict: true }),\n },\n ],\n});\n```\n\nIn strict mode, optional properties like `not` and `valueSource` are nullable instead of omitted, so the output may contain `null`s. Formatting functions treat those the same as missing properties."
|
|
3569
3682
|
},
|
|
3570
3683
|
{
|
|
3571
|
-
"page":
|
|
3684
|
+
"page": 52,
|
|
3572
3685
|
"heading": "getQueryJsonSchema › Validate the result with diagnostics",
|
|
3573
3686
|
"anchor": "validate-the-result-with-diagnostics",
|
|
3574
3687
|
"text": "### Validate the result with `diagnostics`\n\nThe schema is necessary but not sufficient. Some constraints can't be expressed with the compatible keyword subset:\n\n- Independent combinator alternation (rule/group, combinator, rule/group, ...). The IC schema only allows rules, groups, and combinator strings in any order.\n- With `listsAsArrays: false`, the number of comma-separated values for `between`/`in`.\n- In `'loose'` mode, which operators and values are valid for each field.\n\nAlways check LLM output (and any other untrusted query) with the [`diagnostics` export format](./export#diagnostics) before using it:\n\n```ts\nimport { formatQuery } from '@react-querybuilder/core';\n\nconst { diagnostics } = formatQuery(query, { format: 'diagnostics', fields });\nconst problems = diagnostics.filter(d => d.code !== 'UNREFERENCED_FIELD');\nif (problems.length > 0) {\n // Reject, or send `problems` back to the LLM to fix\n}\n```\n\nWhen `fields` is provided, `diagnostics` reports unknown fields (`UNDEFINED_FIELD`), operators not in `field.operators` (`INVALID_OPERATOR`), wrong `between`/`in` value counts (`INVALID_VALUE_ARITY`), values missing from `field.values` (`VALUE_NOT_IN_OPTIONS`), and values that don't match the field's type (`VALUE_TYPE_MISMATCH`). It reports broken IC alternation (`INVALID_STRUCTURE`) with or without `fields`.\n\n**Caution:**\n\nA schema-valid query is not automatically safe to run. See [Quote field names](./export#quote-field-names) and always whitelist fields, operators, and combinators on the server."
|
|
3575
3688
|
},
|
|
3576
3689
|
{
|
|
3577
|
-
"page":
|
|
3690
|
+
"page": 53,
|
|
3578
3691
|
"heading": "",
|
|
3579
3692
|
"anchor": "",
|
|
3580
3693
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nA partial list of exports from `react-querybuilder`."
|
|
3581
3694
|
},
|
|
3582
3695
|
{
|
|
3583
|
-
"page":
|
|
3696
|
+
"page": 53,
|
|
3584
3697
|
"heading": "Utilities",
|
|
3585
3698
|
"anchor": "utilities",
|
|
3586
3699
|
"text": "## Utilities"
|
|
3587
3700
|
},
|
|
3588
3701
|
{
|
|
3589
|
-
"page":
|
|
3702
|
+
"page": 53,
|
|
3590
3703
|
"heading": "Utilities › transformQuery",
|
|
3591
3704
|
"anchor": "transformquery",
|
|
3592
3705
|
"text": "### `transformQuery`\n\n```ts\nfunction transformQuery(query: RuleGroupTypeAny, options: QueryTransformerOptions): any;\n```\n\nThis function recursively processes a query object (`RuleGroupType` or `RuleGroupTypeIC`), passing each `RuleType` object to a provided `ruleProcessor` function. Available options include:\n\n- `ruleProcessor`: Custom processing function for each rule.\n- `ruleGroupProcessor`: Custom processing function for each rule group. Each group's `rules` property is retained and recursively processed regardless of other mutations.\n- `propertyMap`: Keys in rule or group objects that match keys in this object are renamed to the corresponding value.\n- `combinatorMap`: Translates combinators; for example, `{and: \"&&\", or: \"||\"}` would convert \"and\"/\"or\" combinators to \"&&\"/\"||\", respectively.\n- `operatorMap`: Converts operators that match keys in this object to corresponding values, e.g., `{\"=\": \"==\"}`.\n- `deleteRemappedProperties`: Defaults to `true`; pass `false` to retain both remapped properties _and_ original properties in the resulting object.\n\nSee the [test suite](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/core/src/utils/transformQuery.test.ts) for example usage."
|
|
3593
3706
|
},
|
|
3594
3707
|
{
|
|
3595
|
-
"page":
|
|
3708
|
+
"page": 53,
|
|
3596
3709
|
"heading": "Utilities › defaultValidator",
|
|
3597
3710
|
"anchor": "defaultvalidator",
|
|
3598
3711
|
"text": "### `defaultValidator`\n\n```ts\nfunction defaultValidator(query: RuleGroupTypeAny): {\n [id: string]: { valid: boolean; reasons?: string[] };\n};\n```\n\nPass `validator={defaultValidator}` to automatically validate groups (rules are ignored). A group is marked invalid when either of these conditions are true:\n\n1. The group has no child rules or groups (`query.rules.length === 0`)\n2. The group has a missing/invalid `combinator` property and more than one child rule or group (`rules.length >= 2`)\n\nYou can see the default validator in action in the [demo](/demo) by checking the <DemoLink option=\"validateQuery\" text=\"'Use validation' option\" />. Empty groups display bold text on their \"+ Rule\" button and a description where rules normally appear."
|
|
3599
3712
|
},
|
|
3600
3713
|
{
|
|
3601
|
-
"page":
|
|
3714
|
+
"page": 53,
|
|
3602
3715
|
"heading": "Utilities › findPath",
|
|
3603
3716
|
"anchor": "findpath",
|
|
3604
3717
|
"text": "### `findPath`\n\n```ts\nfunction findPath(path: Path, query: RuleGroupTypeAny): RuleType | RuleGroupTypeAny | null;\n```\n\n`findPath` finds the rule or group within the query hierarchy that has a given `path`. This is useful in custom [`onAddRule`](../components/querybuilder#onaddrule) and [`onAddGroup`](../components/querybuilder#onaddgroup) functions.\n\nMore extensive documentation on the `path` property is [here](../tips/path)."
|
|
3605
3718
|
},
|
|
3606
3719
|
{
|
|
3607
|
-
"page":
|
|
3720
|
+
"page": 53,
|
|
3608
3721
|
"heading": "Utilities › findID",
|
|
3609
3722
|
"anchor": "findid",
|
|
3610
3723
|
"text": "### `findID`\n\n```ts\nfunction findID(id: string, query: RuleGroupTypeAny): RuleType | RuleGroupTypeAny | null;\n```\n\n`findID` finds the rule or group within the query hierarchy that has a given `id`."
|
|
3611
3724
|
},
|
|
3612
3725
|
{
|
|
3613
|
-
"page":
|
|
3726
|
+
"page": 53,
|
|
3614
3727
|
"heading": "Utilities › convertQuery",
|
|
3615
3728
|
"anchor": "convertquery",
|
|
3616
3729
|
"text": "### `convertQuery`\n\n```ts\nfunction convertQuery(query: RuleGroupType): RuleGroupTypeIC;\n// OR\nfunction convertQuery(query: RuleGroupTypeIC): RuleGroupType;\n```\n\n`convertQuery` toggles a query between the conventional `RuleGroupType` structure (with combinators at the group level) and the \"independent combinators\" structure `RuleGroupTypeIC` (with combinators between every other rule/group).\n\n`convertToIC` and `convertFromIC` perform the same function as `convertQuery` but only in the directions indicated by their names."
|
|
3617
3730
|
},
|
|
3618
3731
|
{
|
|
3619
|
-
"page":
|
|
3732
|
+
"page": 53,
|
|
3620
3733
|
"heading": "Number parsing",
|
|
3621
3734
|
"anchor": "number-parsing",
|
|
3622
3735
|
"text": "## Number parsing\n\nHTML `<input>` controls store values as strings (even for `type=\"number\"`), but your requirements may call for true numeric values. The [`parseNumbers`](../components/querybuilder#parsenumbers) prop on the `QueryBuilder` component and the `formatQuery` function's [`parseNumbers`](./export#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.\n\n`boolean | \"enhanced\" | \"enhanced-limited\" | \"native\" | \"native-limited\" | \"strict\" | \"strict-limited\"`\n\n> _Tip: Try the **`\"strict-limited\"`** option first._\n\n- 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\"`.\n - Being based on the `inputType`, the `\"*-limited\"` suffix can only affect `formatQuery` output when a `fields` array is also provided.\n- `true`, `\"strict\"`, and `\"enhanced\"` will retain the original value when numeric parsing fails.\n- `\"enhanced\"` and `\"native\"` can lead to information loss since any trailing invalid characters will be removed.\n- `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`.\n- `\"enhanced\"` uses `numeric-quantity` with `allowTrailingInvalid: true`.\n- `\"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.\n\n#### Examples\n\n```ts\nconst query: RuleGroupType = {\n combinator: 'and',\n not: false,\n rules: [\n { field: 'digits', operator: '=', value: '20' },\n { field: 'age', operator: 'between', value: '26, 52' },\n { field: 'lastName', operator: '=', value: 'Vai' },\n ],\n};\n\n// Default configuration - all values are strings:\nformatQuery(query, { format: 'sql' });\n// \"(digits = '20' and age between '26' and '52' and lastName = 'Vai')\"\n\n// `parseNumbers: true` - numeric strings converted to actual numbers:\nformatQuery(query, { format: 'sql', parseNumbers: true });\n// \"(digits = 20 and age between 26 and 52 and lastName = 'Vai')\"\n```\n\n**More about the \"strict\" option:**\n\nTo 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).\n\nEach of the following expressions evaluates to `true`:\n\n```ts\n// Everything after the '3' is ignored by `parseFloat`\nparseFloat('000123abcdef') === 123;\n\n// `value` contains non-numeric characters, so remains as-is\nformatQuery(\n { rules: [{ field: 'f', operator: '=', value: '000123abcdef' }] },\n { format: 'sql', parseNumbers: true }\n) === \"(f = '000123abcdef')\";\n\n// `value` is wholly numeric (after trimming whitespace) so it gets converted to a number\nformatQuery(\n { rules: [{ field: 'f', operator: '=', value: ' 000123 ' }] },\n { format: 'sql', parseNumbers: true }\n) === '(f = 123)';\n```"
|
|
3623
3736
|
},
|
|
3624
3737
|
{
|
|
3625
|
-
"page":
|
|
3738
|
+
"page": 53,
|
|
3626
3739
|
"heading": "Defaults",
|
|
3627
3740
|
"anchor": "defaults",
|
|
3628
3741
|
"text": "## Defaults\n\nThe default configuration objects are exported for convenience, including the following.\n\n- `defaultCombinators` (see [`combinators` prop](../components/querybuilder#combinators))\n- `defaultOperators` (see [`operators` prop](../components/querybuilder#operators))\n- `defaultTranslations` (see [`translations` prop](../components/querybuilder#translations))\n- `defaultValueProcessor` and variants for non-SQL formats (see [Export](./export) > [Value processor](./export#value-processor))\n- `defaultFields` (see [`fields` prop](../components/querybuilder#fields))\n- `standardClassnames` (see [CSS classes](../styling/classnames))\n\nThe default components are also exported:\n\n- [`ActionElement`](../components/actionelement) - used for action buttons (to add rules, remove groups, etc.)\n- [`DragHandle`](../components/draghandle) - used for the drag handle on rules and group headers\n- [`InlineCombinator`](../components/rulegroup) - used when either [`showCombinatorsBetweenRules`](../components/querybuilder#showcombinatorsbetweenrules) is `true` or the query is using independent combinators.\n- [`NotToggle`](../components/nottoggle) - used for the \"Invert this group\" toggle switch\n- [`Rule`](../components/rule) - the default rule component\n- [`RuleGroup`](../components/rulegroup) - the default rule group component\n- [`ShiftActions`](../components/shiftactions) - used for the \"shift up\"/\"shift down\" buttons when [`showShiftActions`](../components/querybuilder#showshiftactions) is `true`\n- [`ValueEditor`](../components/valueeditor) - the default `valueEditor` component\n- [`ValueSelector`](../components/valueselector) - used for drop-down lists (combinator, field, and operator selectors)"
|
|
3629
3742
|
},
|
|
3630
3743
|
{
|
|
3631
|
-
"page":
|
|
3744
|
+
"page": 54,
|
|
3632
3745
|
"heading": "",
|
|
3633
3746
|
"anchor": "",
|
|
3634
3747
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\nUtilities for building and modifying query objects programmatically, without the\n`<QueryBuilder />` component."
|
|
3635
3748
|
},
|
|
3636
3749
|
{
|
|
3637
|
-
"page":
|
|
3750
|
+
"page": 54,
|
|
3638
3751
|
"heading": "Query tools",
|
|
3639
3752
|
"anchor": "query-tools",
|
|
3640
3753
|
"text": "## Query tools\n\nSeveral methods are available to assist with programmatic manipulation of query objects. These methods are used by the `<QueryBuilder />` component itself, so they're guaranteed to achieve the same result as a corresponding UI-based update. Each method returns the modified query.\n\nCheck out the [\"External controls\" Tips & Tricks page](../tips/external-controls) to see these methods used outside the `<QueryBuilder />` component context.\n\nFor a stateful, chainable wrapper around these methods that holds the query for you, see [`QueryManager`](#query-manager) below."
|
|
3641
3754
|
},
|
|
3642
3755
|
{
|
|
3643
|
-
"page":
|
|
3756
|
+
"page": 54,
|
|
3644
3757
|
"heading": "Query tools › add",
|
|
3645
3758
|
"anchor": "add",
|
|
3646
3759
|
"text": "### `add`\n\n`(query: RuleGroupTypeAny, ruleOrGroup: RuleGroupTypeAny | RuleType, parentPathOrID: Path | string, options: AddOptions) => RuleGroupTypeAny`\n\nAdds 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`.\n\n```ts\nexport interface AddOptions extends QueryToolOptions {\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query has independent\n * combinators), then the first combinator in this list will be inserted\n * before the new rule/group if the parent group is not empty. This option\n * is overridden by `combinatorPreceding`.\n */\n combinators?: OptionList;\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query has independent\n * combinators), then this combinator will be inserted before the new rule/group\n * if the parent group is not empty. This option will supersede `combinators`.\n */\n combinatorPreceding?: string;\n /**\n * ID generator.\n */\n idGenerator?: () => string;\n}\n```"
|
|
3647
3760
|
},
|
|
3648
3761
|
{
|
|
3649
|
-
"page":
|
|
3762
|
+
"page": 54,
|
|
3650
3763
|
"heading": "Query tools › remove",
|
|
3651
3764
|
"anchor": "remove",
|
|
3652
3765
|
"text": "### `remove`\n\n`(query: RuleGroupTypeAny, pathOrID: Path | string, options: RemoveOptions) => RuleGroupTypeAny`\n\nRemoves a rule or group (and the preceding independent combinator if one exists) at the specified `path` or with the given `id`."
|
|
3653
3766
|
},
|
|
3654
3767
|
{
|
|
3655
|
-
"page":
|
|
3768
|
+
"page": 54,
|
|
3656
3769
|
"heading": "Query tools › update",
|
|
3657
3770
|
"anchor": "update",
|
|
3658
3771
|
"text": "### `update`\n\n`(query: RuleGroupTypeAny, prop: string, value: any, pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`\n\nUpdates a property of a rule or group, or an independent combinator, at the specified `path` or with the given `id`.\n\nMultiple 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):\n\n`(query: RuleGroupTypeAny, props: UpdateValueMap, pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`\n\n`(query: RuleGroupTypeAny, props: string[], values: any[], pathOrID: Path | string, options: UpdateOptions) => RuleGroupTypeAny`\n\n```ts\n// Equivalent ways to set `valueSource` and `value` in a single call:\nupdate(query, { valueSource: 'field', value: 'otherField' }, rulePath);\nupdate(query, ['valueSource', 'value'], ['field', 'otherField'], rulePath);\n```\n\nRegardless 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`).\n\n```ts\nexport interface UpdateOptions extends QueryToolOptions {\n /**\n * When updating the `field` of a rule, the rule's `operator`, `value`, and `valueSource`\n * will be reset to their respective defaults. Defaults to `true`.\n */\n resetOnFieldChange?: boolean;\n /**\n * When updating the `operator` of a rule, the rule's `value` and `valueSource`\n * will be reset to their respective defaults. Defaults to `false`.\n */\n resetOnOperatorChange?: boolean;\n /**\n * Determines the default operator name for a given field.\n */\n getRuleDefaultOperator?: (field: string) => string;\n /**\n * Determines the valid value sources for a given field and operator.\n */\n getValueSources?: (field: string, operator: string) => ValueSources | ValueSourceFlexibleOptions;\n /**\n * Gets the default value for a given rule, in case the value needs to be reset.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n getRuleDefaultValue?: (rule: RuleType) => any;\n /**\n * Determines the valid match modes for a given field.\n */\n getMatchModes?: (field: string) => MatchModeOptions;\n}\n```"
|
|
3659
3772
|
},
|
|
3660
3773
|
{
|
|
3661
|
-
"page":
|
|
3774
|
+
"page": 54,
|
|
3662
3775
|
"heading": "Query tools › move",
|
|
3663
3776
|
"anchor": "move",
|
|
3664
3777
|
"text": "### `move`\n\n`(query: RuleGroupTypeAny, oldPathOrID: Path | string, newPath: Path | 'up' | 'down', options: MoveOptions) => RuleGroupTypeAny`\n\nMoves (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.\n\n```ts\nexport interface MoveOptions extends QueryToolOptions {\n /**\n * When `true`, the source rule/group will not be removed from its original path.\n */\n clone?: boolean;\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query is using independent\n * combinators), then the first combinator in this list will be inserted before\n * the rule/group if necessary.\n */\n combinators?: OptionList;\n /**\n * ID generator.\n */\n idGenerator?: () => string;\n}\n```"
|
|
3665
3778
|
},
|
|
3666
3779
|
{
|
|
3667
|
-
"page":
|
|
3780
|
+
"page": 54,
|
|
3668
3781
|
"heading": "Query tools › insert",
|
|
3669
3782
|
"anchor": "insert",
|
|
3670
3783
|
"text": "### `insert`\n\n`(query: RuleGroupTypeAny, ruleOrGroup: RuleGroupTypeAny | RuleType, path: Path, options: InsertOptions) => RuleGroupTypeAny`\n\nInserts a rule or group (and an independent combinator if necessary to keep the query valid) at the specified `path`.\n\n```ts\nexport interface InsertOptions extends QueryToolOptions {\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query has independent\n * combinators), then the first combinator in this list will be inserted\n * before the new rule/group if the parent group is not empty. This option\n * is overridden by `combinatorPreceding`.\n */\n combinators?: OptionList;\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query has independent\n * combinators), then this combinator will be inserted before the new rule/group\n * if the parent group is not empty and the new rule/group is not the first in the\n * group (`path.at(-1) > 0`). This option will supersede `combinators`.\n */\n combinatorPreceding?: string;\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query has independent\n * combinators), then this combinator will be inserted after the new rule/group\n * if the parent group is not empty and the new rule/group is the first in the\n * group (`path.at(-1) === 0`). This option will supersede `combinators`.\n */\n combinatorSucceeding?: string;\n /**\n * ID generator.\n *\n * @default generateID\n */\n idGenerator?: () => string;\n /**\n * When `true`, the new rule/group will replace the rule/group at `path`.\n */\n replace?: boolean;\n}\n```"
|
|
3671
3784
|
},
|
|
3672
3785
|
{
|
|
3673
|
-
"page":
|
|
3786
|
+
"page": 54,
|
|
3674
3787
|
"heading": "Query tools › ungroup",
|
|
3675
3788
|
"anchor": "ungroup",
|
|
3676
3789
|
"text": "### `ungroup`\n\n`(query: RuleGroupTypeAny, pathOrID: Path | string, options: UngroupOptions) => RuleGroupTypeAny`\n\nReplaces the group at the specified `path` or with the given `id` with its own rules in the parent group. The group's `combinator`, `not`, `id`, `muted`, and `disabled` properties are discarded; its rules keep their `id`s. In an independent combinators query, the promoted rules keep their combinators, preserving the parent's alternation. An empty group is simply removed, along with the adjacent independent combinator if one exists.\n\n```ts\nexport interface UngroupOptions extends QueryToolOptions {}\n\nexport interface UngroupMethod {\n <RG extends RuleGroupTypeAny>(\n /** The query to update. */\n query: RG,\n /** The path or ID of the group to ungroup. */\n pathOrID: Path | string,\n /** Options. */\n options?: UngroupOptions\n ): RG;\n}\n```"
|
|
3677
3790
|
},
|
|
3678
3791
|
{
|
|
3679
|
-
"page":
|
|
3792
|
+
"page": 54,
|
|
3680
3793
|
"heading": "Query tools › group",
|
|
3681
3794
|
"anchor": "group",
|
|
3682
3795
|
"text": "### `group`\n\n`(query: RuleGroupTypeAny, sourcePathOrID: Path | string, targetPathOrID: Path | string, options: GroupOptions) => RuleGroupTypeAny`\n\nCreates 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.\n\n```ts\nexport interface GroupOptions extends QueryToolOptions {\n /**\n * When `true`, the source rule/group will not be removed from its original path.\n */\n clone?: boolean;\n /**\n * If the query extends `RuleGroupTypeIC` (i.e. the query is using independent\n * combinators), then the first combinator in this list will be inserted between\n * the two rules/groups.\n */\n combinators?: OptionList;\n /**\n * ID generator.\n */\n idGenerator?: () => string;\n}\n```"
|
|
3683
3796
|
},
|
|
3684
3797
|
{
|
|
3685
|
-
"page":
|
|
3798
|
+
"page": 54,
|
|
3686
3799
|
"heading": "Query tools › Aborted operations",
|
|
3687
3800
|
"anchor": "aborted-operations",
|
|
3688
3801
|
"text": "### Aborted operations\n\nQuery 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.\n\nPass `onAbort` (accepted by every query tool) to find out which happened and why:\n\n```ts\nconst newQuery = remove(query, 'some-id', {\n onAbort: ({ reason, operation, pathOrID }) => {\n console.warn(`${operation} aborted: ${reason}`, pathOrID);\n },\n});\n```\n\n```ts\nexport interface AbortOptions extends GuardOptions {\n /**\n * Called when the operation returns the query unmodified, with the reason why. Query tools\n * never throw, so this is the only way to distinguish \"the target was invalid\" from\n * \"the operation had nothing to do\".\n */\n onAbort?: (info: AbortInfo) => void;\n}\n```\n\n`reason` is one of:\n\n| `AbortReason` | Meaning |\n| ------------------------- | --------------------------------------------------------------------- |\n| `\"target-not-found\"` | The rule/group identified by the given path or `id` does not exist. |\n| `\"parent-not-found\"` | The parent group identified by the given path or `id` does not exist. |\n| `\"parent-not-a-group\"` | The given parent path or `id` refers to a rule rather than a group. |\n| `\"target-not-a-group\"` | The given path or `id` refers to a rule rather than a group. |\n| `\"destination-not-found\"` | The destination's parent group does not exist. |\n| `\"root-not-allowed\"` | The root group cannot be removed, moved, grouped, or ungrouped. |\n| `\"not-a-combinator-slot\"` | In an independent combinators query, the target index holds a rule. |\n| `\"same-location\"` | The rule/group is already at the destination. _Not an error._ |\n| `\"no-change\"` | The property already has the given value. _Not an error._ |\n| `\"target-disabled\"` | The target is disabled, or descends from a disabled group. |\n| `\"parent-disabled\"` | The parent group is disabled, or descends from a disabled group. |\n| `\"max-levels-exceeded\"` | Adding the group would nest it deeper than `maxLevels` allows. |\n\n[`QueryManager`](#query-manager)'s [strict mode](#strict-mode) builds on this channel."
|
|
3689
3802
|
},
|
|
3690
3803
|
{
|
|
3691
|
-
"page":
|
|
3804
|
+
"page": 54,
|
|
3692
3805
|
"heading": "Query tools › Guards",
|
|
3693
3806
|
"anchor": "guards",
|
|
3694
3807
|
"text": "### Guards\n\nThe last three reasons above are produced by opt-in guards, accepted by every query tool alongside `onAbort`:\n\n- `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`.\n- `queryDisabled` — abort every mutation, as though the whole query were disabled. Defaults to `false`.\n- `maxLevels` — the maximum depth at which a _group_ may be added by `add` or `insert`. Rules are unaffected. Defaults to `Infinity`.\n\n`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.\n\nUpdating 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.\n\n```ts\n// Aborts: the rule at [0] is disabled\nupdate(query, 'value', 'x', [0], { respectDisabled: true });\n\n// Succeeds: re-enabling is always allowed\nupdate(query, 'disabled', false, [0], { respectDisabled: true });\n```\n\n`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."
|
|
3695
3808
|
},
|
|
3696
3809
|
{
|
|
3697
|
-
"page":
|
|
3810
|
+
"page": 54,
|
|
3698
3811
|
"heading": "Query tools › Freezing (tools)",
|
|
3699
3812
|
"anchor": "freezing-tools",
|
|
3700
3813
|
"text": "### Freezing (tools)\n\nThe 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:\n\n```ts\nconst nextQuery = add(query, rule, [], { freeze: false });\nObject.isFrozen(nextQuery); // false\n```\n\nStructural sharing is unaffected, so reference comparison still works. The `*InPlace` variants never freeze anything, so the option is inert for them.\n\n`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."
|
|
3701
3814
|
},
|
|
3702
3815
|
{
|
|
3703
|
-
"page":
|
|
3816
|
+
"page": 54,
|
|
3704
3817
|
"heading": "Query manager",
|
|
3705
3818
|
"anchor": "query-manager",
|
|
3706
3819
|
"text": "## Query manager\n\n`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.\n\n```ts\nimport { QueryManager } from 'react-querybuilder';\n// or: import { QueryManager } from '@react-querybuilder/core';\n\nconst q = new QueryManager(undefined, { fields });\n\nq.add({ field: 'firstName', operator: '=', value: 'Steve' })\n .add({ field: 'lastName', operator: '=', value: 'Vai' })\n .update('operator', 'contains', 'rule-id-1');\n\nq.validate(); // boolean | ValidationMap\nq.format('sql'); // string\nq.getQuery(); // RuleGroupType\n```"
|
|
3707
3820
|
},
|
|
3708
3821
|
{
|
|
3709
|
-
"page":
|
|
3822
|
+
"page": 54,
|
|
3710
3823
|
"heading": "Query manager › Constructor",
|
|
3711
3824
|
"anchor": "constructor",
|
|
3712
3825
|
"text": "### Constructor\n\n`new QueryManager(query?: RuleGroupTypeAny, options?: QueryManagerOptions)`\n\nWithout 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.\n\n```ts\nexport interface QueryManagerOptions<\n F extends FullField = FullField,\n O extends FullOperator = FullOperator,\n C extends FullCombinator = FullCombinator,\n> {\n /** The fields available for rules. Accepts the same shapes as the `fields` prop. */\n fields?: FlexibleOptionListProp<F> | BaseOptionMap<F>;\n /** The operators available for rules. Accepts the same shapes as the `operators` prop. */\n operators?: FlexibleOptionListProp<O> | BaseOptionMap<O>;\n /** The combinators available for groups. Defaults to `defaultCombinators`. */\n combinators?: FlexibleOptionListProp<C> | BaseOptionMap<C>;\n /** Properties applied to every field in `fields`. */\n baseField?: Record<string, unknown>;\n /** Properties applied to every operator in `operators`. */\n baseOperator?: Record<string, unknown>;\n /** Properties applied to every combinator in `combinators`. */\n baseCombinator?: Record<string, unknown>;\n /** When `false`, an empty placeholder option is prepended to the field list. */\n autoSelectField?: boolean;\n /** When `false`, an empty placeholder option is prepended to each operator list. */\n autoSelectOperator?: boolean;\n /** When `false`, an empty placeholder option is prepended to each value list. */\n autoSelectValue?: boolean;\n /**\n * Translations, accepting the same shape as the `translations` prop. Only the placeholder\n * properties of `fields`, `operators`, and `values` are used, and only when the corresponding\n * `autoSelect*` option is `false`; the remaining keys describe UI elements that have no\n * meaning outside the `QueryBuilder` component. Labels are typed as `unknown` so the React\n * `Translations` type, whose labels are `ReactNode`, can be passed as-is.\n */\n translations?: Partial<BaseTranslations<unknown>>;\n /** The default `field` for rules created by {@link QueryManager.createRule}. */\n getDefaultField?: DefaultFieldProp<F>;\n /** The default `operator` for a given field. */\n getDefaultOperator?: DefaultOperatorProp<F>;\n /** Overrides the computed default `value` for a new rule. */\n getDefaultValue?: (rule: RuleType, misc: { fieldData: F }) => unknown;\n /** The operators available for a given field. */\n getOperators?: (field: string, misc: { fieldData: F }) => FlexibleOptionList<O> | null;\n /** The value editor type for a given field/operator, which informs the default value. */\n getValueEditorType?: (field: string, operator: string, misc: { fieldData: F }) => ValueEditorType;\n /** The value list for a given field/operator, which informs the default value. */\n getValues?: (\n field: string,\n operator: string,\n misc: { fieldData: F }\n ) => FlexibleOptionList<Option>;\n /** The valid value sources for a given field/operator. */\n getValueSources?: (\n field: string,\n operator: string,\n misc: { fieldData: F }\n ) => ValueSources | ValueSourceFlexibleOptions;\n /** The valid match modes for a given field. */\n getMatchModes?: (\n field: string,\n misc: { fieldData: F }\n ) => boolean | MatchMode[] | FlexibleOption<MatchMode>[];\n /** The named parameters available for a given field/operator. */\n getParameters?: (\n field: string,\n operator: string,\n misc: { fieldData: F }\n ) => FlexibleOptionList<Option> | null;\n /** When `true`, multi-value defaults are arrays instead of comma-joined strings. */\n listsAsArrays?: boolean;\n /** When `true`, groups created by {@link QueryManager.createRuleGroup} contain one new rule. */\n addRuleToNewGroups?: boolean;\n /**\n * When updating a rule's `field`, reset its `operator`, `value`, and `valueSource` to their\n * defaults. Defaults to `true`, matching the `QueryBuilder` prop of the same name.\n */\n resetOnFieldChange?: boolean;\n /**\n * When updating a rule's `operator`, reset its `value` to the default. Defaults to `false`,\n * matching the `QueryBuilder` prop of the same name.\n */\n resetOnOperatorChange?: boolean;\n /**\n * The maximum depth at which groups may be added. As with the `QueryBuilder` prop of the same\n * name, a non-positive value means unlimited. Defaults to `Infinity`.\n */\n maxLevels?: number;\n /**\n * Honor `disabled` properties within the query, so mutations targeting a disabled rule or\n * group (or a descendant of a disabled group) are aborted. Updating a node's own `disabled`\n * property is always permitted. Defaults to `true`, matching the `QueryBuilder` component;\n * pass `false` to mutate freely regardless of the property.\n */\n respectDisabled?: boolean;\n /**\n * Paths that are disabled without the corresponding rule or group carrying a `disabled`\n * property. This mirrors the array form of the `QueryBuilder` `disabled` prop\n * (e.g. `disabled={[[2]]}`), which disables nodes by position rather than by data.\n *\n * A path is treated as disabled if it appears here or descends from a path that does. Honored\n * only when `respectDisabled` is `true`; as with the `disabled` property, a node's own\n * `disabled` can always be changed so it is never permanently locked.\n */\n disabledPaths?: Path[];\n /** Abort every mutation, as though the entire query were disabled. Defaults to `false`. */\n queryDisabled?: boolean;\n /** The input type for a given field/operator, surfaced by {@link QueryManager.getRuleContext}. */\n getInputType?: (field: string, operator: string, misc: { fieldData: F }) => InputType | null;\n /** Extra props for a subquery builder, surfaced by {@link QueryManager.getRuleContext}. */\n getSubQueryBuilderProps?: (field: string, misc: { fieldData: F }) => Record<string, unknown>;\n /**\n * Enables undo/redo recording. Pass `true` for the defaults, or an object to configure\n * `maxHistory` and/or `coalesceMs`. Disabled by default, so instances that never undo\n * retain no extra references.\n */\n history?: boolean | QueryHistoryOptions;\n /**\n * Throw a {@link QueryManagerError} when a mutation is aborted because its target could not\n * be used. Disabled by default, in which case such mutations are silent no-ops. Can be\n * overridden per call.\n */\n strict?: boolean;\n /**\n * Called whenever a mutation is aborted, including for the non-error reasons\n * `\"same-location\"` and `\"no-change\"`. Can be overridden per call.\n */\n onInvalidTarget?: (info: AbortInfo) => void;\n /**\n * Deep-freeze everything the manager hands out—the query, the field list, the field map, and\n * the combinator list—so accidental mutation throws in strict mode. Defaults to `true`.\n *\n * Pass `false` when the manager's output is handed to a framework that wraps objects in\n * proxies (Vue `reactive`, Solid stores) or otherwise needs to mutate them. This also disables\n * immer's auto-freeze for mutations made through the manager.\n *\n * The shallow copy returned by {@link QueryManager.getOptions} is frozen either way: it is a\n * one-level copy that no framework proxy is placed inside, so freezing it costs nothing and\n * still prevents callers from mutating the options snapshot they were handed.\n */\n freeze?: boolean;\n /** Validates the query. Defaults to {@link defaultValidator}. */\n validator?: QueryValidator;\n /** Generates `id` properties for new rules and groups. Defaults to {@link generateID}. */\n idGenerator?: () => string;\n /**\n * Clock used to time history coalescing. Defaults to `Date.now`.\n *\n * @internal Test seam. Exists so that history recording can be compared against the\n * `react-querybuilder/history` implementation without depending on wall-clock timing.\n */\n now?: () => number;\n}\n```\n\nThe constructor also accepts the [guard options](#guards) `respectDisabled` (defaulting to **`true`** here, matching the `QueryBuilder` component), `queryDisabled`, and `maxLevels`, plus `resetOnFieldChange` (default `true`) and `resetOnOperatorChange` (default `false`), which mirror the props of the same names."
|
|
3713
3826
|
},
|
|
3714
3827
|
{
|
|
3715
|
-
"page":
|
|
3828
|
+
"page": 54,
|
|
3716
3829
|
"heading": "Query manager › Freezing (manager)",
|
|
3717
3830
|
"anchor": "freezing-manager",
|
|
3718
3831
|
"text": "### Freezing (manager)\n\nEverything the manager hands out directly—the query, the field list, the field map, and the combinator list—is deep-frozen. Pass `freeze: false` to disable that, along with immer's auto-freeze for mutations made through the manager:\n\n```ts\nconst q = new QueryManager(query, { fields, freeze: false });\nObject.isFrozen(q.getQuery()); // false\n```\n\nThis is what makes a manager's output usable with a framework that wraps objects in proxies. The one exception is `getOptions()`, which is a one-level copy that no proxy is placed inside; it stays frozen either way, so callers can't mutate the options snapshot they were handed.\n\nNote that under `freeze: false`, \"a query previously handed out by `getQuery` is never modified\" remains true—mutations still go through the non-`InPlace` tools—but it is a convention rather than a runtime-enforced guarantee."
|
|
3719
3832
|
},
|
|
3720
3833
|
{
|
|
3721
|
-
"page":
|
|
3834
|
+
"page": 54,
|
|
3722
3835
|
"heading": "Query manager › State access",
|
|
3723
3836
|
"anchor": "state-access",
|
|
3724
3837
|
"text": "### State access\n\n- `getQuery(): RuleGroupTypeAny` — The current query. The returned object is structurally shared, so it's safe to retain and compare by reference to detect changes. It's also frozen unless the [`freeze` option](#freezing-manager) is `false`.\n- `setQuery(query: RuleGroupTypeAny)` — Replaces the current query, assigning `id`s as needed."
|
|
3725
3838
|
},
|
|
3726
3839
|
{
|
|
3727
|
-
"page":
|
|
3840
|
+
"page": 54,
|
|
3728
3841
|
"heading": "Query manager › Factories",
|
|
3729
3842
|
"anchor": "factories",
|
|
3730
3843
|
"text": "### Factories\n\n- `createRule(): RuleType` — Creates a rule using the configured fields, operators, and defaults, applying the same precedence rules as the `<QueryBuilder />` component.\n- `createRuleGroup(independentCombinators?: boolean): RuleGroupTypeAny` — Creates a group.\n\nNeither method adds anything to the query; pass the result to `add` or `insert`."
|
|
3731
3844
|
},
|
|
3732
3845
|
{
|
|
3733
|
-
"page":
|
|
3846
|
+
"page": 54,
|
|
3734
3847
|
"heading": "Query manager › Mutation",
|
|
3735
3848
|
"anchor": "mutation",
|
|
3736
3849
|
"text": "### Mutation\n\nEach method below delegates to the query tool of the same name, using the manager's configuration (`combinators`, `idGenerator`, and the default resolvers) as the default options. Per-call options take precedence.\n\n- `add(ruleOrGroup, parentPathOrID?, options?)` — Unlike the `add` query tool, `parentPathOrID` is optional and defaults to the root group.\n- `remove(pathOrID, options?)`\n- `update(prop, value, pathOrID, options?)`, `update(props, values, pathOrID, options?)`, or `update(propsMap, pathOrID, options?)` — All three argument forms of the `update` query tool are supported.\n- `move(oldPathOrID, newPath, options?)`\n- `insert(ruleOrGroup, path, options?)` — Like the `insert` query tool, this accepts a path only, not an `id`.\n- `group(sourcePathOrID, targetPathOrID, options?)`\n- `ungroup(pathOrID, options?)`\n\n**Note:**\n\nAs with the query tools themselves, these methods are a no-op when the target path or `id` can't be resolved—including attempts to remove the root group. Nothing is thrown by default, and mutators always return the manager, so compare `getQuery()` by reference if you need to know whether a call had any effect. See [strict mode](#strict-mode) to turn these into errors instead."
|
|
3737
3850
|
},
|
|
3738
3851
|
{
|
|
3739
|
-
"page":
|
|
3852
|
+
"page": 54,
|
|
3740
3853
|
"heading": "Query manager › Validation and export",
|
|
3741
3854
|
"anchor": "validation-and-export",
|
|
3742
3855
|
"text": "### Validation and export\n\n- `validate(): boolean | ValidationMap` — Validates the current query with the configured `validator`, defaulting to [`defaultValidator`](./misc#defaultvalidator).\n- `format(options?)` — Passes the current query to [`formatQuery`](./export). Accepts everything `formatQuery` accepts as its second parameter, including the format-name shorthand (`q.format('sql')`), and has the same return types."
|
|
3743
3856
|
},
|
|
3744
3857
|
{
|
|
3745
|
-
"page":
|
|
3858
|
+
"page": 54,
|
|
3746
3859
|
"heading": "Query manager › Cloning",
|
|
3747
3860
|
"anchor": "cloning",
|
|
3748
3861
|
"text": "### Cloning\n\n`clone(options?: { regenerateIDs?: boolean })` returns an independent manager with the same configuration and the current query. Subscribers and history are not carried over. Because every mutation produces a new query object, the two managers share the initial query safely and diverge from the first change. Pass `{ regenerateIDs: true }` to give every rule and group in the clone a new `id`, which is useful when both queries will be used together."
|
|
3749
3862
|
},
|
|
3750
3863
|
{
|
|
3751
|
-
"page":
|
|
3864
|
+
"page": 54,
|
|
3752
3865
|
"heading": "Query manager › Reconfiguration",
|
|
3753
3866
|
"anchor": "reconfiguration",
|
|
3754
3867
|
"text": "### Reconfiguration\n\n`reconfigure(options: Partial<QueryManagerOptions>, config?: { replace?: boolean })` updates the manager's configuration in place, keeping the current query, the undo/redo history, and every subscriber. Use it to propagate new `translations`, `fields`, `operators`, and so on without discarding state:\n\n```ts\nq.reconfigure({ translations: { fields: { placeholderLabel: 'Choisir un champ' } } });\n```\n\nThe incoming options are shallow-merged over the current ones, so keys left out are preserved. Passing a key explicitly as `undefined` resets it to its default. Object-valued options like `translations` are replaced wholesale rather than deep-merged—spread the current value in yourself to patch a single key. Pass `{ replace: true }` to discard the existing options entirely and start from the incoming set.\n\nRelated methods:\n\n- `getOptions(): QueryManagerOptions` — The options currently in effect, as a frozen shallow copy. This one is frozen even under `freeze: false`; see [Freezing](#freezing-manager).\n- `getConfigVersion(): number` — A counter incremented only when a `reconfigure` call changes the effective configuration; calls that resolve to the options already in effect leave it unchanged. Bound to the instance, so it can be passed directly to React's `useSyncExternalStore` alongside `subscribe`. The [`useQueryManager`](./hooks#usequerymanager) hook already does this.\n\n**Note:**\n\n`reconfigure` never rewrites the query, even when the new options no longer describe it—a rule whose `field` is not in the new `fields` list is left as-is. Call [`validate()`](#validation-and-export) to detect that, or `setQuery(getQuery())` to re-normalize.\n\nA call that resolves to the configuration already in effect is a **no-op**: nothing is re-derived, `getConfigVersion()` does not change, and subscribers are not notified. Equality is structural for data and by identity for functions (see [`optionsEqual`](./framework-adapters#configuration)), so rebuilding the options object on every render—which every framework adapter does—does not force a reconfigure as long as the data is the same. Rebuilding a callback per render _does_ count as a change; memoize it to avoid that. The comparison is against the merged options, or against the replacement under `{ replace: true }`, so `reconfigure({})` behaves like `reconfigure(getOptions())`.\n\nOtherwise subscribers are notified once and `getConfigVersion()` is incremented. Inside a [batch](#batching) the options are applied immediately—configuration is not part of a batch's rollback—but the notification is deferred and merged into the batch's single notification. History options are honored immediately: lowering `maxHistory` trims the undo stack, and turning history off clears both stacks."
|
|
3755
3868
|
},
|
|
3756
3869
|
{
|
|
3757
|
-
"page":
|
|
3870
|
+
"page": 54,
|
|
3758
3871
|
"heading": "Query manager › Subscriptions",
|
|
3759
3872
|
"anchor": "subscriptions",
|
|
3760
3873
|
"text": "### Subscriptions\n\n`subscribe(listener: (change: SubscriptionChange) => void)` registers a listener called after every change to the query or the configuration, and returns a function that unregisters it. Mutations that resolve to a no-op do not notify, and a [batch](#batching) notifies once no matter how many changes it contains.\n\nThe listener receives a `SubscriptionChange`—`{ query: boolean; config: boolean }`, at least one of which is always `true`—describing what changed. A framework adapter can use it to skip work a given change does not affect (re-deriving option lists on a query-only change, say) instead of diffing the manager's output to find out. The argument is optional: a zero-argument listener, including `useSyncExternalStore`'s `onStoreChange`, is a valid listener.\n\n| Notification source | `change` |\n| ---------------------------------------- | -------------------------------- |\n| Any mutation, `setQuery`, `undo`, `redo` | `{ query: true, config: false }` |\n| `reconfigure` | `{ query: false, config: true }` |\n| A batch containing both | `{ query: true, config: true }` |\n\nThe method is bound to the instance, so it is a stable reference across renders and can be passed directly to React's `useSyncExternalStore`. `getQuery` is bound as well, so it can serve as the snapshot getter without a wrapper:\n\n```ts\nconst query = useSyncExternalStore(q.subscribe, q.getQuery);\n```\n\nIn React, prefer the [`useQueryManager`](./hooks#usequerymanager) hook, which wraps this and handles creating the manager exactly once."
|
|
3761
3874
|
},
|
|
3762
3875
|
{
|
|
3763
|
-
"page":
|
|
3876
|
+
"page": 54,
|
|
3764
3877
|
"heading": "Query manager › Batching",
|
|
3765
3878
|
"anchor": "batching",
|
|
3766
3879
|
"text": "### Batching\n\n`batch(fn: () => void)` runs `fn`, deferring history recording and subscriber notification until it returns. The whole batch becomes a single undo step and triggers a single notification, or neither if the query ends up unchanged. Batches may be nested; only the outermost one commits.\n\nIf `fn` throws, the query and its history are restored to their pre-batch state and the error propagates, so a batch either applies completely or not at all.\n\n[`undo()`, `redo()`, and `clearHistory()`](#undoredo) may be called inside a batch, and their notifications are deferred like everything else. Because they manage the history stacks themselves, a batch containing one of them records no entry of its own, leaving the stacks exactly as those methods left them.\n\n```ts\nq.batch(() => {\n q.add(rule1);\n q.add(rule2);\n q.add(rule3);\n}); // one notification, one undo step\n```"
|
|
3767
3880
|
},
|
|
3768
3881
|
{
|
|
3769
|
-
"page":
|
|
3882
|
+
"page": 54,
|
|
3770
3883
|
"heading": "Query manager › Strict mode",
|
|
3771
3884
|
"anchor": "strict-mode",
|
|
3772
3885
|
"text": "### Strict mode\n\nBy default, a mutation whose target can't be used is a silent no-op (see [aborted operations](#aborted-operations)). Set `strict: true` to raise a `QueryManagerError` instead:\n\n```ts\nconst q = new QueryManager(query, { fields, strict: true });\n\nq.remove('no-such-id'); // throws QueryManagerError\n```\n\nThe error carries a `code` (the [`AbortReason`](#aborted-operations)) and the full `info` object, so failures can be handled by kind rather than by message:\n\n```ts\ntry {\n q.remove(id);\n} catch (error) {\n if (error instanceof QueryManagerError && error.code === 'target-not-found') {\n // ...\n }\n}\n```\n\nThe two non-error reasons—`\"same-location\"` and `\"no-change\"`—never throw, so idempotent code (setting a value to what it already is, or dropping a rule where it started) keeps working under `strict`.\n\nTo observe aborted operations without changing control flow, use `onInvalidTarget`. It is called for _every_ abort, including the non-error reasons, and runs before any `strict` throw, so an operation can be both observed and enforced:\n\n```ts\nconst q = new QueryManager(query, {\n fields,\n onInvalidTarget: ({ reason, operation }) => logger.warn(`${operation}: ${reason}`),\n});\n```\n\nBoth options can be overridden per call, which takes precedence over the manager's own configuration:\n\n```ts\nq.remove(id, { strict: true }); // just this call\nq.remove(id, { strict: false }); // opt out of a manager-wide strict setting\n```\n\nA `strict` throw inside [`batch`](#batching) rolls the whole batch back."
|
|
3773
3886
|
},
|
|
3774
3887
|
{
|
|
3775
|
-
"page":
|
|
3888
|
+
"page": 54,
|
|
3776
3889
|
"heading": "Query manager › Undo/redo",
|
|
3777
3890
|
"anchor": "undoredo",
|
|
3778
3891
|
"text": "### Undo/redo\n\nRecording is opt-in through the `history` option, so instances that never undo retain no extra references. Pass `true` for the defaults, or an object to configure `maxHistory` (default `50`) and/or `coalesceMs` (default `500`).\n\n```ts\nconst q = new QueryManager(undefined, { fields, history: true });\n```\n\n- `undo()` / `redo()` — Restore the previous or most recently undone query. No-ops when the corresponding `can*` method returns `false`. Both notify subscribers.\n- `canUndo(): boolean` / `canRedo(): boolean`\n- `clearHistory()` — Discards all history without changing the current query.\n- `getHistory(): { past, future }` — Copies of the recorded queries, `past` oldest first and `future` newest first.\n\n`undo()`, `redo()`, and `clearHistory()` may be called inside a [batch](#batching); see [Batching](#batching) for how that interacts with the batch's own history entry.\n\nRecording follows the same semantics as the [`react-querybuilder/history`](../tips/undo-redo) entry point: consecutive changes to the same property of the same rule within `coalesceMs` are merged into a single undo step (so typing a value produces one entry rather than one per keystroke), structural changes never coalesce, and changes that alter no observable property are not recorded at all."
|
|
3779
3892
|
},
|
|
3780
3893
|
{
|
|
3781
|
-
"page":
|
|
3894
|
+
"page": 54,
|
|
3782
3895
|
"heading": "Query manager › Traversal",
|
|
3783
3896
|
"anchor": "traversal",
|
|
3784
3897
|
"text": "### Traversal\n\n`walk(options?)` is a generator yielding every rule and group in the query, depth-first in pre-order, starting with the root group itself. Combinator strings in independent-combinator queries are skipped.\n\nEach entry is a `QueryNode`:\n\n```ts\ninterface QueryNode {\n node: RuleGroupTypeAny | RuleType;\n path: Path;\n parent: RuleGroupTypeAny | null; // `null` for the root group\n}\n```\n\n```ts\nfor (const { node, path } of q.walk({ rulesOnly: true })) {\n console.log(path, node.field);\n}\n```\n\n`WalkOptions`:\n\n| Option | Description |\n| ------------ | ----------------------------------------------------------------------------------------------------------- |\n| `from` | Traverse only the subtree at this `Path` or `id`, visiting that node first. Yields nothing if unresolvable. |\n| `rulesOnly` | Visit only rules. Groups are still traversed, just not yielded (except as `parent`). |\n| `groupsOnly` | Visit only groups. |\n\nDerived methods:\n\n- `rules(options?)` / `groups(options?)` — Shorthand for `walk` with `rulesOnly`/`groupsOnly`.\n- `find(predicate, options?): QueryNode | null` — The first matching node.\n- `filter(predicate, options?): QueryNode[]` — All matching nodes.\n- `[Symbol.iterator]()` — Equivalent to `walk()` with no options, so a manager can be spread (`[...q]`) or used directly in `for...of`.\n\nTraversal operates on the query as it was when iteration began, so mutating the manager mid-iteration does not affect a walk already in progress. Because generators are lazy, that snapshot is taken on the first iteration step rather than when `walk()` is called."
|
|
3785
3898
|
},
|
|
3786
3899
|
{
|
|
3787
|
-
"page":
|
|
3900
|
+
"page": 54,
|
|
3788
3901
|
"heading": "Query manager › Lookups",
|
|
3789
3902
|
"anchor": "lookups",
|
|
3790
3903
|
"text": "### Lookups\n\nThese mirror the standalone [`findPath`](./misc#findpath) and [`findID`](./misc#findid) utilities, minus the trailing `query` parameter.\n\n- `findPath(path: Path)` — The rule or group at `path`.\n- `findID(id: string)` — The rule or group with the given `id`.\n- `getPathOfID(id: string): Path | null` — The path of the rule or group with the given `id`.\n- `pathIsDisabled(path: Path): boolean` — Whether the node at `path` is disabled, either itself or by an ancestor group.\n\nMethods that accept _either_ a path or an `id`, and narrow the result:\n\n- `getNode(pathOrID)` — The rule or group.\n- `getRule(pathOrID): RuleType | null` — `null` if the target is a group.\n- `getGroup(pathOrID): RuleGroupTypeAny | null` — `null` if the target is a rule.\n- `getParent(pathOrID): RuleGroupTypeAny | null` — The containing group, or `null` for the root group.\n\nUnlike the standalone `findPath`, which can return `undefined` for an out-of-range index, all of these normalize an unresolvable target to `null`.\n\n`findID`, `getPathOfID`, and the `pathOrID` forms above are backed by an `id`-to-`Path` index built once per query, so repeated lookups are constant time rather than a tree walk. The index is rebuilt automatically whenever the query changes, including via `undo`, `redo`, and a rolled-back `batch`.\n\n**Note:**\n\n`validate()` results are cached against the current query for the same reason. A custom `validator` with side effects, or one that depends on anything other than the query, may therefore run fewer times than expected."
|
|
3791
3904
|
},
|
|
3792
3905
|
{
|
|
3793
|
-
"page":
|
|
3906
|
+
"page": 54,
|
|
3794
3907
|
"heading": "Query manager › Rule configuration",
|
|
3795
3908
|
"anchor": "rule-configuration",
|
|
3796
3909
|
"text": "### Rule configuration\n\nThese methods resolve the same field/operator configuration the `QueryBuilder` component uses, applying the same precedence rules (field-level properties first, then the corresponding `get*` option, then the manager-level defaults).\n\n- `getFields(): FullOptionList<FullField>` — The normalized field list, for populating a field selector.\n- `getCombinators(): FullOptionList<FullCombinator>` — The normalized combinator list, for populating a combinator selector.\n- `getFieldData(field): FullField` — The configured field. For an unconfigured field, returns the same minimal fallback (`{ name, value, label }`, all set to the field name) that `getRuleContext` reports as `fieldData`.\n\n`getFields` and `getCombinators` return frozen arrays (unless the [`freeze` option](#freezing-manager) is `false`), so they are safe to hand to rendering code without defensive copying. Either way, treat them as read-only.\n\n- `getOperators(field): FullOptionList<FullOperator>` — The operator list for a field.\n- `getValueSources(field, operator): ValueSourceFullOptions` — The available value sources.\n- `getMatchModes(field): MatchModeOptions` — The available match modes.\n- `getValues(field, operator): FullOptionList<Option>` — The value option list.\n- `getValueEditorType(field, operator): ValueEditorType` — The value editor type.\n\nWhen `autoSelectField`, `autoSelectOperator`, or `autoSelectValue` is `false`, an empty placeholder option is prepended to the corresponding list. Pass the `translations` option to control those placeholders exactly as the `translations` prop does for the `QueryBuilder` component—only the placeholder properties of `fields`, `operators`, and `values` are used, since the remaining keys describe UI elements that have no meaning outside the component. This matters beyond presentation: `placeholderName` becomes the `field`, `operator`, or `value` written into rules created by the manager, so a manager paired with a component should receive the same translations.\n\n```ts\nconst q = new QueryManager(undefined, {\n fields,\n autoSelectField: false,\n translations: { fields: { placeholderName: '#', placeholderLabel: 'Select a field' } },\n});\nq.createRule(); // => { field: '#', ... }\n```\n\n`getRuleContext(pathOrID)` resolves all of the above for a specific rule at once, plus its validation result, returning `null` when the target can't be resolved or isn't a rule.\n\n```ts\ninterface RuleContext {\n fieldData: FullField;\n hideValueControls: boolean;\n inputType: InputType | null;\n matchModes: MatchModeOptions;\n operatorObject: FullOperator | undefined;\n operators: OptionList<FullOperator>;\n parameters: FlexibleOptionList<Option> | null;\n validationResult: boolean | ValidationResult;\n valueEditorType: ValueEditorType;\n values: FlexibleOptionList<Option>;\n valueSourceOptions: ValueSourceFullOptions;\n valueSources: ValueSources;\n}\n```\n\nThis is the same derivation the [`useRule`](./hooks#userule) hook performs—both call the shared `deriveRuleContext` utility—so a non-React implementation can render a rule without reimplementing the precedence rules.\n\n```ts\nconst ctx = q.getRuleContext([0]);\n// => { fieldData: { name: 'firstName', ... }, valueEditorType: 'text', valueSources: ['value'], ... }\n```\n\n`getRuleGroupContext(pathOrID)` is the equivalent for groups, defaulting to the root group. It returns `null` when the target can't be resolved or isn't a group.\n\n```ts\ninterface RuleGroupContext {\n combinator: string;\n combinatorObject: FullCombinator | undefined;\n combinators: FullOptionList<FullCombinator>;\n /** The selected combinator's `className`, or `null` for independent combinators. */\n combinatorBasedClassName: Classname | null;\n independentCombinators: boolean;\n validationResult: boolean | ValidationResult;\n}\n```\n\n**Note:**\n\nUnlike a rule, a group's `validationResult` comes _only_ from the query-level validation map—there is no field-level validator fallback."
|
|
3797
3910
|
},
|
|
3798
3911
|
{
|
|
3799
|
-
"page":
|
|
3912
|
+
"page": 54,
|
|
3800
3913
|
"heading": "Query manager › Classnames",
|
|
3801
3914
|
"anchor": "classnames",
|
|
3802
3915
|
"text": "### Classnames\n\n`@react-querybuilder/core` also exports the classname derivations used by `useRule` and `useRuleGroup`, so an implementation in any framework can produce a byte-identical `class` attribute for every element:\n\n- `deriveRuleClassNames({ classNames, suppressStandardClassnames })` — the per-element classnames for a rule (`fields`, `operators`, `value`, `removeRule`, and so on).\n- `deriveRuleOuterClassName({ classNames, suppressStandardClassnames, ...state })` — the rule's wrapper classname, including conditional state classes for `disabled`, `muted`, drag-and-drop, subqueries, and validation.\n- `deriveRuleGroupClassNames({ classNames, suppressStandardClassnames, ...dndState })` — the per-element classnames for a rule group, including its `header`.\n- `deriveRuleGroupOuterClassName({ ... })` — the group's wrapper classname.\n- `deriveRuleClassName(key, { ... })` — a single rule classname, for cases where only one is needed.\n- `deriveQueryBuilderClassNames({ classNames, suppressStandardClassnames, disabled, validationResult })` — the query builder's own wrapper classname, including the conditional `disabled`, `valid`, and `invalid` classes. Only a `boolean` `validationResult` contributes a class; a `ValidationMap` describes individual rules and groups, not the query as a whole.\n\n**Caution:**\n\nA group's wrapper reflects **fewer** states than a rule's: `dndOver`, `dndCopy`, `dndDropNotAllowed`, and `hasSubQuery` apply to rules only. The two condition sets are declared separately for exactly this reason, so use the matching function rather than assuming symmetry.\n\nEach takes the merged [`controlClassnames`](../components/querybuilder#controlclassnames) object and returns plain strings.\n\nComposition is declared as data rather than code: every derived classname names the `controlClassnames` keys that contribute to it (in application order, after the standard classname) plus any state-dependent classes. Conditional elements—the rule wrapper and the group `header`—use the same declaration, so there is no element a port can handle differently by accident.\n\n```ts\ninterface ClassnameSpec {\n sources: readonly (keyof Classnames)[];\n conditions?: readonly { key; when; standardOnly? }[];\n}\n```\n\nFor each entry the result is the standard classname, then each `sources` entry, then each active condition's custom class, then a single object of active standard classes.\n\n```ts\nderiveRuleClassNames({ classNames: { valueSelector: 'vs', fields: 'f' } });\n// => { fields: 'rule-fields vs f', operators: 'rule-operators vs', ... }\n```"
|
|
3803
3916
|
},
|
|
3804
3917
|
{
|
|
3805
|
-
"page":
|
|
3918
|
+
"page": 54,
|
|
3806
3919
|
"heading": "Query manager › Option resolvers and factories",
|
|
3807
3920
|
"anchor": "option-resolvers-and-factories",
|
|
3808
3921
|
"text": "### Option resolvers and factories\n\nThe precedence rules the `QueryBuilder` component applies when resolving a rule's configuration are exported as standalone functions, so `useQueryBuilderSetup` and `QueryManager` share one implementation rather than each keeping its own:\n\n- `resolveOperatorList({ field, fieldData, getOperators, operators, ... })` — the field's own `operators`, then `getOperators`, then the query-level list.\n- `resolveDefaultOperator({ field, fieldData, getDefaultOperator, getOperators })` — the field's `defaultOperator`, then `getDefaultOperator` (string or function), then the first available operator.\n- `resolveValueEditorType({ field, operator, fieldData, getValueEditorType })` — the field's `valueEditorType` (string or function of the operator), then `getValueEditorType`, then `\"text\"`.\n- `resolveValueList({ field, operator, fieldData, getValues, ... })` — the field's own `values`, then `getValues`, then an empty list.\n- `createRule(options)` / `createRuleGroup(options, independentCombinators?)` — build a new rule or group from that configuration.\n\nEach accepts an optional `placeholder` so a UI layer can supply translated placeholder options; omit it where translations don't apply.\n\n**Note:**\n\n`createRule` computes `value` in a second pass, once `field`, `operator`, and `valueSource` are known, since the default value depends on all three. `createRuleGroup` generates the group's own `id` _before_ any contained rule's — observable when `idGenerator` is deterministic."
|
|
3809
3922
|
},
|
|
3810
3923
|
{
|
|
3811
|
-
"page":
|
|
3924
|
+
"page": 54,
|
|
3812
3925
|
"heading": "Query manager › Value editors",
|
|
3813
3926
|
"anchor": "value-editors",
|
|
3814
3927
|
"text": "### Value editors\n\nThe logic behind the [`useValueEditor`](./hooks#usevalueeditor) hook is exported separately, since a value editor is the control an implementation is most likely to rewrite:\n\n- `getValueEditorReset({ skipHook, type, operator, value, inputType })` — returns `{ reset, value }`. A rule's `value` must collapse to a single element when it's an array (or a comma-containing string in a `number` input) but the operator is no longer one of `between`/`notBetween`/`in`/`notIn` and the editor isn't a multiselect. React applies this in an effect; apply it wherever is idiomatic.\n- `getMultiValueUpdate({ value, index, valueAsArray, operator, values, listsAsArrays, parseNumberMethod })` — the next value when the editor at `index` in a series changes. For `between`/`notBetween`, editing the first bound guarantees an array of at least two elements, seeding the second from the first available option.\n- `coerceBigIntValue(value, parseNumberMethod)` — a `bigint`, falling back to the parsed number when the value can't be represented as one.\n- `coerceInputType(inputType, operator)` — the `type` an `<input>` should use. `bigint` values and the `in`/`notIn` operators both require a text input.\n- `isBetweenOperator(operator)` — whether an operator's value is a pair of bounds.\n- `getValueSelectorUpdate(value, { multiple, listsAsArrays })` / `normalizeValueSelectorValue(value, multiple)` — the equivalents for [`useValueSelector`](./hooks#usevalueselector). The latter stringifies multiselect values so they match option names, which are always strings."
|
|
3815
3928
|
},
|
|
3816
3929
|
{
|
|
3817
|
-
"page":
|
|
3930
|
+
"page": 54,
|
|
3818
3931
|
"heading": "Query manager › Query actions",
|
|
3819
3932
|
"anchor": "query-actions",
|
|
3820
3933
|
"text": "### Query actions\n\n`createQueryActions(config)` builds the six mutations a query builder performs as pure functions of the current query. Each returns the next query, or `undefined` when the mutation was aborted—because the target is disabled, a confirmation callback declined, or `maxLevels` was reached.\n\n```ts\nconst actions = createQueryActions({\n qbId,\n combinators,\n idGenerator,\n maxLevels,\n queryDisabled,\n respectDisabled,\n resetOnFieldChange,\n resetOnOperatorChange,\n getRuleDefaultOperator,\n getValueSources,\n getRuleDefaultValue,\n getMatchModes,\n onAddRule,\n onAddGroup,\n onRemove,\n onMoveRule,\n onMoveGroup,\n onGroupRule,\n onGroupGroup,\n onUngroup,\n onLog,\n});\n\n// { addRule, addGroup, propChange, removeRuleOrGroup, moveRule, groupRule, ungroupRuleGroup }\n```\n\nThis is the policy that surrounds the [query tools](#query-tools)—disabled gating, the confirmation callback protocol, depth limits, and debug logging. An implementation supplies only its own storage: read the current query, call the action, apply a non-`undefined` result.\n\n```ts\nconst newQuery = actions.addRule(currentQuery, rule, parentPath);\nif (newQuery) applyQuery(newQuery);\n```\n\nThe confirmation callbacks have three distinct return contracts:\n\n| Callback | Return value |\n| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| `onAddRule`, `onAddGroup` | `true` to proceed, falsy to cancel, or a **replacement rule/group** to add instead |\n| `onMoveRule`, `onMoveGroup`, `onGroupRule`, `onGroupGroup` | `true` to proceed, falsy to cancel, or a **replacement query** to apply instead |\n| `onUngroup` | `true` to proceed, falsy to cancel, or a **replacement query** to apply instead |\n| `onRemove` | boolean only |\n\nThe move, group, and ungroup actions compute the prospective query _before_ invoking their callback, so it can inspect the result it is being asked to approve."
|
|
3821
3934
|
},
|
|
3822
3935
|
{
|
|
3823
|
-
"page":
|
|
3936
|
+
"page": 54,
|
|
3824
3937
|
"heading": "Query manager › Controlled and uncontrolled queries",
|
|
3825
3938
|
"anchor": "controlled-and-uncontrolled-queries",
|
|
3826
3939
|
"text": "### Controlled and uncontrolled queries\n\n`resolveCandidateQuery({ query, storeQuery, defaultQuery, fallbackQuery }, { idGenerator })` applies the precedence a query builder uses to decide what to render: the controlled `query`, then whatever is already in its store, then the uncontrolled `defaultQuery`, then a freshly created empty group. The result is passed through `prepareRuleGroup` unless it already has an `id`, which is taken to mean it has been prepared before—most often because the caller is passing back the object it received from `onQueryChange`."
|
|
3827
3940
|
},
|
|
3828
3941
|
{
|
|
3829
|
-
"page":
|
|
3942
|
+
"page": 54,
|
|
3830
3943
|
"heading": "Query manager › Paths",
|
|
3831
3944
|
"anchor": "paths",
|
|
3832
3945
|
"text": "### Paths\n\n`derivePathInfo(path, childCount, { disabled, disabledPaths })` returns `{ path, disabled }` for each child of a group, applying the rule that a child is disabled if its parent is disabled or its own path appears in `disabledPaths`. The `usePathsMemo` hook wraps it to keep the array referentially stable across renders."
|
|
3833
3946
|
},
|
|
3834
3947
|
{
|
|
3835
|
-
"page":
|
|
3948
|
+
"page": 54,
|
|
3836
3949
|
"heading": "Query manager › Inspection",
|
|
3837
3950
|
"anchor": "inspection",
|
|
3838
3951
|
"text": "### Inspection\n\n- `isIC(): boolean` — Whether the current query uses independent combinators.\n- `signatureOf(other: RuleGroupTypeAny): string` — How the current query differs from `other`, using the same signature scheme as [undo/redo](#undoredo) coalescing.\n- `diagnostics(): DiagnosticsResult` — Shorthand for `format('diagnostics')`.\n- `toJSON(): RuleGroupTypeAny` — Returns the current query, so `JSON.stringify(q)` matches `JSON.stringify(q.getQuery())`."
|
|
3839
3952
|
},
|
|
3840
3953
|
{
|
|
3841
|
-
"page":
|
|
3954
|
+
"page": 54,
|
|
3842
3955
|
"heading": "Query manager › Conversion",
|
|
3843
3956
|
"anchor": "conversion",
|
|
3844
3957
|
"text": "### Conversion\n\n`toIC()` and `fromIC()` return a **new manager** with the same configuration and the query converted to or from the independent combinators structure (see [`convertQuery`](./misc#convertquery)). Both are idempotent and never modify the original. As with [`clone`](#cloning), subscribers and history are not carried over.\n\n```ts\nconst ic = q.toIC(); // QueryManager<RuleGroupTypeIC>\nq.getQuery(); // unchanged\n```\n\n`transform(options?)` runs [`transformQuery`](./misc#transformquery) against the current query and returns its result directly, rather than a new manager — `transformQuery` can produce arbitrary shapes that are no longer valid queries. The manager is never modified.\n\n```ts\nq.transform({ propertyMap: { combinator: 'AndOr' } });\n```"
|
|
3845
3958
|
},
|
|
3846
3959
|
{
|
|
3847
|
-
"page":
|
|
3960
|
+
"page": 55,
|
|
3848
3961
|
"heading": "",
|
|
3849
3962
|
"anchor": "",
|
|
3850
3963
|
"text": "> _Refer to the [TypeScript reference](../typescript) page for information about the types and interfaces referenced below._\n\n`react-querybuilder` provides two methods for validating queries: query-level validation and field-based validation."
|
|
3851
3964
|
},
|
|
3852
3965
|
{
|
|
3853
|
-
"page":
|
|
3966
|
+
"page": 55,
|
|
3854
3967
|
"heading": "Query-level validation",
|
|
3855
3968
|
"anchor": "query-level-validation",
|
|
3856
3969
|
"text": "## Query-level validation\n\nQuery-level validation can treat the entire query as valid or invalid, or you can report validation results for specific rules/groups within the query based on the `id` property.\n\nTo mark an entire query as valid or invalid, return a `boolean` value from the `validator` callback function (`true` for valid, `false` for invalid).\n\n```tsx\nimport { QueryBuilder, RuleGroupType } from 'react-querybuilder';\n\n/**\n * This function returns false (indicating \"invalid\") when no rules are present.\n */\nconst validator = (q: RuleGroupType) => q.rules.length > 0;\n\nconst App = () => {\n return <QueryBuilder validator={validator} />;\n};\n```\n\nThe alternate return value from a query-level validator is a validation map—an object where each key represents the `id` of a rule or group. Associated values are either `boolean` (`true` for valid, `false` for invalid) or a validation result. Validation results are objects with two keys: a required `boolean` `valid` key and an optional `reasons` array specifying why that rule/group is valid or invalid. (Reasons are assumed to be strings, but the type is `any[]` since the QueryBuilder default components ignore them. Feel free to use them however you like in your custom components.)\n\n```tsx\nimport { QueryBuilder, RuleGroupType, ValidationMap } from 'react-querybuilder';\n\n/**\n * This function returns a validation map. A real validator function would\n * have logic to determine which rules are valid/invalid and why, but\n * this function is simplified for brevity.\n */\nconst validator: QueryValidator = (q): ValidationMap => ({\n r1: true, // valid rule\n r2: false, // invalid rule\n r3: { valid: true, reasons: ['awesome rule'] }, // valid rule\n r4: { valid: false, reasons: ['lame rule'] }, // invalid rule\n});\n\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n { id: 'r1', field: 'field1', operator: '=', value: 'Value 1' },\n { id: 'r2', field: 'field2', operator: '=', value: 'Value 2' },\n { id: 'r3', field: 'field3', operator: '=', value: 'Value 3' },\n { id: 'r4', field: 'field4', operator: '=', value: 'Value 4' },\n ],\n};\n\nconst App = () => {\n return <QueryBuilder query={query} validator={validator} />;\n};\n```"
|
|
3857
3970
|
},
|
|
3858
3971
|
{
|
|
3859
|
-
"page":
|
|
3972
|
+
"page": 55,
|
|
3860
3973
|
"heading": "Field-based validation",
|
|
3861
3974
|
"anchor": "field-based-validation",
|
|
3862
3975
|
"text": "## Field-based validation\n\nAssigning a `validator` to individual fields allows you to provide separate callback functions depending on the field's value type or other attributes.\n\nIn the following configuration, any rule that specifies `field2` as the field (e.g., the second rule) will be marked invalid.\n\n```tsx\nimport {\n Field,\n QueryBuilder,\n RuleGroupType,\n RuleValidator,\n ValidationResult,\n} from 'react-querybuilder';\n\n/**\n * This function returns a validation result.\n */\nconst validator: RuleValidator = (q): ValidationResult => ({\n valid: false,\n reasons: ['this field is always invalid'],\n});\n\nconst fields: Field[] = [\n { name: 'field1', label: 'Field 1' },\n { name: 'field2', label: 'Field 2', validator },\n { name: 'field3', label: 'Field 3' },\n { name: 'field4', label: 'Field 4' },\n];\n\nconst query: RuleGroupType = {\n combinator: 'and',\n rules: [\n { field: 'field1', operator: '=', value: 'Value 1' },\n { field: 'field2', operator: '=', value: 'Value 2' },\n { field: 'field3', operator: '=', value: 'Value 3' },\n { field: 'field4', operator: '=', value: 'Value 4' },\n ],\n};\n\nconst App = () => {\n return <QueryBuilder query={query} fields={fields} />;\n};\n```"
|
|
3863
3976
|
},
|
|
3864
3977
|
{
|
|
3865
|
-
"page":
|
|
3978
|
+
"page": 55,
|
|
3866
3979
|
"heading": "Effect on HTML",
|
|
3867
3980
|
"anchor": "effect-on-html",
|
|
3868
3981
|
"text": "## Effect on HTML\n\nIf you provide a query- or field-level validator function, the wrapper `<div>` for each evaluated query object (rule, group, or entire query) will be assigned one of two classes: `queryBuilder-valid` or `queryBuilder-invalid`. You can use these classes to style the elements with CSS.\n\n**See it in action:**\n\nIn the <DemoLink option=\"validateQuery\" text=\"demo\" />, check the \"Use validation\" option and create an empty group or text input without a value. Empty groups will show a message where rules usually appear (using the `:after` pseudo-selector), and text fields without values will have a [purple](https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/) background."
|
|
3869
3982
|
},
|
|
3870
3983
|
{
|
|
3871
|
-
"page":
|
|
3984
|
+
"page": 55,
|
|
3872
3985
|
"heading": "Effect on exports",
|
|
3873
3986
|
"anchor": "effect-on-exports",
|
|
3874
3987
|
"text": "## Effect on exports\n\nSee the [validation section on the Export page](./export#validation) for more information."
|
|
3875
3988
|
},
|
|
3876
3989
|
{
|
|
3877
|
-
"page":
|
|
3990
|
+
"page": 55,
|
|
3878
3991
|
"heading": "Default validator",
|
|
3879
3992
|
"anchor": "default-validator",
|
|
3880
3993
|
"text": "## Default validator\n\nYou can pass the provided [`defaultValidator`](./misc#defaultvalidator) to the `validator` prop to check for invalid combinators, empty groups, or (if the query uses independent combinators) out-of-sequence `rules` arrays. The [demo](/demo) uses the default validator."
|
|
3881
3994
|
}
|
|
3882
3995
|
],
|
|
3883
3996
|
types: {
|
|
3884
|
-
"RuleType": "/**\n * The main rule type. The `field`, `operator`, and `value` properties\n * can be narrowed with generics.\n */\nexport interface RuleType<\n F extends string = string,\n O extends string = string,\n // oxlint-disable-next-line typescript/no-explicit-any\n V = any,\n C extends string = string,\n // oxlint-disable-next-line typescript/no-explicit-any\n M = Record<string, any>,\n> extends CommonRuleAndGroupProperties {\n field: F;\n operator: O;\n value: V;\n valueSource?: ValueSource;\n match?: MatchConfig;\n /**\n * When present, the rule's left-hand side is this expression. The `field`\n * property continues to drive operator selection and validation.\n */\n lhs?: ExpressionNode;\n /**\n * Only used when adding a rule to a query that uses independent combinators.\n */\n combinatorPreceding?: C;\n /**\n * Opaque metadata for use by extension packages. The core library\n * preserves this property but does not read or interpret it.\n */\n meta?: M;\n}",
|
|
3997
|
+
"RuleType": "/**\n * The main rule type. The `field`, `operator`, and `value` properties\n * can be narrowed with generics.\n */\nexport interface RuleType<\n F extends string = string,\n O extends string = string,\n // oxlint-disable-next-line typescript/no-explicit-any\n V = any,\n C extends string = string,\n // oxlint-disable-next-line typescript/no-explicit-any\n M = Record<string, any>,\n> extends CommonRuleAndGroupProperties {\n field: F;\n operator: O;\n value: V;\n valueSource?: ValueSource;\n match?: MatchConfig;\n /**\n * Regular expression flags for `matches`/`doesNotMatch` rules (e.g. `\"i\"`, `\"im\"`). The `g`\n * and `y` flags are ignored. Flag support varies by export format.\n */\n regexFlags?: string;\n /**\n * When present, the rule's left-hand side is this expression. The `field`\n * property continues to drive operator selection and validation.\n */\n lhs?: ExpressionNode;\n /**\n * Only used when adding a rule to a query that uses independent combinators.\n */\n combinatorPreceding?: C;\n /**\n * Opaque metadata for use by extension packages. The core library\n * preserves this property but does not read or interpret it.\n */\n meta?: M;\n}",
|
|
3885
3998
|
"RuleGroupType": "/**\n * The main rule group type. This type is used for query definitions as well as\n * all sub-groups of queries.\n */\nexport interface RuleGroupType<\n R extends RuleType = RuleType,\n C extends string = string,\n> extends CommonRuleAndGroupProperties {\n combinator: C;\n rules: RuleGroupArray<RuleGroupType<R, C>, R>;\n not?: boolean;\n}",
|
|
3886
3999
|
"RuleGroupTypeIC": "/**\n * The main rule group interface when using independent combinators. This type is used\n * for query definitions as well as all sub-groups of queries.\n */\nexport interface RuleGroupTypeIC<\n R extends RuleType = RuleType,\n C extends string = string,\n> extends Except<RuleGroupType<R, C>, 'combinator' | 'rules'> {\n combinator?: undefined;\n rules: RuleGroupICArray<RuleGroupTypeIC<R, C>, R, C>;\n /**\n * Only used when adding a rule to a query that uses independent combinators\n */\n combinatorPreceding?: C;\n}",
|
|
3887
4000
|
"RuleGroupTypeAny": "/**\n * Shorthand for \"either {@link RuleGroupType} or {@link RuleGroupTypeIC}\".\n */\nexport type RuleGroupTypeAny<R extends RuleType = RuleType, C extends string = string> =\n | RuleGroupType<R, C>\n | RuleGroupTypeIC<R, C>;",
|
|
@@ -3893,11 +4006,11 @@ const docsIndex = {
|
|
|
3893
4006
|
"InputType": "/**\n * HTML5 input types\n */\nexport type InputType =\n | 'button'\n | 'checkbox'\n | 'color'\n | 'date'\n | 'datetime-local'\n | 'email'\n | 'file'\n | 'hidden'\n | 'image'\n | 'month'\n | 'number'\n | 'password'\n | 'radio'\n | 'range'\n | 'reset'\n | 'search'\n | 'submit'\n | 'tel'\n | 'text'\n | 'time'\n | 'url'\n | 'week'\n // \"bigint\" is not supported by browsers directly, but it can\n // be used to trigger alternate value editor functionality\n | 'bigint'\n | (string & {});",
|
|
3894
4007
|
"ParseNumbersPropConfig": "/**\n * Options for the `parseNumbers` prop of {@link react-querybuilder!QueryBuilder QueryBuilder}.\n */\nexport type ParseNumbersPropConfig =\n | boolean\n | `${ParseNumberMethodName}${ParseNumbersModerationLevel}`;",
|
|
3895
4008
|
"ValidationResult": "/**\n * Object with a `valid` boolean value and optional `reasons`.\n */\nexport interface ValidationResult {\n valid: boolean;\n // oxlint-disable-next-line typescript/no-explicit-any\n reasons?: any[];\n}",
|
|
3896
|
-
"ExportFormat": "/**\n * Available export formats for {@link formatQuery}.\n *\n * @group Export\n */\nexport type ExportFormat =\n | 'json'\n | 'sql'\n | 'json_without_ids'\n | 'parameterized'\n | 'parameterized_named'\n | 'mongodb'\n | 'mongodb_query'\n | 'cel'\n | 'jsonlogic'\n | 'spel'\n | 'elasticsearch'\n | 'jsonata'\n | 'natural_language'\n | 'ldap'\n | 'drizzle'\n | 'tanstack_db'\n | 'prisma'\n | 'sequelize'\n | 'cypher'\n | 'gql'\n | 'sparql'\n | 'gremlin'\n | 'diagnostics';",
|
|
3897
|
-
"FormatQueryOptions": "/**\n * Options object shape for {@link formatQuery}.\n *\n * @group Export\n */\nexport interface FormatQueryOptions {\n /**\n * The {@link ExportFormat}.\n */\n format?: ExportFormat;\n /**\n * This function will be used to process the `operator` from each rule\n * for query language formats. If not defined, the appropriate\n * `defaultOperatorProcessor*` for the format will be used.\n */\n operatorProcessor?: RuleProcessor;\n /**\n * This function will be used to process the `value` from each rule\n * for query language formats. If not defined, the appropriate\n * `defaultValueProcessor*` for the format will be used.\n */\n valueProcessor?: ValueProcessorLegacy | ValueProcessorByRule;\n /**\n * This function will be used to process each rule. If not defined, the appropriate\n * `defaultRuleProcessor*` for the given format will be used.\n */\n ruleProcessor?: RuleProcessor;\n /**\n * This function will be used to process each rule group. If not defined, the appropriate\n * `defaultRuleGroupProcessor*` for the format will be used.\n *\n * If this function is defined, it will override the `format` option. This also allows\n * `formatQuery` to produce completely custom output formats.\n */\n ruleGroupProcessor?: RuleGroupProcessor;\n /**\n * In the \"sql\", \"parameterized\", \"parameterized_named\", and \"jsonata\" export\n * formats, field names will be bracketed by this string. If an array of strings\n * is passed, field names will be preceded by the first element and\n * succeeded by the second element.\n *\n * Tip: Use `fieldIdentifierSeparator` to bracket identifiers individually within field names.\n *\n * @default '' // the empty string\n *\n * @example\n * formatQuery(query, { format: 'sql', quoteFieldNamesWith: '\"' })\n * // `\"First name\" = 'Steve'`\n *\n * @example\n * formatQuery(query, { format: 'sql', quoteFieldNamesWith: ['[', ']'] })\n * // \"[First name] = 'Steve'\"\n */\n quoteFieldNamesWith?: string | [string, string];\n /**\n * When used in conjunction with the `quoteFieldNamesWith` option, field names will\n * be split by this string, each part being individually processed as per the rules\n * of the `quoteFieldNamesWith` configuration. The parts will then be re-joined\n * by the same string.\n *\n * A common value for this option is `'.'`.\n *\n * A value of `''` (the empty string) will disable splitting/rejoining.\n *\n * @default ''\n *\n * @example\n * formatQuery(query, {\n * format: 'sql',\n * quoteFieldNamesWith: ['[', ']'],\n * fieldIdentifierSeparator: '.',\n * })\n * // \"[dbo].[Musicians].[First name] = 'Steve'\"\n */\n fieldIdentifierSeparator?: string;\n /**\n * Character to use for quoting string values in the SQL format.\n * @default `'`\n */\n quoteValuesWith?: string;\n /**\n * Validator function for the entire query. Can be the same function passed\n * as `validator` prop to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n */\n validator?: QueryValidator;\n /**\n * This can be the same {@link FullField} array passed to {@link react-querybuilder!QueryBuilder QueryBuilder}, but\n * really all you need to provide is the `name` and `validator` for each field.\n *\n * The full field object from this array, where the field's identifying property\n * matches the rule's `field`, will be passed to the rule processor.\n */\n fields?: FlexibleOptionList<FullField>;\n /**\n * This can be the same `getOperators` function passed to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n *\n * The full operator object from this array, where the operator's identifying property\n * matches the rule's `operator`, will be passed to the rule processor.\n */\n getOperators?(\n field: string,\n misc: { fieldData: FullField }\n ): FlexibleOptionList<FullOperator> | null;\n /**\n * This can be the same `getParameters` function passed to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n * When provided, any rule whose `valueSource` is `\"parameter\"` and whose value (with any\n * leading `paramPrefix` stripped) is not found in this list will be treated as invalid.\n *\n * As with the `getParameters` prop, prefer names _without_ the parameter prefix.\n */\n getParameters?(\n field?: string,\n operator?: string,\n misc?: { fieldData: FullField }\n ): FlexibleOptionList<FullOption>;\n /**\n * This string will be inserted in place of invalid groups for non-JSON formats.\n * Defaults to `'(1 = 1)'` for \"sql\"/\"parameterized\"/\"parameterized_named\" and\n * `'$and:[{$expr:true}]'` for \"mongodb\".\n */\n fallbackExpression?: string;\n /**\n * This string will be placed in front of named parameters (aka bind variables)\n * when using the \"parameterized_named\" export format.\n *\n * @default \":\"\n */\n paramPrefix?: string;\n /**\n * Maintains the parameter prefix in the `params` object keys when using the\n * \"parameterized_named\" export format. Recommended when using SQLite.\n *\n * @default false\n *\n * @example\n * console.log(formatQuery(query, {\n * format: \"parameterized_named\",\n * paramPrefix: \"$\",\n * paramsKeepPrefix: true\n * }).params)\n * // { $firstName: \"Stev\" }\n * // Default (`paramsKeepPrefix` is `false`):\n * // { firstName: \"Stev\" }\n */\n paramsKeepPrefix?: boolean;\n /**\n * Renders parameter placeholders as a series of sequential numbers\n * instead of '?' like the default. This option will respect the\n * `paramPrefix` option like the 'parameterized_named' format.\n *\n * @default false\n */\n numberedParams?: boolean;\n /**\n * Preserves the order of values for \"between\" and \"notBetween\" rules, even if a larger\n * value comes before a smaller value (which will always evaluate to false).\n */\n preserveValueOrder?: boolean;\n /**\n * Renders values as either `number`-types or unquoted strings, as\n * appropriate and when possible. Each `string`-type value is evaluated\n * against {@link numericRegex} to determine if it can be represented as a\n * plain numeric value. If so, `parseFloat` is used to convert it to a number.\n */\n parseNumbers?: ParseNumbersPropConfig;\n /**\n * Any rules where the field is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderFieldName?: string;\n /**\n * Any rules where the operator is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderOperatorName?: string;\n /**\n * Any rules where the value is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderValueName?: string;\n /**\n * Operator to use when concatenating wildcard characters and field names in \"sql\" format.\n * The ANSI standard is `||`, while SQL Server uses `+`. MySQL does not implement a concatenation\n * operator by default, and therefore requires use of the `CONCAT` function.\n *\n * If `concatOperator` is set to `\"CONCAT\"` (case-insensitive), the `CONCAT` function will be\n * used. Note that Oracle SQL does not support more than two values in the `CONCAT` function,\n * so this option should not be used in that context. The default setting (`\"||\"`) is already\n * compatible with Oracle SQL.\n *\n * @default '||'\n */\n concatOperator?: '||' | '+' | 'CONCAT' | (string & {});\n /**\n * Option presets to maximize compatibility with various SQL dialects.\n */\n preset?: SQLPreset;\n /**\n * Map of operators to their translations for the \"natural_language\" format. If the\n * result can differ based on the `valueSource`, the key should map to an array where the\n * second element represents the string to be used when `valueSource` is \"field\". The first\n * element will be used in all other cases.\n */\n operatorMap?: ExportOperatorMap;\n /**\n * [Constituent word order](https://en.wikipedia.org/wiki/Word_order#Constituent_word_orders)\n * for the \"natural_language\" format. Can be abbreviated like \"SVO\" or spelled out like\n * \"subject-verb-object\".\n *\n * - Subject = field\n * - Verb = operator\n * - Object = value\n */\n wordOrder?: ConstituentWordOrderString | Lowercase<ConstituentWordOrderString> | ({} & string);\n /**\n * Translatable strings used by the \"natural_language\" format.\n */\n translations?: Partial<Record<NLTranslationKey, string>>;\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: Record<string, any>;\n}",
|
|
3898
|
-
"ParserCommonOptions": "/**\n * Options common to all parsers.\n */\nexport interface ParserCommonOptions {\n /**\n * Field whitelist (same shapes as the `QueryBuilder` `fields` prop). Rules with unknown fields\n * are dropped.\n */\n fields?: FlexibleOptionList<FullField> | BaseOptionMap<FullField>;\n getValueSources?: (field: string, operator: string) => ValueSources;\n listsAsArrays?: boolean;\n /**\n * When true, the generated query will use independent combinators ({@link RuleGroupTypeIC}).\n */\n independentCombinators?: boolean;\n /**\n * When true, a unique `id` will be generated for each rule and group in the query.\n */\n generateIDs?: boolean;\n /**\n * Generates a `bigint` value if the string represents a valid integer\n * outside the safe boundaries of the `number` type.\n */\n bigIntOnOverflow?: boolean;\n}",
|
|
4009
|
+
"ExportFormat": "/**\n * Available export formats for {@link formatQuery}.\n *\n * @group Export\n */\nexport type ExportFormat =\n | 'json'\n | 'sql'\n | 'json_without_ids'\n | 'parameterized'\n | 'parameterized_named'\n | 'mongodb'\n | 'mongodb_query'\n | 'cel'\n | 'jsonlogic'\n | 'spel'\n | 'elasticsearch'\n | 'jsonata'\n | 'natural_language'\n | 'ldap'\n | 'drizzle'\n | 'tanstack_db'\n | 'prisma'\n | 'sequelize'\n | 'cypher'\n | 'gql'\n | 'sparql'\n | 'gremlin'\n | 'pinecone'\n | 'chroma'\n | 'qdrant'\n | 'weaviate'\n | 'milvus'\n | 'diagnostics';",
|
|
4010
|
+
"FormatQueryOptions": "/**\n * Options object shape for {@link formatQuery}.\n *\n * @group Export\n */\nexport interface FormatQueryOptions {\n /**\n * The {@link ExportFormat}.\n */\n format?: ExportFormat;\n /**\n * This function will be used to process the `operator` from each rule\n * for query language formats. If not defined, the appropriate\n * `defaultOperatorProcessor*` for the format will be used.\n */\n operatorProcessor?: RuleProcessor;\n /**\n * This function will be used to process the `value` from each rule\n * for query language formats. If not defined, the appropriate\n * `defaultValueProcessor*` for the format will be used.\n */\n valueProcessor?: ValueProcessorLegacy | ValueProcessorByRule;\n /**\n * This function will be used to process each rule. If not defined, the appropriate\n * `defaultRuleProcessor*` for the given format will be used.\n */\n ruleProcessor?: RuleProcessor;\n /**\n * This function will be used to process each rule group. If not defined, the appropriate\n * `defaultRuleGroupProcessor*` for the format will be used.\n *\n * If this function is defined, it will override the `format` option. This also allows\n * `formatQuery` to produce completely custom output formats.\n */\n ruleGroupProcessor?: RuleGroupProcessor;\n /**\n * In the \"sql\", \"parameterized\", \"parameterized_named\", and \"jsonata\" export\n * formats, field names will be bracketed by this string. If an array of strings\n * is passed, field names will be preceded by the first element and\n * succeeded by the second element.\n *\n * Tip: Use `fieldIdentifierSeparator` to bracket identifiers individually within field names.\n *\n * @default '' // the empty string\n *\n * @example\n * formatQuery(query, { format: 'sql', quoteFieldNamesWith: '\"' })\n * // `\"First name\" = 'Steve'`\n *\n * @example\n * formatQuery(query, { format: 'sql', quoteFieldNamesWith: ['[', ']'] })\n * // \"[First name] = 'Steve'\"\n */\n quoteFieldNamesWith?: string | [string, string];\n /**\n * When used in conjunction with the `quoteFieldNamesWith` option, field names will\n * be split by this string, each part being individually processed as per the rules\n * of the `quoteFieldNamesWith` configuration. The parts will then be re-joined\n * by the same string.\n *\n * A common value for this option is `'.'`.\n *\n * A value of `''` (the empty string) will disable splitting/rejoining.\n *\n * @default ''\n *\n * @example\n * formatQuery(query, {\n * format: 'sql',\n * quoteFieldNamesWith: ['[', ']'],\n * fieldIdentifierSeparator: '.',\n * })\n * // \"[dbo].[Musicians].[First name] = 'Steve'\"\n */\n fieldIdentifierSeparator?: string;\n /**\n * Character to use for quoting string values in the SQL format.\n * @default `'`\n */\n quoteValuesWith?: string;\n /**\n * Validator function for the entire query. Can be the same function passed\n * as `validator` prop to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n */\n validator?: QueryValidator;\n /**\n * This can be the same {@link FullField} array passed to {@link react-querybuilder!QueryBuilder QueryBuilder}, but\n * really all you need to provide is the `name` and `validator` for each field.\n *\n * The full field object from this array, where the field's identifying property\n * matches the rule's `field`, will be passed to the rule processor.\n */\n fields?: FlexibleOptionList<FullField>;\n /**\n * This can be the same `getOperators` function passed to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n *\n * The full operator object from this array, where the operator's identifying property\n * matches the rule's `operator`, will be passed to the rule processor.\n */\n getOperators?(\n field: string,\n misc: { fieldData: FullField }\n ): FlexibleOptionList<FullOperator> | null;\n /**\n * This can be the same `getParameters` function passed to {@link react-querybuilder!QueryBuilder QueryBuilder}.\n * When provided, any rule whose `valueSource` is `\"parameter\"` and whose value (with any\n * leading `paramPrefix` stripped) is not found in this list will be treated as invalid.\n *\n * As with the `getParameters` prop, prefer names _without_ the parameter prefix.\n */\n getParameters?(\n field?: string,\n operator?: string,\n misc?: { fieldData: FullField }\n ): FlexibleOptionList<FullOption>;\n /**\n * This string will be inserted in place of invalid groups for non-JSON formats.\n * Defaults to `'(1 = 1)'` for \"sql\"/\"parameterized\"/\"parameterized_named\" and\n * `'$and:[{$expr:true}]'` for \"mongodb\".\n */\n fallbackExpression?: string;\n /**\n * This string will be placed in front of named parameters (aka bind variables)\n * when using the \"parameterized_named\" export format.\n *\n * @default \":\"\n */\n paramPrefix?: string;\n /**\n * Maintains the parameter prefix in the `params` object keys when using the\n * \"parameterized_named\" export format. Recommended when using SQLite.\n *\n * @default false\n *\n * @example\n * console.log(formatQuery(query, {\n * format: \"parameterized_named\",\n * paramPrefix: \"$\",\n * paramsKeepPrefix: true\n * }).params)\n * // { $firstName: \"Stev\" }\n * // Default (`paramsKeepPrefix` is `false`):\n * // { firstName: \"Stev\" }\n */\n paramsKeepPrefix?: boolean;\n /**\n * Renders parameter placeholders as a series of sequential numbers\n * instead of '?' like the default. This option will respect the\n * `paramPrefix` option like the 'parameterized_named' format.\n *\n * @default false\n */\n numberedParams?: boolean;\n /**\n * Preserves the order of values for \"between\" and \"notBetween\" rules, even if a larger\n * value comes before a smaller value (which will always evaluate to false).\n */\n preserveValueOrder?: boolean;\n /**\n * Renders values as either `number`-types or unquoted strings, as\n * appropriate and when possible. Each `string`-type value is evaluated\n * against {@link numericRegex} to determine if it can be represented as a\n * plain numeric value. If so, `parseFloat` is used to convert it to a number.\n */\n parseNumbers?: ParseNumbersPropConfig;\n /**\n * Any rules where the field is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderFieldName?: string;\n /**\n * Any rules where the operator is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderOperatorName?: string;\n /**\n * Any rules where the value is equal to this value will be ignored.\n *\n * @default '~'\n */\n placeholderValueName?: string;\n /**\n * Operator to use when concatenating wildcard characters and field names in \"sql\" format.\n * The ANSI standard is `||`, while SQL Server uses `+`. MySQL does not implement a concatenation\n * operator by default, and therefore requires use of the `CONCAT` function.\n *\n * If `concatOperator` is set to `\"CONCAT\"` (case-insensitive), the `CONCAT` function will be\n * used. Note that Oracle SQL does not support more than two values in the `CONCAT` function,\n * so this option should not be used in that context. The default setting (`\"||\"`) is already\n * compatible with Oracle SQL.\n *\n * @default '||'\n */\n concatOperator?: '||' | '+' | 'CONCAT' | (string & {});\n /**\n * Option presets to maximize compatibility with various SQL dialects.\n */\n preset?: SQLPreset;\n /**\n * Map of operators to their translations for the \"natural_language\" format. If the\n * result can differ based on the `valueSource`, the key should map to an array where the\n * second element represents the string to be used when `valueSource` is \"field\". The first\n * element will be used in all other cases.\n */\n operatorMap?: ExportOperatorMap;\n /**\n * [Constituent word order](https://en.wikipedia.org/wiki/Word_order#Constituent_word_orders)\n * for the \"natural_language\" format. Can be abbreviated like \"SVO\" or spelled out like\n * \"subject-verb-object\".\n *\n * - Subject = field\n * - Verb = operator\n * - Object = value\n */\n wordOrder?: ConstituentWordOrderString | Lowercase<ConstituentWordOrderString> | ({} & string);\n /**\n * Translatable strings used by the \"natural_language\" format.\n */\n translations?: Partial<Record<NLTranslationKey, string>>;\n /**\n * For the \"diagnostics\" format only. When set to a format with an entry in\n * {@link formatCapabilities}, rules the target format can't express are reported\n * as `\"format-check\"` warnings (they don't affect validity). Ignored by other formats.\n */\n diagnosticsTargetFormat?: ExportFormat;\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: Record<string, any>;\n}",
|
|
4011
|
+
"ParserCommonOptions": "/**\n * Options common to all parsers.\n */\nexport interface ParserCommonOptions {\n /**\n * Field whitelist (same shapes as the `QueryBuilder` `fields` prop). Rules with unknown fields\n * are dropped.\n */\n fields?: FlexibleOptionList<FullField> | BaseOptionMap<FullField>;\n getValueSources?: (field: string, operator: string) => ValueSources;\n listsAsArrays?: boolean;\n /**\n * When true, the generated query will use independent combinators ({@link RuleGroupTypeIC}).\n */\n independentCombinators?: boolean;\n /**\n * When true, a unique `id` will be generated for each rule and group in the query.\n */\n generateIDs?: boolean;\n /**\n * Generates a `bigint` value if the string represents a valid integer\n * outside the safe boundaries of the `number` type.\n */\n bigIntOnOverflow?: boolean;\n /**\n * Import regular expressions as `matches`/`doesNotMatch` rules (with `regexFlags`). When\n * `undefined`, regexes are imported this way only for fields whose `operators` include\n * `matches` or `doesNotMatch`; otherwise they fall back to string-match operators like\n * `contains` (pattern source as value).\n */\n parseRegex?: boolean;\n}",
|
|
3899
4012
|
"GetQueryJsonSchemaOptions": "/**\n * Options for {@link getQueryJsonSchema}.\n */\nexport interface GetQueryJsonSchemaOptions {\n /** `'standard'` = `RuleGroupType`; `'ic'` = `RuleGroupTypeIC`. Default `'standard'`. */\n queryType?: 'standard' | 'ic';\n /**\n * `'full'` = per-field operator/value constraints (`anyOf` per field/operator class);\n * `'loose'` = field/operator enums only (smaller; for large `fields`). Default `'full'`.\n */\n mode?: 'full' | 'loose';\n /**\n * Provider compat (e.g. OpenAI structured outputs): `additionalProperties: false`\n * everywhere, all props required (optional ones nullable), root is an object. Default `false`.\n */\n strict?: boolean;\n /**\n * Group recursion. `'ref'` (default) = recursive `$ref`. Number = max nested group depth\n * below root, fully inlined (no `$ref`/`$defs`) for providers w/o recursion support.\n */\n maxDepth?: 'ref' | number;\n /** Include `id`/`path` props. Default `false` (LLM output shouldn't invent ids). */\n includeIds?: boolean;\n /**\n * `true` = allow `valueSource: 'field'` for all fields; `false` = never.\n * Default: derived from each field's `valueSources`.\n */\n allowFieldValueSource?: boolean;\n /** Include `not` on groups. Default `true`. */\n includeNot?: boolean;\n /** Combinators. Default {@link defaultCombinators}. */\n combinators?: FlexibleOptionListProp<FullCombinator>;\n /** Fallback operators for fields w/o `operators`. Default {@link defaultOperators}. */\n operators?: FlexibleOptionListProp<FullOperator>;\n /** Add field `label`s as `description`s. Default `true`. */\n descriptions?: boolean;\n /** Emit `$schema` (draft-07). Default `false`. */\n includeSchemaDeclaration?: boolean;\n /**\n * List values (`between`/`in`/multiselect) as arrays (`true`, default) or\n * comma-separated strings (`false`). Should match `formatQuery`'s `listsAsArrays`.\n */\n listsAsArrays?: boolean;\n}",
|
|
3900
|
-
"DiagnosticEntry": "/**\n * A single diagnostic entry produced by the `\"diagnostics\"` format.\n *\n * @group Export\n */\nexport interface DiagnosticEntry {\n /** The `id` of the rule or group this diagnostic pertains to. */\n id: string;\n /** The path to the rule or group within the query tree. */\n path: number[];\n /** A machine-readable code identifying the type of diagnostic. */\n code: string;\n /** A human-readable description of the diagnostic. */\n message: string;\n /**\n * Which check produced this diagnostic.\n *\n * - `\"placeholder\"` — a placeholder field, operator, or value\n * - `\"muted\"` — a muted rule or group\n * - `\"query-validator\"` — the query-level validator\n * - `\"field-validator\"` — a field-level validator\n * - `\"type-check\"` — value/type mismatch based on field `inputType`\n * - `\"type-check\"` also covers `between`/`in` value arity, `values` membership, and\n * boolean values for checkbox/switch editors\n * - `\"field-check\"` — field existence and operator validity against the `fields` config\n * - `\"structure-check\"` — `rules` array shape (e.g. independent combinator alternation)\n */\n source:\n | 'placeholder'\n | 'muted'\n | 'query-validator'\n | 'field-validator'\n | 'type-check'\n | 'field-check'\n | 'structure-check';\n}",
|
|
4013
|
+
"DiagnosticEntry": "/**\n * A single diagnostic entry produced by the `\"diagnostics\"` format.\n *\n * @group Export\n */\nexport interface DiagnosticEntry {\n /** The `id` of the rule or group this diagnostic pertains to. */\n id: string;\n /** The path to the rule or group within the query tree. */\n path: number[];\n /** A machine-readable code identifying the type of diagnostic. */\n code: string;\n /** A human-readable description of the diagnostic. */\n message: string;\n /**\n * Which check produced this diagnostic.\n *\n * - `\"placeholder\"` — a placeholder field, operator, or value\n * - `\"muted\"` — a muted rule or group\n * - `\"query-validator\"` — the query-level validator\n * - `\"field-validator\"` — a field-level validator\n * - `\"type-check\"` — value/type mismatch based on field `inputType`\n * - `\"type-check\"` also covers `between`/`in` value arity, `values` membership, and\n * boolean values for checkbox/switch editors\n * - `\"field-check\"` — field existence and operator validity against the `fields` config\n * - `\"structure-check\"` — `rules` array shape (e.g. independent combinator alternation)\n * - `\"format-check\"` — operator/value source/match mode unsupported by the\n * `diagnosticsTargetFormat` (always `severity: \"warning\"`)\n */\n source:\n | 'placeholder'\n | 'muted'\n | 'query-validator'\n | 'field-validator'\n | 'type-check'\n | 'field-check'\n | 'structure-check'\n | 'format-check';\n /**\n * Severity. Absent means `\"error\"` (legacy). `\"warning\"` entries never affect validity.\n */\n severity?: 'error' | 'warning';\n}",
|
|
3901
4014
|
"DiagnosticsResult": "/**\n * Top-level result of {@link formatQuery} for the `\"diagnostics\"` format.\n *\n * @group Export\n */\nexport interface DiagnosticsResult {\n /** The annotated query tree with `valid`, `path`, and `level` on every node. */\n query: RuleGroupDiagnosticsResult | RuleGroupICDiagnosticsResult;\n /** A flat array of all diagnostic entries across the tree. */\n diagnostics: DiagnosticEntry[];\n /** Aggregate statistics about the query. */\n stats: DiagnosticsStats;\n /** Per-field summary of rule counts and invalid counts. */\n fieldSummary: Record<string, DiagnosticsFieldSummaryEntry>;\n}",
|
|
3902
4015
|
"QueryBuilderContextProps": "/**\n * Props passed down through context from a {@link QueryBuilderContextProvider}.\n *\n * @group Props\n */\nexport interface QueryBuilderContextProps<\n F extends FullField = FullField,\n O extends string = string,\n> extends QueryBuilderFlags {\n /**\n * Defines replacement components.\n */\n controlElements?: ControlElementsProp<F, O>;\n /**\n * This can be used to assign specific CSS classes to various controls\n * that are rendered by {@link QueryBuilder}.\n */\n controlClassnames?: Partial<Classnames>;\n /**\n * This can be used to override translatable texts applied to the various\n * controls that are rendered by {@link QueryBuilder}.\n */\n translations?: Partial<Translations>;\n}",
|
|
3903
4016
|
"QueryBuilderProps": "/**\n * Props for {@link QueryBuilder}.\n *\n * Notes:\n * - Only one of `query` or `defaultQuery` should be provided. If `query` is present,\n * then `defaultQuery` should be undefined and vice versa.\n * - If rendered initially with a `query` prop, then `query` must be defined in every\n * subsequent render or warnings will be logged (in non-production modes only).\n *\n * @typeParam RG - The type of the query object, inferred from either the `query` or `defaultQuery` prop.\n * Must extend {@link RuleGroupType} or {@link RuleGroupTypeIC}.\n * @typeParam F - The field type (see {@link Field}).\n * @typeParam O - The operator type (see {@link Operator}).\n * @typeParam C - The combinator type (see {@link Combinator}).\n *\n * @group Props\n */\nexport type QueryBuilderProps<\n RG extends RuleGroupTypeAny,\n F extends FullField,\n O extends FullOperator,\n C extends FullCombinator,\n> = RG extends RuleGroupType<infer R> | RuleGroupTypeIC<infer R>\n ? QueryBuilderContextProps<F, GetOptionIdentifierType<O>> & {\n /**\n * Identifier for this query builder instance within React Query Builder's internal\n * Redux store. Useful for addressing a specific query builder from outside its own\n * component tree, e.g. from a custom toolbar.\n *\n * If not provided, an identifier is generated automatically. Only evaluated when the\n * component mounts; subsequent changes are ignored (and logged in non-production modes).\n *\n * If another _mounted_ query builder is already using the same `qbId`, this query\n * builder will fall back to a generated identifier and log an error in non-production\n * modes. Reusing a `qbId` _after_ the previous query builder has unmounted is\n * supported—see the `preserveQueryStateOnUnmount` prop.\n */\n qbId?: string;\n /**\n * Initial query object for uncontrolled components.\n */\n defaultQuery?: RG;\n /**\n * Query object for controlled components.\n */\n query?: RG;\n /**\n * List of valid {@link FullField}s.\n *\n * @default []\n */\n fields?: FlexibleOptionListProp<F> | BaseOptionMap<F>;\n /**\n * List of valid {@link FullOperator}s.\n *\n * @see {@link DefaultOperatorName}\n *\n * @default\n * [\n * { name: '=', label: '=' },\n * { name: '!=', label: '!=' },\n * { name: '<', label: '<' },\n * { name: '>', label: '>' },\n * { name: '<=', label: '<=' },\n * { name: '>=', label: '>=' },\n * { name: 'contains', label: 'contains' },\n * { name: 'beginsWith', label: 'begins with' },\n * { name: 'endsWith', label: 'ends with' },\n * { name: 'doesNotContain', label: 'does not contain' },\n * { name: 'doesNotBeginWith', label: 'does not begin with' },\n * { name: 'doesNotEndWith', label: 'does not end with' },\n * { name: 'null', label: 'is null' },\n * { name: 'notNull', label: 'is not null' },\n * { name: 'in', label: 'in' },\n * { name: 'notIn', label: 'not in' },\n * { name: 'between', label: 'between' },\n * { name: 'notBetween', label: 'not between' },\n * ]\n */\n operators?: FlexibleOptionListProp<O>;\n /**\n * List of valid {@link FullCombinator}s.\n *\n * @see {@link DefaultCombinatorName}\n *\n * @default\n * [\n * {name: 'and', label: 'AND'},\n * {name: 'or', label: 'OR'},\n * ]\n */\n combinators?: FlexibleOptionListProp<C>;\n /**\n * Default properties applied to all objects in the `fields` prop. Properties on\n * individual field definitions will override these.\n */\n baseField?: Record<string, unknown>;\n /**\n * Default properties applied to all objects in the `operators` prop. Properties on\n * individual operator definitions will override these.\n */\n baseOperator?: Record<string, unknown>;\n /**\n * Default properties applied to all objects in the `combinators` prop. Properties on\n * individual combinator definitions will override these.\n */\n baseCombinator?: Record<string, unknown>;\n /**\n * The default `field` value for new rules. This can be the field `name`\n * itself or a function that returns a valid {@link FullField} `name` given\n * the `fields` list.\n */\n getDefaultField?: GetOptionIdentifierType<F> | ((fieldsData: FullOptionList<F>) => string);\n /**\n * The default `operator` value for new rules. This can be the operator\n * `name` or a function that returns a valid {@link FullOperator} `name` for\n * a given field name.\n */\n getDefaultOperator?:\n | GetOptionIdentifierType<O>\n | ((field: GetOptionIdentifierType<F>, misc: { fieldData: F }) => string);\n /**\n * Returns the default `value` for new rules.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n getDefaultValue?(rule: R, misc: { fieldData: F }): any;\n /**\n * This function should return the list of allowed {@link FullOperator}s\n * for the given {@link FullField} `name`. If `null` is returned, the\n * {@link DefaultOperator}s are used.\n */\n getOperators?(\n field: GetOptionIdentifierType<F>,\n misc: { fieldData: F }\n ): FlexibleOptionListProp<FullOperator> | null;\n /**\n * This function should return the type of {@link ValueEditor} (see\n * {@link ValueEditorType}) for the given field `name` and operator `name`.\n */\n getValueEditorType?(\n field: GetOptionIdentifierType<F>,\n operator: GetOptionIdentifierType<O>,\n misc: { fieldData: F }\n ): ValueEditorType;\n /**\n * This function should return the separator element for a given field\n * `name` and operator `name`. The element can be any valid React element,\n * including a bare string (e.g., \"and\" or \"to\") or an HTML element like\n * `<span />`. It will be placed in between value editors when multiple\n * editors are rendered, such as when the `operator` is `\"between\"`.\n */\n getValueEditorSeparator?(\n field: GetOptionIdentifierType<F>,\n operator: GetOptionIdentifierType<O>,\n misc: { fieldData: F }\n ): ReactNode;\n /**\n * This function should return the list of valid {@link ValueSources}\n * for a given field `name` and operator `name`. The return value must\n * be an array that includes at least one valid {@link ValueSource}\n * (i.e. `[\"value\"]`, `[\"field\"]`, `[\"value\", \"field\"]`, or\n * `[\"field\", \"value\"]`).\n */\n getValueSources?(\n field: GetOptionIdentifierType<F>,\n operator: GetOptionIdentifierType<O>,\n misc: { fieldData: F }\n ): ValueSources | ValueSourceFlexibleOptions;\n /**\n * This function should return a list of named parameters to be presented\n * as options when a rule's `valueSource` is `\"parameter\"`. Each entry's\n * `name` is stored in `rule.value` when selected. When this prop is omitted\n * or resolves to a nullish/empty list, the value editor accepts free-form\n * text for the parameter name.\n *\n * Prefer names _without_ the parameter prefix (e.g. `{ name: 'p1' }`, not\n * `{ name: ':p1' }`); `formatQuery` adds the appropriate prefix per dialect.\n */\n getParameters?(\n field?: GetOptionIdentifierType<F>,\n operator?: GetOptionIdentifierType<O>,\n misc?: { fieldData: F }\n ): FlexibleOptionListProp<FullOption>;\n /**\n * This function should return the `type` of `<input />`\n * for the given field `name` and operator `name` (only applicable when\n * `getValueEditorType` returns `\"text\"` or a falsy value). If no\n * function is provided, `\"text\"` is used as the default.\n */\n getInputType?(\n field: GetOptionIdentifierType<F>,\n operator: GetOptionIdentifierType<O>,\n misc: { fieldData: F }\n ): InputType | null;\n /**\n * This function should return the list of allowed values for the\n * given field `name` and operator `name` (only applicable when\n * `getValueEditorType` returns `\"select\"` or `\"radio\"`). If no\n * function is provided, an empty array is used as the default.\n */\n getValues?(\n field: GetOptionIdentifierType<F>,\n operator: GetOptionIdentifierType<O>,\n misc: { fieldData: F }\n ): FlexibleOptionListProp<Option>;\n /**\n * This function should return the list of valid {@link MatchMode}s or\n * {@link MatchConfig}s for a given field `name`. The return value must\n * be an array that includes at least one valid {@link MatchMode}, or `true`\n * to indicate that all match modes are allowed. Any other return value\n * will be ignored (no match modes will be allowed).\n */\n getMatchModes?(\n field: GetOptionIdentifierType<F>,\n misc: { fieldData: F }\n ): boolean | MatchMode[] | FlexibleOption<MatchMode>[];\n /**\n * This function should return any props that a subquery (see {@link MatchMode})\n * should override from the props provided to this query builder. Note that certain\n * props like `query`, `onQueryChange`, and `enableDragAndDrop` will be ignored.\n */\n getSubQueryBuilderProps?(\n field: GetOptionIdentifierType<F>,\n misc: { fieldData: F }\n ): QueryBuilderProps<GenericizeRuleGroupType<RG>, FullOption, FullOption, FullOption>;\n /**\n * The return value of this function will be used to apply classnames to the\n * outer `<div>` of the given {@link Rule}.\n */\n getRuleClassname?(rule: R, misc: { fieldData: F }): Classname;\n /**\n * The return value of this function will be used to apply classnames to the\n * outer `<div>` of the given {@link RuleGroup}.\n */\n getRuleGroupClassname?(ruleGroup: RG): Classname;\n /**\n * This callback is invoked before a new rule is added. The function should either manipulate\n * the rule and return the new object, return `true` to allow the addition to proceed as normal,\n * or return `false` to cancel the addition of the rule.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n onAddRule?(rule: R, parentPath: Path, query: RG, context?: any): RuleType | boolean;\n /**\n * This callback is invoked before a new group is added. The function should either manipulate\n * the group and return the new object, return `true` to allow the addition to proceed as normal,\n * or return `false` to cancel the addition of the group.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n onAddGroup?(ruleGroup: RG, parentPath: Path, query: RG, context?: any): RG | boolean;\n /**\n * This callback is invoked before a rule is moved or shifted. The function should return\n * `true` to allow the move/shift to proceed as normal, `false` to cancel the move/shift, or\n * a new query object (presumably based on `query` or `nextQuery`) which will become the new\n * query state.\n */\n onMoveRule?(\n /** The rule being moved. */\n rule: R,\n /** The original path of the rule. */\n fromPath: Path,\n /**\n * The target path of the rule, or the direction of the shift. Note that the target path\n * is not necessarily the final path since moves (particularly downward) can affect the\n * indexes of sibling rules/groups at the original path.\n */\n toPath: Path | 'up' | 'down',\n /** The current query, before the move. */\n query: RG,\n /** The next query, if the move is allowed to proceed. */\n nextQuery: RG,\n /** The options passed to {@link move} to generate `nextQuery`. */\n options: MoveOptions,\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any\n ): RG | boolean;\n /**\n * This callback is invoked before a group is moved or shifted. The function should return\n * `true` to allow the move/shift to proceed as normal, `false` to cancel the move/shift, or\n * a new query object (presumably based on `query` or `nextQuery`) which will become the new\n * query state.\n */\n onMoveGroup?(\n /** The group being moved. */\n ruleGroup: RG,\n /** The original path of the group. */\n fromPath: Path,\n /**\n * The target path of the group, or the direction of the shift. Note that the target path\n * is not necessarily the final path since moves (particularly downward) can affect the\n * indexes of sibling rules/groups at the original path.\n */\n toPath: Path | 'up' | 'down',\n /** The current query, before the move. */\n query: RG,\n /** The next query, if the move is allowed to proceed. */\n nextQuery: RG,\n /** The options passed to {@link move} to generate `nextQuery`. */\n options: MoveOptions,\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any\n ): RG | boolean;\n /**\n * This callback is invoked before a rule is grouped with another object. The function should\n * return `true` to allow the grouping to proceed as normal, `false` to cancel the grouping,\n * or a new query object (presumably based on `query` or `nextQuery`) which will become the new\n * query state.\n */\n onGroupRule?(\n /** The rule being moved. */\n rule: R,\n /** The original path of the rule. */\n fromPath: Path,\n /**\n * The path of the target object to group the rule with. Note that the target path\n * is not necessarily the final path since moves (particularly downward) can affect the\n * indexes of sibling rules/groups at the original path.\n */\n toPath: Path,\n /** The current query, before the group. */\n query: RG,\n /** The next query, if the group is allowed to proceed. */\n nextQuery: RG,\n /** The options passed to {@link group} to generate `nextQuery`. */\n options: GroupOptions,\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any\n ): RG | boolean;\n /**\n * This callback is invoked before a group is grouped with another object. The function should\n * return `true` to allow the grouping to proceed as normal, `false` to cancel the grouping,\n * or a new query object (presumably based on `query` or `nextQuery`) which will become the new\n * query state.\n */\n onGroupGroup?(\n /** The group being moved. */\n ruleGroup: RG,\n /** The original path of the group. */\n fromPath: Path,\n /**\n * The path of the target object to group the group with. Note that the target path\n * is not necessarily the final path since moves (particularly downward) can affect the\n * indexes of sibling rules/groups at the original path.\n */\n toPath: Path,\n /** The current query, before the group. */\n query: RG,\n /** The next query, if the group is allowed to proceed. */\n nextQuery: RG,\n /** The options passed to {@link group} to generate `nextQuery`. */\n options: GroupOptions,\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any\n ): RG | boolean;\n /**\n * This callback is invoked before a group is replaced by its own rules in the parent group.\n * The function should return `true` to allow the ungrouping to proceed as normal, `false` to\n * cancel it, or a new query object (presumably based on `query` or `nextQuery`) which will\n * become the new query state.\n */\n onUngroup?(\n /** The group being ungrouped. */\n ruleGroup: RG,\n /** The path of the group being ungrouped. */\n path: Path,\n /** The current query, before the ungroup. */\n query: RG,\n /** The next query, if the ungroup is allowed to proceed. */\n nextQuery: RG,\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any\n ): RG | boolean;\n /**\n * This callback is invoked before a rule or group is removed. The function should return\n * `true` if the rule or group should be removed or `false` if it should not be removed.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n onRemove?(ruleOrGroup: R | RG, path: Path, query: RG, context?: any): boolean;\n /**\n * This callback is invoked anytime the query state is updated.\n */\n onQueryChange?(query: RG): void;\n /**\n * Each log object will be passed to this function when `debugMode` is `true`.\n *\n * @default console.log\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n onLog?(obj: any): void;\n /**\n * @deprecated As of v7, this prop is ignored. To enable independent combinators, use\n * {@link RuleGroupTypeIC} for the `query` or `defaultQuery` prop. The query builder\n * will detect the query type and behave accordingly.\n */\n independentCombinators?: boolean;\n /**\n * Disables the entire query builder if true, or the rules and groups at\n * the specified paths (as well as all child rules/groups and subcomponents)\n * if an array of paths is provided. If the root path is specified (`disabled={[[]]}`),\n * no changes to the query are allowed.\n *\n * @default false\n */\n disabled?: boolean | Path[];\n /**\n * Store values as numbers whenever possible.\n *\n * _**TIP: Try `\"strict-limited\"` first.**_\n *\n * Options include `true`, `false`, `\"enhanced\"`, `\"native\"`, and `\"strict\"`. The `string` options\n * can be suffixed with `\"-limited\"`.\n *\n * - `false` avoids numeric parsing\n * - `true` or `\"strict\"` parses values using `numeric-quantity`, bailing out (returning the original\n * string) when trailing invalid characters are present\n * - `\"enhanced\"` is the same as `true`/`\"strict\"`, but ignores trailing invalid characters (CAUTION:\n * this can lead to information loss)\n * - `\"native\"` parses values using `parseFloat`, returning `NaN` when parsing fails\n *\n * When the value is `true` or a string without the \"-limited\" suffix, the default {@link ValueEditor}\n * will attempt to parse *all* inputs as numbers. **CAUTION: This can lead to unexpected behavior.**\n *\n * When the value is a string with the \"-limited\" suffix, the default {@link ValueEditor} will\n * only attempt to parse inputs as numbers when the `inputType` is `\"number\"`.\n *\n * @default false\n */\n parseNumbers?: ParseNumbersPropConfig;\n /**\n * Query validation function.\n */\n validator?: QueryValidator;\n /**\n * `id` generator function. Should always produce a unique/random value.\n *\n * @default generateID\n */\n idGenerator?: () => string;\n /**\n * Generator function for the `title` attribute applied to the outermost `<div>` of each\n * rule group. As this is intended to help with accessibility, the text output from this\n * function should be meaningful, descriptive, and unique within the page.\n */\n accessibleDescriptionGenerator?: AccessibleDescriptionGenerator;\n /**\n * Maximum number of levels deep the query is allowed to go. The minimum is 1; values\n * less than 1 will be ignored.\n */\n maxLevels?: number;\n /**\n * Container for custom props that are passed to all components.\n */\n // oxlint-disable-next-line typescript/no-explicit-any\n context?: any;\n }\n : never;"
|
|
@@ -3981,8 +4094,7 @@ const getSectionMarkdown = (page, anchor) => {
|
|
|
3981
4094
|
};
|
|
3982
4095
|
/** Page markdown, or one section's when `anchor` is non-empty. */
|
|
3983
4096
|
const getDocMarkdown = (page, anchor = "") => anchor ? getSectionMarkdown(page, anchor) : getPageMarkdown(page);
|
|
3984
|
-
|
|
3985
|
-
//#region src/formats.ts
|
|
4097
|
+
// src/formats.ts
|
|
3986
4098
|
const ormFormats = [
|
|
3987
4099
|
"drizzle",
|
|
3988
4100
|
"prisma",
|
|
@@ -4005,6 +4117,11 @@ const exportFormats = Object.keys({
|
|
|
4005
4117
|
gql: true,
|
|
4006
4118
|
sparql: true,
|
|
4007
4119
|
gremlin: true,
|
|
4120
|
+
pinecone: true,
|
|
4121
|
+
chroma: true,
|
|
4122
|
+
qdrant: true,
|
|
4123
|
+
weaviate: true,
|
|
4124
|
+
milvus: true,
|
|
4008
4125
|
natural_language: true,
|
|
4009
4126
|
json: true,
|
|
4010
4127
|
json_without_ids: true,
|
|
@@ -4022,8 +4139,7 @@ const importFormats = [
|
|
|
4022
4139
|
"gremlin",
|
|
4023
4140
|
"sparql"
|
|
4024
4141
|
];
|
|
4025
|
-
|
|
4026
|
-
//#region src/handlers.ts
|
|
4142
|
+
// src/handlers.ts
|
|
4027
4143
|
const docsUrl = "https://react-querybuilder.js.org/docs";
|
|
4028
4144
|
/** JSON round-trip (bigints → `{ $bigint }`); strips non-JSON values. */
|
|
4029
4145
|
const toJson = (v) => JSON.parse(JSON.stringify(v, bigIntJsonStringifyReplacer));
|
|
@@ -4160,12 +4276,13 @@ const handleConvertQuery = safe(async ({ input, from, to, fields, parseOptions,
|
|
|
4160
4276
|
});
|
|
4161
4277
|
/** Diagnostic codes reported as warnings (not validity failures). */
|
|
4162
4278
|
const warningCodes = /* @__PURE__ */ new Set(["MUTED", "UNREFERENCED_FIELD"]);
|
|
4279
|
+
const isWarning = (d) => d.severity === "warning" || warningCodes.has(d.code);
|
|
4163
4280
|
const toIssue = (d) => ({
|
|
4164
4281
|
code: d.code,
|
|
4165
4282
|
message: d.message,
|
|
4166
4283
|
path: d.path
|
|
4167
4284
|
});
|
|
4168
|
-
const handleValidateQuery = safe(({ query: queryInput, fields, queryType }) => {
|
|
4285
|
+
const handleValidateQuery = safe(({ query: queryInput, fields, queryType, targetFormat }) => {
|
|
4169
4286
|
const query = parseQueryInput(queryInput);
|
|
4170
4287
|
const shapeErrors = checkShape(query);
|
|
4171
4288
|
if (shapeErrors.length > 0) return ok({
|
|
@@ -4188,10 +4305,11 @@ const handleValidateQuery = safe(({ query: queryInput, fields, queryType }) => {
|
|
|
4188
4305
|
}));
|
|
4189
4306
|
const { diagnostics } = formatQuery(rg, {
|
|
4190
4307
|
format: "diagnostics",
|
|
4191
|
-
fields: fieldList
|
|
4308
|
+
fields: fieldList,
|
|
4309
|
+
diagnosticsTargetFormat: targetFormat
|
|
4192
4310
|
});
|
|
4193
|
-
errors.push(...diagnostics.filter((d) => !
|
|
4194
|
-
const warnings = diagnostics.filter(
|
|
4311
|
+
errors.push(...diagnostics.filter((d) => !isWarning(d)).map(toIssue));
|
|
4312
|
+
const warnings = diagnostics.filter(isWarning).map(toIssue);
|
|
4195
4313
|
const valid = errors.length === 0;
|
|
4196
4314
|
const summary = valid ? `Valid.${warnings.length > 0 ? `\nWarnings:\n${warnings.map((w) => `- ${w.message}`).join("\n")}` : ""}` : void 0;
|
|
4197
4315
|
return ok({
|
|
@@ -4225,8 +4343,7 @@ const handleGetDoc = safe(({ page }) => {
|
|
|
4225
4343
|
markdown
|
|
4226
4344
|
}, markdown);
|
|
4227
4345
|
});
|
|
4228
|
-
|
|
4229
|
-
//#region src/prompts.ts
|
|
4346
|
+
// src/prompts.ts
|
|
4230
4347
|
const docs = "https://react-querybuilder.js.org/docs";
|
|
4231
4348
|
const uiPackages = {
|
|
4232
4349
|
antd: {
|
|
@@ -4306,8 +4423,7 @@ const convertFilterPrompt = (input, from = "", to = "") => user([
|
|
|
4306
4423
|
"3. If convert_query warns that rules were dropped or the result is empty, call parse_query to inspect the intermediate query and report unsupported constructs.",
|
|
4307
4424
|
"4. Show the result and the intermediate RQB query. Mention any warnings."
|
|
4308
4425
|
].join("\n"), "Convert a filter between formats");
|
|
4309
|
-
|
|
4310
|
-
//#region src/schemas.ts
|
|
4426
|
+
// src/schemas.ts
|
|
4311
4427
|
const str = (description) => ({
|
|
4312
4428
|
type: "string",
|
|
4313
4429
|
description
|
|
@@ -4472,6 +4588,11 @@ const validateQueryInputSchema = {
|
|
|
4472
4588
|
type: "string",
|
|
4473
4589
|
enum: ["standard", "ic"],
|
|
4474
4590
|
description: "Expected query type: \"standard\" (`RuleGroupType`) or \"ic\" (`RuleGroupTypeIC`). Omit to accept either."
|
|
4591
|
+
},
|
|
4592
|
+
targetFormat: {
|
|
4593
|
+
type: "string",
|
|
4594
|
+
enum: [...exportFormats, ...ormFormats],
|
|
4595
|
+
description: "Export format the query will be sent to. Rules it cannot express (e.g. vector DB formats like \"pinecone\", \"chroma\", \"qdrant\", \"weaviate\", \"milvus\") are reported as warnings."
|
|
4475
4596
|
}
|
|
4476
4597
|
},
|
|
4477
4598
|
required: ["query", "fields"],
|
|
@@ -4547,8 +4668,7 @@ const getDocInputSchema = {
|
|
|
4547
4668
|
required: ["page"],
|
|
4548
4669
|
additionalProperties: false
|
|
4549
4670
|
};
|
|
4550
|
-
|
|
4551
|
-
//#region src/server.ts
|
|
4671
|
+
// src/server.ts
|
|
4552
4672
|
/** Server name advertised to clients. */
|
|
4553
4673
|
const serverName = "react-querybuilder";
|
|
4554
4674
|
const readOnly = {
|
|
@@ -4558,10 +4678,10 @@ const readOnly = {
|
|
|
4558
4678
|
};
|
|
4559
4679
|
const v = docsIndex.version;
|
|
4560
4680
|
const descriptions = {
|
|
4561
|
-
format_query: `Convert a React Query Builder (RQB) query object to another format with the library's own formatQuery (v${v}). Prefer this over hand-writing SQL/Mongo/etc. from an RQB query. Formats: ${exportFormats.join(", ")}. String outputs: sql, mongodb (JSON string), cel, spel, jsonata, ldap, cypher, gql, sparql, gremlin, natural_language, json, json_without_ids. Object outputs: parameterized/parameterized_named ({ sql, params }), mongodb_query, jsonlogic, elasticsearch, diagnostics. Use "natural_language" to explain a query in plain English. Pass options.fields for field-aware output (e.g. parseNumbers by inputType). ORM formats (drizzle, prisma, sequelize, tanstack_db) need live ORM objects and are not supported here.`,
|
|
4681
|
+
format_query: `Convert a React Query Builder (RQB) query object to another format with the library's own formatQuery (v${v}). Prefer this over hand-writing SQL/Mongo/etc. from an RQB query. Formats: ${exportFormats.join(", ")}. String outputs: sql, mongodb (JSON string), cel, spel, jsonata, ldap, cypher, gql, sparql, gremlin, milvus (vector DB filter expr), natural_language, json, json_without_ids. Object outputs: parameterized/parameterized_named ({ sql, params }), mongodb_query, jsonlogic, elasticsearch, pinecone/chroma/qdrant/weaviate (vector DB metadata filters), diagnostics. Use "natural_language" to explain a query in plain English. Pass options.fields for field-aware output (e.g. parseNumbers by inputType). ORM formats (drizzle, prisma, sequelize, tanstack_db) need live ORM objects and are not supported here.`,
|
|
4562
4682
|
parse_query: `Parse a query string/object (${importFormats.join(", ")}) into an RQB query object (RuleGroupType, or RuleGroupTypeIC with options.independentCombinators). Use the result as the QueryBuilder \`query\`/\`defaultQuery\` prop. Pass options.fields to drop unknown fields. To go straight to another format, use convert_query instead.`,
|
|
4563
4683
|
convert_query: `Convert between query languages in one step (parse ${importFormats.join("/")} → RQB query → any format_query format), e.g. SQL WHERE clause → MongoDB, JsonLogic → parameterized SQL. Returns the result plus the intermediate RQB query. Prefer this over separate parse_query + format_query calls unless you need to edit the intermediate query. \`fields\` applies to both steps.`,
|
|
4564
|
-
validate_query: "Validate an RQB query against a fields config: shape, IC alternation (rule, combinator, rule...), unknown fields, operator allowed for field, value arity (between/in), value type vs inputType, values-list membership, placeholders. Returns { valid, errors[], warnings[] }. Run this on any query you generate before using it in app code.",
|
|
4684
|
+
validate_query: "Validate an RQB query against a fields config: shape, IC alternation (rule, combinator, rule...), unknown fields, operator allowed for field, value arity (between/in), value type vs inputType, values-list membership, placeholders. Returns { valid, errors[], warnings[] }. Pass targetFormat (e.g. \"qdrant\") to warn about rules that format can't express. Run this on any query you generate before using it in app code.",
|
|
4565
4685
|
get_query_schema: "Generate a JSON Schema describing valid RQB queries for a fields config (wraps getQueryJsonSchema). Use it for LLM structured output / tool input schemas or Ajv validation. Set options.strict for OpenAI structured outputs. For app code, call getQueryJsonSchema from @react-querybuilder/core directly; this tool previews the output.",
|
|
4566
4686
|
search_docs: `Search the React Query Builder docs (v${v}, matching this package version). Returns ranked { title, url, snippet }. Follow up with get_doc for full content.`,
|
|
4567
4687
|
get_doc: "Get a React Query Builder docs page (or one section, via #anchor) as markdown, by URL from search_docs or slug (e.g. \"utils/export\", \"components/querybuilder\", \"compat\")."
|
|
@@ -4665,7 +4785,6 @@ const createServer = () => {
|
|
|
4665
4785
|
}, ({ input, from, to }) => convertFilterPrompt(input, from, to));
|
|
4666
4786
|
return server;
|
|
4667
4787
|
};
|
|
4668
|
-
//#endregion
|
|
4669
4788
|
export { makeSnippet as C, sectionUrl as D, sectionTitle as E, getSectionMarkdown as S, searchDocs as T, importFormats as _, checkShape as a, getDocMarkdown as b, handleFormatQuery as c, handleParseQuery as d, handleSearchDocs as f, exportFormats as g, parseQueryInput as h, setupQueryBuilderPrompt as i, handleGetDoc as l, ok as m, serverName as n, fail as o, handleValidateQuery as p, convertFilterPrompt as r, handleConvertQuery as s, createServer as t, handleGetQuerySchema as u, ormFormats as v, resolveDocRef as w, getPageMarkdown as x, docsIndex as y };
|
|
4670
4789
|
|
|
4671
|
-
//# sourceMappingURL=server-
|
|
4790
|
+
//# sourceMappingURL=server-BVOAyrEd.mjs.map
|