Skip to main content
Version: v7 / v8

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:

  • parseRAQB converts a saved RAQB query tree to an RQB query object.
  • parseRAQBFields converts an RAQB Config (or just its fields) to an RQB fields array.

Both are exported from @react-querybuilder/migrate-raqb, 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 i @react-querybuilder/migrate-raqb
import { parseRAQB, parseRAQBFields } from '@react-querybuilder/migrate-raqb';

The package has no runtime dependencies—only a peer dependency on @react-querybuilder/core (v8.21.2 or later). It adds no dependency on RAQB or immutable.js.

info

@react-querybuilder/migrate-raqb requires React Query Builder v8. If you're migrating from RAQB, migrate straight to the latest version—there's no reason to land on v7 first. Run the conversion under v8, save the resulting queries, and the output is plain JSON that works anywhere.

tip

Once your queries are converted and saved in RQB format, uninstall @react-querybuilder/migrate-raqb. Nothing in your application needs it at runtime.

Concept mapping

RAQBReact Query Builder
ConfigDiscrete props: fields, operators, combinators, controlElements
Config.fieldsfields
Query + Builder render propA single <QueryBuilder /> element
immutable tree + Utils.getTree()Plain JSON query object, used directly
group nodeRule group (an object with a rules array)
properties.conjunctioncombinator
properties.notnot
rule nodeRule ({ field, operator, value })
properties.valueSrcvalueSource
properties.isLockeddisabled (see showLockButtons)
rule_group (!group field)Rule with a match mode and a nested group as its value
switch_group / case_groupNo equivalent — see Unsupported constructs
WidgetsvalueEditorType / inputType or a custom valueEditor
funcs@react-querybuilder/expr
sqlFormat, mongodbFormat, etc.formatQuery
loadFromJsonLogic, loadFromSpelparseJsonLogic, 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.

Before (RAQB)
import { useCallback, useState } from 'react';
import type {
BuilderProps,
Config,
ImmutableTree,
JsonGroup,
} from '@react-awesome-query-builder/ui';
import { BasicConfig, Builder, Query, Utils as QbUtils } from '@react-awesome-query-builder/ui';
import '@react-awesome-query-builder/ui/css/styles.css';

const config: Config = {
...BasicConfig,
fields: {
price: { label: 'Price', type: 'number' },
color: {
label: 'Color',
type: 'select',
fieldSettings: {
listValues: [
{ value: 'yellow', title: 'Yellow' },
{ value: 'green', title: 'Green' },
],
},
},
},
settings: {
...BasicConfig.settings,
showNot: true,
showLock: true,
maxNesting: 3,
addRuleLabel: 'New rule',
},
};

const initialTree: JsonGroup = { id: QbUtils.uuid(), type: 'group' };

export const App = () => {
const [tree, setTree] = useState(() => QbUtils.loadTree(initialTree));

const onChange = useCallback((immutableTree: ImmutableTree) => {
setTree(immutableTree);
// Convert before saving — the state itself is an immutable.js tree
console.log(QbUtils.getTree(immutableTree));
}, []);

const renderBuilder = useCallback(
(props: BuilderProps) => (
<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>
</>
);
};
After (RQB)
import { useState } from 'react';
import type { Field, RuleGroupType } from 'react-querybuilder';
import { formatQuery, QueryBuilder } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const fields: Field[] = [
{ name: 'price', label: 'Price', inputType: 'number' },
{
name: 'color',
label: 'Color',
valueEditorType: 'select',
values: [
{ name: 'yellow', label: 'Yellow' },
{ name: 'green', label: 'Green' },
],
},
];

const initialQuery: RuleGroupType = { combinator: 'and', rules: [] };

export const App = () => {
// Already plain JSON — save it as-is
const [query, setQuery] = useState(initialQuery);

return (
<>
<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 Config object, so there's no base config to spread (no BasicConfig) and nothing to keep in state alongside the query.
  • onQueryChange receives the query object directly, so it can be passed straight to a useState setter and saved without conversion.
  • There is no renderBuilder indirection or required wrapper markup. Use controlClassnames and controlElements to 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 settingRQB prop
showNotshowNotToggle
showLockshowLockButtons
canReorder / canRegroupenableDragAndDrop (requires @react-querybuilder/dnd)
maxNestingmaxLevels
maxNumberOfRulesReturn false from onAddRule
immutableGroupsMode and friendsdisabled (all or by path)
clearValueOnChangeField / clearValueOnChangeOpresetOnFieldChange / resetOnOperatorChange
setOpOnChangeFieldgetDefaultOperator, autoSelectOperator
defaultField / defaultOperatorgetDefaultField / getDefaultOperator
defaultConjunctionFirst entry of combinators, or the combinator of your initial query
addRuleLabel, valuePlaceholder, and other labelstranslations
fieldSeparatorHandled 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' },
],
},
},
});
Result
[
{ "name": "price", "label": "Price", "inputType": "number" },
{
"name": "color",
"label": "Color",
"valueEditorType": "select",
"values": [
{ "name": "yellow", "label": "Yellow" },
{ "name": "green", "label": "Green" }
]
}
]

