Migrating from react-awesome-query-builder
Refer to the TypeScript reference page for information about the types and interfaces referenced below.
This guide covers moving an existing react-awesome-query-builder (RAQB) implementation to React Query Builder (RQB). For a broader feature-level overview, see Comparison with other libraries.
Two utilities do most of the work:
parseRAQBconverts a saved RAQB query tree to an RQB query object.parseRAQBFieldsconverts an RAQBConfig(or just itsfields) to an RQBfieldsarray.
Both are exported from @react-querybuilder/migrate-raqb, a standalone package outside the main React Query Builder repository. Since migration is generally a one-time task, these utilities are packaged separately to keep them out of your production bundle.
- npm
- Bun
- Yarn
- pnpm
npm i @react-querybuilder/migrate-raqb
bun add @react-querybuilder/migrate-raqb
yarn add @react-querybuilder/migrate-raqb
pnpm add @react-querybuilder/migrate-raqb
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';
The package has no runtime dependencies—only a peer dependency on @react-querybuilder/core (v8.21.2 or later). It adds no dependency on RAQB or immutable.js.
@react-querybuilder/migrate-raqb requires React Query Builder v8. If you're migrating from RAQB, migrate straight to the latest version—there's no reason to land on v7 first. Run the conversion under v8, save the resulting queries, and the output is plain JSON that works anywhere.
Once your queries are converted and saved in RQB format, uninstall @react-querybuilder/migrate-raqb. Nothing in your application needs it at runtime.
Concept mapping
| RAQB | React Query Builder |
|---|---|
Config | Discrete props: fields, operators, combinators, controlElements |
Config.fields | fields |
Query + Builder render prop | A single <QueryBuilder /> element |
immutable tree + Utils.getTree() | Plain JSON query object, used directly |
group node | Rule group (an object with a rules array) |
properties.conjunction | combinator |
properties.not | not |
rule node | Rule ({ field, operator, value }) |
properties.valueSrc | valueSource |
properties.isLocked | disabled (see showLockButtons) |
rule_group (!group field) | Rule with a match mode and a nested group as its value |
switch_group / case_group | No equivalent — see Unsupported constructs |
| Widgets | valueEditorType / inputType or a custom valueEditor |
funcs | @react-querybuilder/expr |
sqlFormat, mongodbFormat, etc. | formatQuery |
loadFromJsonLogic, loadFromSpel | parseJsonLogic, parseSpEL |
Rendering the component
RAQB 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.
import { useCallback, useState } from 'react';
import type {
BuilderProps,
Config,
ImmutableTree,
JsonGroup,
} from '@react-awesome-query-builder/ui';
import { BasicConfig, Builder, Query, Utils as QbUtils } from '@react-awesome-query-builder/ui';
import '@react-awesome-query-builder/ui/css/styles.css';
const config: Config = {
...BasicConfig,
fields: {
price: { label: 'Price', type: 'number' },
color: {
label: 'Color',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'yellow', title: 'Yellow' },
{ value: 'green', title: 'Green' },
],
},
},
},
settings: {
...BasicConfig.settings,
showNot: true,
showLock: true,
maxNesting: 3,
addRuleLabel: 'New rule',
},
};
const initialTree: JsonGroup = { id: QbUtils.uuid(), type: 'group' };
export const App = () => {
const [tree, setTree] = useState(() => QbUtils.loadTree(initialTree));
const onChange = useCallback((immutableTree: ImmutableTree) => {
setTree(immutableTree);
// Convert before saving — the state itself is an immutable.js tree
console.log(QbUtils.getTree(immutableTree));
}, []);
const renderBuilder = useCallback(
(props: BuilderProps) => (
<div className="query-builder-container">
<div className="query-builder qb-lite">
<Builder {...props} />
</div>
</div>
),
[]
);
return (
<>
<Query {...config} value={tree} onChange={onChange} renderBuilder={renderBuilder} />
<pre>{QbUtils.sqlFormat(tree, config)}</pre>
</>
);
};
import { useState } from 'react';
import type { Field, RuleGroupType } from 'react-querybuilder';
import { formatQuery, QueryBuilder } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';
const fields: Field[] = [
{ name: 'price', label: 'Price', inputType: 'number' },
{
name: 'color',
label: 'Color',
valueEditorType: 'select',
values: [
{ name: 'yellow', label: 'Yellow' },
{ name: 'green', label: 'Green' },
],
},
];
const initialQuery: RuleGroupType = { combinator: 'and', rules: [] };
export const App = () => {
// Already plain JSON — save it as-is
const [query, setQuery] = useState(initialQuery);
return (
<>
<QueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
showNotToggle
showLockButtons
maxLevels={3}
translations={{ addRule: { label: 'New rule' } }}
/>
<pre>{formatQuery(query, 'sql')}</pre>
</>
);
};
Notable differences:
- Configuration is spread across discrete props instead of one
Configobject, so there's no base config to spread (noBasicConfig) and nothing to keep in state alongside the query. onQueryChangereceives the query object directly, so it can be passed straight to auseStatesetter and saved without conversion.- There is no
renderBuilderindirection or required wrapper markup. UsecontrolClassnamesandcontrolElementsto customize markup and styling. - Export functions take the query object, not a tree plus a config.
Settings
RAQB's config.settings correspond to individual <QueryBuilder /> props:
| RAQB setting | RQB prop |
|---|---|
showNot | showNotToggle |
showLock | showLockButtons |
canReorder / canRegroup | enableDragAndDrop (requires @react-querybuilder/dnd) |
maxNesting | maxLevels |
maxNumberOfRules | Return false from onAddRule |
immutableGroupsMode and friends | disabled (all or by path) |
clearValueOnChangeField / clearValueOnChangeOp | resetOnFieldChange / resetOnOperatorChange |
setOpOnChangeField | getDefaultOperator, autoSelectOperator |
defaultField / defaultOperator | getDefaultField / getDefaultOperator |
defaultConjunction | First entry of combinators, or the combinator of your initial query |
addRuleLabel, valuePlaceholder, and other labels | translations |
fieldSeparator | Handled at conversion time by parseRAQBFields (field names are flattened) |
Converting the config
parseRAQBFields accepts either a full RAQB Config object or just the fields map, and returns an RQB fields array.
import { parseRAQBFields } from '@react-querybuilder/migrate-raqb';
const fields = parseRAQBFields({
price: {
label: 'Price',
type: 'number',
fieldSettings: { min: 0, max: 100 },
},
color: {
label: 'Color',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'yellow', title: 'Yellow' },
{ value: 'green', title: 'Green' },
],
},
},
});
[
{ "name": "price", "label": "Price", "inputType": "number" },
{
"name": "color",
"label": "Color",
"valueEditorType": "select",
"values": [
{ "name": "yellow", "label": "Yellow" },
{ "name": "green", "label": "Green" }
]
}
]
Translation details:
- Nested
!structfields are flattened to dot-separated names, matching the field paths stored in RAQB query trees. The separator comes fromsettings.fieldSeparator(default"."), or thefieldSeparatoroption. !groupfields become fields withmatchModesandsubproperties, which is RQB's subquery representation.typemaps toinputType(number,date,time,datetime-local, etc.) orvalueEditorType(select,multiselect,checkbox).fieldSettings.listValuesandfieldSettings.treeValuesmap tovalues. Tree values are flattened.operators,defaultOperator,excludeOperators,valueSources, anddefaultValueare translated to their RQB equivalents.- Fields marked
hideForSelectare omitted unless you passincludeHidden: true. - Export-time settings (
fieldName,tableName,jsonLogicVar,isSpelVariable) andfieldSettings.validateValueare not translated.
parseRAQBFields options
fieldSeparator(string): Character used to join nested field names.operatorMap(Record<string, string>): Additional or overriding operator name mappings. Should match theoperatorMappassed toparseRAQB.includeHidden(boolean): Include fields markedhideForSelect. Defaults tofalse.onUnsupported((info: RAQBUnsupportedInfo) => void): Called for each construct that could not be fully translated.
Converting queries
parseRAQB accepts RAQB's plain-JSON tree — the output of Utils.getTree(immutableTree) — or a JSON string of the same. Immutable.js trees are not accepted; passing one throws an error explaining the fix.
// In your RAQB app, save the plain JSON form:
const jsonTree = QbUtils.getTree(immutableTree);
import { parseRAQB } from '@react-querybuilder/migrate-raqb';
const query = parseRAQB(jsonTree);
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: { field: 'price', operator: 'greater', value: [10], valueSrc: ['value'] },
},
{
type: 'rule',
properties: { field: 'color', operator: 'select_any_in', value: [['yellow', 'green']] },
},
],
});
{
"combinator": "and",
"rules": [
{ "field": "price", "operator": ">", "value": 10 },
{ "field": "color", "operator": "in", "value": "yellow,green" }
]
}
Both children1 shapes are supported: the default array form and the keyed-object form produced when children1AsArray is false.
Operator mapping
| RAQB | RQB |
|---|---|
equal / not_equal | = / != |
less / less_or_equal | < / <= |
greater / greater_or_equal | > / >= |
like / not_like | contains / doesNotContain |
starts_with / ends_with | beginsWith / endsWith |
between / not_between | between / notBetween |
is_null / is_not_null | null / notNull |
is_empty / is_not_empty | = / != with an empty string |
select_equals / select_not_equals | = / != |
select_any_in / select_not_any_in | in / notIn |
multiselect_equals / multiselect_not_equals | = / != (array value kept) |
multiselect_contains / multiselect_not_contains | contains / doesNotContain |
some / all / none | Match modes some / all / none |
!group counts equal / greater_or_equal / less_or_equal | Match modes exactly / atLeast / atMost with a threshold |
Add to or override this map with the operatorMap option.
parseRAQB options
Beyond the standard import configuration options (fields, listsAsArrays, generateIDs, independentCombinators, etc.), parseRAQB accepts:
operatorMap(Record<string, DefaultOperatorName>): Additional or overriding RAQB-to-RQB operator mappings, merged over the defaults.functionMap(Record<string, string>): Additional or overriding RAQB-to-exprfunction name mappings, merged over the defaults.funcArgOrder(Record<string, string[]>): Explicit argument order per RAQB function name. By default, argument order follows the key order of the serialized function object.relativeDateTimes(boolean): Convert RAQB's built-in date/time functions to relative date/time values instead of expressions. Defaults totrue. See Date and time functions.onUnsupported((info: RAQBUnsupportedInfo) => void): Called for each construct that could not be converted.
As with other parsers, passing fields restricts the output to rules whose fields exist in the list.
Functions and expressions
RAQB's valueSrc: "func" operands convert to RQB expressions. 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.
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'name',
operator: 'equal',
valueSrc: ['func'],
value: [{ func: 'LOWER', args: { str: { value: 'Steve', valueSrc: 'value' } } }],
},
},
],
});
{
"combinator": "and",
"rules": [
{
"field": "name",
"operator": "=",
"valueSource": "expression",
"value": { "kind": "func", "fn": "lower", "args": [{ "kind": "value", "value": "Steve" }] }
}
]
}
Queries containing expressions require @react-querybuilder/expr to render, validate, or serialize. Wrap your query builder in QueryBuilderExpressions and register the export processor as described in the expressions documentation. Without that package, expression values will not display or export correctly.
RAQB's LOWER and UPPER map to expr's lower and upper, and LINEAR_REGRESSION is expanded to the equivalent arithmetic expression. Every other RAQB function—apart from the date/time functions described below—is passed through under its original name and reported to onUnsupported. To render or export those, either register matching function metadata with @react-querybuilder/expr or map them to existing functions with functionMap.
Date and time functions
RAQB's built-in date/time functions don't really compute anything—they describe a date relative to "now". RQB represents that concept as a value rather than an expression, so parseRAQB converts them to @react-querybuilder/datetime's relative date/time value shape ({ mode: "relative", anchor, offset, unit }), which every date/time rule processor serializes symbolically (e.g. current_timestamp - interval '7 days').
| RAQB | RQB value |
|---|---|
NOW() | { anchor: 'now', offset: 0 } |
TODAY() / START_OF_TODAY() | { anchor: 'startOfDay', offset: 0 } |
TRUNCATE_DATETIME(NOW(), dim) | { anchor: 'startOf<Dim>', offset: 0 } |
RELATIVE_DATE(TIME)(date, op, val, dim) | { anchor: <from date>, offset: ±val, unit: dim } |
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'created',
operator: 'between',
valueSrc: ['func', 'func'],
value: [
{
func: 'RELATIVE_DATETIME',
args: { op: { value: 'minus' }, val: { value: 7 }, dim: { value: 'day' } },
},
{ func: 'NOW', args: {} },
],
},
},
],
});
{
"combinator": "and",
"rules": [
{
"field": "created",
"operator": "between",
"value": [
{ "mode": "relative", "anchor": "now", "offset": -7, "unit": "day" },
{ "mode": "relative", "anchor": "now", "offset": 0, "unit": "day" }
]
}
]
}
With @react-querybuilder/datetime's SQL rule processor, that query exports as "created" between current_timestamp - interval '7 days' and current_timestamp.
A few RAQB calls fall outside what relative values can express, and are converted to expressions instead (and reported to onUnsupported):
- A
"second"dimension. RQB's smallest relative unit isminute. - Truncation to
hour,minute, orsecond. RQB truncates only to day, week, month, or year. - Truncation applied after an offset, e.g.
TRUNCATE_DATETIME(RELATIVE_DATETIME(NOW(), 'minus', 3, 'day'), 'month'). RQB applies the anchor first and the offset second, so only the reverse nesting is representable.
Pass relativeDateTimes: false to convert date/time functions to expressions like any other function.
Unsupported constructs
parseRAQB never throws on unrecognized content (aside from immutable.js input). Anything it can't convert is skipped, and a partial query is always returned. Use onUnsupported to log or surface what was dropped.
const unsupported: RAQBUnsupportedInfo[] = [];
const query = parseRAQB(jsonTree, {
onUnsupported: info => unsupported.push(info),
});
Reported constructs include:
switch_group/case_group(RAQB's ternary mode). RQB has no equivalent; consider@react-querybuilder/rules-enginefor conditional logic.proximityand itsoperatorOptions, which have no RQB counterpart.!groupstrict-inequality count operators (less,greater), since RQB match modes cover onlyexactly,atLeast, andatMost.- Functions with no
@react-querybuilder/exprequivalent, which pass through by name.
Putting it together
import { QueryBuilder } from 'react-querybuilder';
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';
import raqbConfig from './raqbConfig';
import savedTree from './savedQuery.json';
const fields = parseRAQBFields(raqbConfig);
const defaultQuery = parseRAQB(savedTree, { fields });
export const App = () => <QueryBuilder fields={fields} defaultQuery={defaultQuery} />;
Converting back to RAQB
The reverse conversion is useful for a phased migration where both query builders must read the same stored queries. @react-querybuilder/migrate-raqb exports formatRAQB for this purpose. (There is no "raqb" formatQuery export format; formatRAQB wraps formatQuery's ruleGroupProcessor option, which takes precedence over format.)
import { formatRAQB } from '@react-querybuilder/migrate-raqb';
import { Utils } from '@react-awesome-query-builder/core';
const jsonTree = formatRAQB(query, { fields });
const immutableTree = Utils.checkTree(Utils.loadTree(jsonTree), raqbConfig);
Pass fields for the most faithful output—several RAQB operators collapse to a single RQB operator, and the field's valueEditorType is used to disambiguate them.
formatRAQBFields, the inverse of parseRAQBFields, is exported from the same package:
import { formatRAQBFields } from '@react-querybuilder/migrate-raqb';
const raqbConfig = { ...BasicConfig, fields: formatRAQBFields(fields) };
formatRAQB accepts all formatQuery options except format, ruleGroupProcessor, and fallbackExpression, plus fallbackTree (the tree returned when the query is empty or fails validation) and:
| Option | Default | Description |
|---|---|---|
raqbOperatorMap | {} | Additional/overriding RQB-to-RAQB operator mappings, keyed by RQB operator name. |
raqbFunctionMap | {} | Additional/overriding expression-function-to-RAQB function name mappings. |
raqbFuncArgOrder | {} | Argument names per RAQB function name. Defaults cover RAQB's built-ins; others get arg0, arg1, ... |
raqbFieldSeparator | "." | Separator used to qualify sub-query rule fields with their parent !group field name. |
raqbRelativeDateTimes | true | Convert relative date/time values to RAQB's built-in date/time functions. |
raqbValueTypes | false | Emit valueType entries inferred from each field's inputType/valueEditorType. |
The mapping is the inverse of the tables above, with these caveats:
- RQB's
doesNotBeginWithanddoesNotEndWithhave no RAQB default equivalent (there is nonot_starts_with/not_ends_with); rules using them are omitted unless you supply araqbOperatorMapentry. - The
"parameter"value source has no RAQB counterpart, so those rules are omitted. in/notInmap toselect_any_in/select_not_any_in, which RAQB only allows forselectandmultiselectfields. On a plain text field RAQB'scheckTreewill reject the rule.- Field-to-field comparisons are more restricted in RAQB: its
fieldwidget for thetexttype supports onlyequal,not_equal, andproximity, socontains/beginsWith/endsWith/</>against another field are rejected. - RAQB clamps inverted ranges, so a
betweenrule withpreserveValueOrderand out-of-order bounds (e.g.[100, 0]) becomes[100, 100]once loaded. bigintvalues are narrowed tonumber, since RAQB trees must be JSON-serializable.endOf*relative date/time anchors are emitted as plain values, since RAQB's built-in functions only truncate to the start of a period.- RAQB defines no
XORconjunction.xorcombinators are emitted as"XOR"rather than silently degraded, so RAQB'scheckTreewill flag them unless a matching custom conjunction is configured. - RAQB's several operators that collapse to one RQB operator (
equal/select_equals/multiselect_equals→=) are disambiguated using each field'svalueEditorType, so passfieldsfor the most faithful output. - When
validatorresults invalidate the entire query,formatRAQBreturnsfallbackTree(raqbFallback, an emptyANDgroup, by default). If you callformatQuerydirectly withdefaultRuleGroupProcessorRAQB, passfallbackExpression: raqbFallback as unknown as string—formatQueryshort-circuits before the rule group processor runs, and its own default fallback is a SQL string.
To combine RAQB output with other formatQuery behavior, or to supply a custom ruleProcessor, use defaultRuleGroupProcessorRAQB directly. RAQB options then move into context:
import { formatQuery } from 'react-querybuilder';
import { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';
const jsonTree = formatQuery(query, {
ruleGroupProcessor: defaultRuleGroupProcessorRAQB,
fields,
context: { raqbValueTypes: true },
fallbackExpression: raqbFallback as unknown as string,
});
RAQB's last stable release was 6.6.15 in May 2025; these utilities exist to give RAQB users a straightforward path to React Query Builder.