Translation details:

  • Nested !struct fields are flattened to dot-separated names, matching the field paths stored in RAQB query trees. The separator comes from settings.fieldSeparator (default "."), or the fieldSeparator option.
  • !group fields become fields with matchModes and subproperties, which is RQB's subquery representation.
  • type maps to inputType (number, date, time, datetime-local, etc.) or valueEditorType (select, multiselect, checkbox).
  • fieldSettings.listValues and fieldSettings.treeValues map to values. Tree values are flattened.
  • operators, defaultOperator, excludeOperators, valueSources, and defaultValue are translated to their RQB equivalents.
  • Fields marked hideForSelect are omitted unless you pass includeHidden: true.
  • Export-time settings (fieldName, tableName, jsonLogicVar, isSpelVariable) and fieldSettings.validateValue are not translated.

parseRAQBFields options

  • fieldSeparator (string): Character used to join nested field names.
  • operatorMap (Record<string, string>): Additional or overriding operator name mappings. Should match the operatorMap passed to parseRAQB.
  • includeHidden (boolean): Include fields marked hideForSelect. Defaults to false.
  • onUnsupported ((info: RAQBUnsupportedInfo) => void): Called for each construct that could not be fully translated.

Converting queries

parseRAQB accepts RAQB's plain-JSON tree — the output of Utils.getTree(immutableTree) — or a JSON string of the same. Immutable.js trees are not accepted; passing one throws an error explaining the fix.

// In your RAQB app, save the plain JSON form:
const jsonTree = QbUtils.getTree(immutableTree);
import { parseRAQB } from '@react-querybuilder/migrate-raqb';

const query = parseRAQB(jsonTree);
Example
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: { field: 'price', operator: 'greater', value: [10], valueSrc: ['value'] },
},
{
type: 'rule',
properties: { field: 'color', operator: 'select_any_in', value: [['yellow', 'green']] },
},
],
});
Result
{
"combinator": "and",
"rules": [
{ "field": "price", "operator": ">", "value": 10 },
{ "field": "color", "operator": "in", "value": "yellow,green" }
]
}

Both children1 shapes are supported: the default array form and the keyed-object form produced when children1AsArray is false.

Operator mapping

RAQBRQB
equal / not_equal= / !=
less / less_or_equal< / <=
greater / greater_or_equal> / >=
like / not_likecontains / doesNotContain
starts_with / ends_withbeginsWith / endsWith
between / not_betweenbetween / notBetween
is_null / is_not_nullnull / notNull
is_empty / is_not_empty= / != with an empty string
select_equals / select_not_equals= / !=
select_any_in / select_not_any_inin / notIn
multiselect_equals / multiselect_not_equals= / != (array value kept)
multiselect_contains / multiselect_not_containscontains / doesNotContain
some / all / noneMatch modes some / all / none
!group counts equal / greater_or_equal / less_or_equalMatch 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-expr function 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 to true. 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.

Example
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'name',
operator: 'equal',
valueSrc: ['func'],
value: [{ func: 'LOWER', args: { str: { value: 'Steve', valueSrc: 'value' } } }],
},
},
],
});
Result
{
"combinator": "and",
"rules": [
{
"field": "name",
"operator": "=",
"valueSource": "expression",
"value": { "kind": "func", "fn": "lower", "args": [{ "kind": "value", "value": "Steve" }] }
}
]
}
caution

Queries containing expressions require @react-querybuilder/expr 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').

RAQBRQB 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 }
Example
parseRAQB({
type: 'group',
properties: { conjunction: 'AND' },
children1: [
{
type: 'rule',
properties: {
field: 'created',
operator: 'between',
valueSrc: ['func', 'func'],
value: [
{
func: 'RELATIVE_DATETIME',
args: { op: { value: 'minus' }, val: { value: 7 }, dim: { value: 'day' } },
},
{ func: 'NOW', args: {} },
],
},
},
],
});
Result
{
"combinator": "and",
"rules": [
{
"field": "created",
"operator": "between",
"value": [
{ "mode": "relative", "anchor": "now", "offset": -7, "unit": "day" },
{ "mode": "relative", "anchor": "now", "offset": 0, "unit": "day" }
]
}
]
}

With @react-querybuilder/datetime's SQL rule processor, that query exports as "created" between current_timestamp - interval '7 days' and current_timestamp.

A few RAQB calls fall outside what relative values can express, and are converted to expressions instead (and reported to onUnsupported):

  • A "second" dimension. RQB's smallest relative unit is minute.
  • Truncation to hour, minute, or second. RQB truncates only to day, week, month, or year.
  • Truncation applied after an offset, e.g. TRUNCATE_DATETIME(RELATIVE_DATETIME(NOW(), 'minus', 3, 'day'), 'month'). RQB applies the anchor first and the offset second, so only the reverse nesting is representable.

Pass relativeDateTimes: false to convert date/time functions to expressions like any other function.

Unsupported constructs

parseRAQB never throws on unrecognized content (aside from immutable.js input). Anything it can't convert is skipped, and a partial query is always returned. Use onUnsupported to log or surface what was dropped.

const unsupported: RAQBUnsupportedInfo[] = [];

const query = parseRAQB(jsonTree, {
onUnsupported: info => unsupported.push(info),
});

Reported constructs include:

  • switch_group / case_group (RAQB's ternary mode). RQB has no equivalent; consider @react-querybuilder/rules-engine for conditional logic.
  • proximity and its operatorOptions, which have no RQB counterpart.
  • !group strict-inequality count operators (less, greater), since RQB match modes cover only exactly, atLeast, and atMost.
  • Functions with no @react-querybuilder/expr equivalent, which pass through by name.

Putting it together

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:

OptionDefaultDescription
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.
raqbRelativeDateTimestrueConvert relative date/time values to RAQB's built-in date/time functions.
raqbValueTypesfalseEmit valueType entries inferred from each field's inputType/valueEditorType.

The mapping is the inverse of the tables above, with these caveats:

  • RQB's doesNotBeginWith and doesNotEndWith have no RAQB default equivalent (there is no not_starts_with/not_ends_with); rules using them are omitted unless you supply a raqbOperatorMap entry.
  • The "parameter" value source has no RAQB counterpart, so those rules are omitted.
  • in/notIn map to select_any_in/select_not_any_in, which RAQB only allows for select and multiselect fields. On a plain text field RAQB's checkTree will reject the rule.
  • Field-to-field comparisons are more restricted in RAQB: its field widget for the text type supports only equal, not_equal, and proximity, so contains/beginsWith/endsWith/</> against another field are rejected.
  • RAQB clamps inverted ranges, so a between rule with preserveValueOrder and out-of-order bounds (e.g. [100, 0]) becomes [100, 100] once loaded.
  • bigint values are narrowed to number, since RAQB trees must be JSON-serializable.
  • endOf* relative date/time anchors are emitted as plain values, since RAQB's built-in functions only truncate to the start of a period.
  • RAQB defines no XOR conjunction. xor combinators are emitted as "XOR" rather than silently degraded, so RAQB's checkTree will flag them unless a matching custom conjunction is configured.
  • RAQB's several operators that collapse to one RQB operator (equal/select_equals/multiselect_equals=) are disambiguated using each field's valueEditorType, so pass fields for the most faithful output.
  • When validator results invalidate the entire query, formatRAQB returns fallbackTree (raqbFallback, an empty AND group, by default). If you call formatQuery directly with defaultRuleGroupProcessorRAQB, pass fallbackExpression: raqbFallback as unknown as stringformatQuery short-circuits before the rule group processor runs, and its own default fallback is a SQL string.

To combine RAQB output with other formatQuery behavior, or to supply a custom ruleProcessor, use defaultRuleGroupProcessorRAQB directly. RAQB options then move into context:

import { formatQuery } from 'react-querybuilder';
import { defaultRuleGroupProcessorRAQB, raqbFallback } from '@react-querybuilder/migrate-raqb';

const jsonTree = formatQuery(query, {
ruleGroupProcessor: defaultRuleGroupProcessorRAQB,
fields,
context: { raqbValueTypes: true },
fallbackExpression: raqbFallback as unknown as string,
});

RAQB's last stable release was 6.6.15 in May 2025; these utilities exist to give RAQB users a straightforward path to React Query Builder.