Migrating to v7 or v8
Version 8 has no breaking changes from version 7, except for @react-querybuilder/chakra. If you're not using the Chakra UI package, you can upgrade from v7 to v8 without any code changes.More information
@react-querybuilder/chakra@7 supports Chakra UI v2@react-querybuilder/chakra@8 supports Chakra UI v3@react-querybuilder/chakra2 continues supporting Chakra UI v2
Version 7 requires minimal code changes when upgrading from v6. While some defaults have changed, most applications will work without modifications. To take advantage of performance improvements and new features, you may need minor refactoring.
Previous migration instructions: v5 to v6 / v4 to v5 / v3 to v4.
Breaking changes
React 18 required
React 18 is now the minimum supported version due to the react-redux v9 dependency.
TypeScript 5.1+ required
The minimum TypeScript version is now 5.1. Component props remain largely unchanged, but their TypeScript interfaces have been overhauled. If you don't use generics in custom subcomponents, you likely won't need any TypeScript changes.
QueryBuilder props
The query type is now automatically inferred from your query or defaultQuery prop instead of the deprecated independentCombinators prop.
QueryBuilderProps now requires four generic arguments:
- Query type (extending
RuleGroupTypeorRuleGroupTypeIC) - Field type
- Operator type
- Combinator type
The latter three must extend FullOption or the more specific FullField/FullOperator/FullCombinator.
Good news: JSX components can usually infer these types automatically from your props, so most users won't need changes.
// Valid in version 6:
const qbp6: QueryBuilderProps = {};
// Version 7 with the defaults (equivalent to the "version 6" line above):
const qbp7: QueryBuilderProps<RuleGroupType, FullField, FullOperator, FullCombinator> = {};
// Also valid in version 7 (since FullField, FullOperator, and FullCombinator all extend FullOption):
const qbp7a: QueryBuilderProps<RuleGroupType, FullOption, FullOption, FullOption> = {};
Option-type props
Custom subcomponents must now use FullOption extensions instead of Option for props like fields, operators, and values.
Key differences:
Full*types require avalueproperty (same type asname)*ByValuetypes make thenameproperty optionalInterface "Full*" counterpart "*ByValue" counterpart FieldFullFieldFieldByValueOperatorFullOperatorOperatorByValueCombinatorFullCombinatorCombinatorByValue- The first generic argument of
ValueEditorProps,ValueSelectorProps, andFieldSelectorPropsmust extendFullOptioninstead ofOptionas in version 6.- In the case of
ValueEditorPropsandFieldSelectorProps, preferFullFieldoverFullOption. - Where editor/selector prop type generics have been used, upgrading to version 7 should only require a minor update similar to this:
type MyFieldNames = "firstName" | "lastName" | "birthdate";
-const MyValueEditor = (props: ValueEditorProps<Field<MyFieldNames>>) => {
+const MyValueEditor = (props: ValueEditorProps<FullField<MyFieldNames>>) => {
// ^ Field -> FullField
return <ValueEditor {...props} />
}
- In the case of
Parser functions removed from main bundle
Parser functions are now only available as separate imports to reduce bundle size by ~50%. This affects infrequently-used utilities that were previously importable from the main package.
// Version 6 only
-import { parseCEL } from "react-querybuilder"
-import { parseJsonLogic } from "react-querybuilder"
-import { parseMongoDB } from "react-querybuilder"
-import { parseSQL } from "react-querybuilder"
// Version 6 or 7
+import { parseCEL } from "react-querybuilder/parseCEL"
+import { parseJsonLogic } from "react-querybuilder/parseJsonLogic"
+import { parseMongoDB } from "react-querybuilder/parseMongoDB"
+import { parseSQL } from "react-querybuilder/parseSQL"
// (New in version 7)
+import { parseSpEL } from "react-querybuilder/parseSpEL"
+import { parseJSONata } from "react-querybuilder/parseJSONata"
Miscellaneous
- MUI:
@react-querybuilder/materialusesTextFieldinstead ofInput(v7.7.0+) - Mantine: Now requires Mantine 7+
- JSON export:
"json_without_ids"format now preserves custom properties - MongoDB parser: Generates more concise queries for
$notoperators - Types: New
Pathtype alias (same asnumber[]) - Independent combinators:
RuleGroupTypeICensures nocombinatorproperty - Hooks:
useQueryBuilderrequiresQueryBuilderStateProviderancestor - Event handling:
useStopEventPropagationnow takes single function parameter
New features
Performance improvements
Ensure all QueryBuilder props have stable references or are memoized.
Version 7 aggressively memoizes props and components for better performance, especially with large queries. To benefit from this optimization:
- Move static props outside your component (like
fieldsarrays) - Use
useMemoanduseCallbackfor dynamic props - Avoid inline definitions in JSX
- Consider using
defaultQueryinstead ofqueryfor uncontrolled behavior
// ❌ BAD: Unstable references cause unnecessary re-renders
function App() {
const { t } = useTranslation();
const [query, setQuery] = useState();
// Recreated on every render
const getOperators = (field: Field) => t(defaultOperators);
return (
<QueryBuilder
onQueryChange={q => setQuery(q)} // Inline function
fields={[
// Inline array
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
]}
getOperators={getOperators}
/>
);
}
// ✅ GOOD: Stable references enable performance optimizations
const fields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
function App() {
const { t } = useTranslation();
const [query, setQuery] = useState();
// Memoized with stable dependencies
const getOperators = useCallback((field: Field) => t(defaultOperators), [t]);
return (
<QueryBuilder
onQueryChange={setQuery} // State setter has stable reference
fields={fields} // Defined outside component
getOperators={getOperators} // Memoized
/>
);
}
useState and useReducer setters have stable references and don't need memoization. You can now pass them directly: onQueryChange={setQuery}.
Option lists now support value identifiers
Previously, option lists (fields, combinators, etc.) required a name property as the unique identifier. Version 7 also accepts value as the identifier, making it easier to integrate with libraries like react-select.
- If both
nameandvalueexist,valuetakes precedence QueryBuilderensures both properties are present, making them equivalent if only one is provided- Subcomponents can assume both properties will always be available
When using Field, Combinator, and Operator interfaces directly, name is still required. Use FlexibleOption to avoid this requirement, though you'll lose IntelliSense for specific type properties.
Field identifier types are now inferred from your fields prop when using narrowed string types. This provides better TypeScript support for custom components.
For example, Field<MyFields>[] will automatically type your subcomponents with MyFields instead of generic string, preventing type errors:
type MyFields = 'firstName' | 'lastName' | 'birthDate';
const fields: Field<MyFields>[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
{ name: 'birthDate', label: 'Birth Date' },
];
function MyValueEditor(props: ValueEditorProps<FullField<MyFields>>) {
if (props.field === 'invalid') {
// TypeScript error: 'invalid' is not assignable to MyFields
return null;
}
return <ValueEditor {...props} />;
}
function App() {
return (
<QueryBuilder
fields={fields} // Types are automatically inferred
controlElements={{ valueEditor: MyValueEditor }}
/>
);
}
Bulk component overrides
Two new "bulk override" properties simplify customization:
actionElement- Sets the default for all button componentsvalueSelector- Sets the default for all selector components
This lets you customize all buttons or all selectors at once instead of individually. Specific controlElements assignments override these bulk settings.
controlElements property | Sets default for |
|---|---|
valueSelector | combinatorSelector, fieldSelector, operatorSelector, valueSourceSelector, valueEditor (when rendering a value selector) |
actionElement | addGroupAction, addRuleAction, cloneGroupAction, cloneRuleAction, lockGroupAction, lockRuleAction, removeGroupAction, removeRuleAction |
These same two properties have also been added to the controlClassnames prop, but they add to, not replace, classnames declared for specific components.
New query management methods
You no longer need to pass the query object via the context prop.
Three new methods simplify query management from custom components:
| Method | Description |
|---|---|
props.schema.getQuery() | Returns the current root query object. Use only in event handlers. |
props.schema.dispatchQuery(query) | Updates the internal query state and calls the onQueryChange callback. Use only in event handlers. |
useQueryBuilderQuery() | React Hook that returns the current root query object. |
useQueryBuilderSelector(selector) | Redux selector Hook for the internal store. |
Notes:
- Prefer
useQueryBuilderQueryoveruseQueryBuilderSelector - These use a custom Redux context internally
- Enable Redux DevTools by importing from
"react-querybuilder/debug" - The old
contextprop workaround is no longer needed
New insert utility
The new insert utility inserts rules or groups at any position in the query hierarchy, unlike add which only appends. Use replace: true to replace existing items.
Separate layout stylesheet
Layout styles are now available separately as query-builder-layout.css/.scss. The main stylesheet includes both layout and decorative styles. Overall styling remains unchanged from version 6.
Enhanced callback metadata
Most get* callbacks now receive a "meta" parameter containing fieldData - the complete Field object with custom properties. This eliminates the need to search for fields manually.
Affected callbacks: getDefaultOperator, getDefaultValue, getInputType, getOperators, getRuleClassname, getValueEditorSeparator, getValueEditorType, getValues, getValueSources
Enhanced formatQuery rule processors
Custom rule processors for formatQuery now receive the full Field object when a fields array is provided. TypeScript now requires label: string on field objects (previously only name was required).
Native PostgreSQL support
formatQuery now natively supports PostgreSQL with numberedParams and paramPrefix: "$". No more manual post-processing of "?" placeholders.
Built-in shift actions
The new showShiftActions prop adds up/down buttons for reordering rules without drag-and-drop. Hold Alt/⌥ Option while clicking to clone instead of move.
Related additions:
ShiftActionscomponent for rendering shift buttonsuseShiftActionshook withshiftUp/shiftDownmethods- New translation properties:
shiftActionUpandshiftActionDown
Accessibility improvements
Accessibility is improved with title attributes on rule group containers. Customize the text using the accessibleDescriptionGenerator prop.
Enhanced drag-and-drop control
Drag-and-drop components now accept a canDrop callback to control where items can be dropped. The callback receives dragging and hovering objects with path information.
Improved number parsing
The parseNumber function now uses numeric-quantity for enhanced parsing. The new "enhanced" option ignores trailing invalid characters like "abc" in "123abc".
New SpEL parser
New parseSpEL function converts SpEL expressions to query objects.
Updated default labels
Main package
| Key | Old | New | Notes |
|---|---|---|---|
translations.addRule.label | "+Rule" | "+ Rule" | Space added between "+" and "Rule" |
translations.addGroup.label | "+Group" | "+ Group" | Space added between "+" and "Group" |
translations.removeRule.label | "x" | "⨯" | HTML entity ✗ / ⨯ |
translations.removeGroup.label | "x" | "⨯" | HTML entity ✗ / ⨯ |
Compatibility packages
Compatibility packages now use ReactNode labels instead of just strings, displaying SVG icons from their design systems by default. Use translations={defaultTranslations} to revert to text labels.
Ant Design (@react-querybuilder/antd)
Icon package: @ant-design/icons
| Key | Icon |
|---|---|
translations.removeGroup.label | <CloseOutlined /> |
translations.removeRule.label | <CloseOutlined /> |
translations.cloneRule.label | <CopyOutlined /> |
translations.cloneRuleGroup.label | <CopyOutlined /> |
translations.lockGroup.label | <UnlockOutlined /> |
translations.lockRule.label | <UnlockOutlined /> |
translations.lockGroupDisabled.label | <LockOutlined /> |
translations.lockRuleDisabled.label | <LockOutlined /> |
translations.shiftActionUp.label | <UpOutlined /> |
translations.shiftActionDown.label | <DownOutlined /> |
Bootstrap (@react-querybuilder/bootstrap)
Icon package: bootstrap-icons
Note: The
BootstrapDragHandlecomponent has been removed. It is unnecessary now that the default drag handle component can accept aReactNodeas itslabel.
| Key | Icon |
|---|---|
translations.removeGroup.label | <i className="bi bi-x" /> |
translations.removeRule.label | <i className="bi bi-x" /> |
translations.cloneRule.label | <i className="bi bi-copy" /> |
translations.cloneRuleGroup.label | <i className="bi bi-copy" /> |
translations.dragHandle.label | <i className="bi bi-grip-vertical" /> |
translations.lockGroup.label | <i className="bi bi-unlock" /> |
translations.lockRule.label | <i className="bi bi-unlock" /> |
translations.lockGroupDisabled.label | <i className="bi bi-lock" /> |
translations.lockRuleDisabled.label | <i className="bi bi-lock" /> |
translations.shiftActionUp.label | <i className="bi bi-chevron-compact-up" /> |
translations.shiftActionDown.label | <i className="bi bi-chevron-compact-down" /> |
Chakra UI (@react-querybuilder/chakra)
Chakra UI published its own icon package for version 2 (@chakra-ui/icons), but recommends react-icons for version 3. @react-querybuilder/chakra version 8 has been updated accordingly, using icons from react-icons/fa by default.
| Key | Chakra UI v2 Icon ( @react-querybuilder/chakra@7,@react-querybuilder/chakra2@*) | Chakra UI v3 Icon ( @react-querybuilder/chakra@8) |
|---|---|---|
translations.dragHandle.label | <DragHandleIcon />(via ChakraDragHandle, not translations) | <FaGripVertical /> |
translations.removeGroup.label | <CloseIcon /> | <FaTimes /> |
translations.removeRule.label | <CloseIcon /> | <FaTimes /> |
translations.cloneRuleGroup.label | <CopyIcon /> | <FaCopy /> |
translations.cloneRule.label | <CopyIcon /> | <FaCopy /> |
translations.lockGroup.label | <UnlockIcon /> | <FaLockOpen /> |
translations.lockRule.label | <UnlockIcon /> | <FaLockOpen /> |
translations.lockGroupDisabled.label | <LockIcon /> | <FaLock /> |
translations.lockRuleDisabled.label | <LockIcon /> | <FaLock /> |
translations.shiftActionDown.label | <ChevronDownIcon /> | <FaChevronDown /> |
translations.shiftActionUp.label | <ChevronUpIcon /> | <FaChevronUp /> |
Fluent UI (@react-querybuilder/fluent)
Icon package: @fluentui/react-icons-mdl2
| Key | Icon |
|---|---|
translations.removeGroup.label | <CancelIcon /> |
translations.removeRule.label | <CancelIcon /> |
translations.cloneRule.label | <DuplicateRowIcon /> |
translations.cloneRuleGroup.label | <DuplicateRowIcon /> |
translations.dragHandle.label | <GripperDotsVerticalIcon /> |
translations.lockGroup.label | <UnlockIcon /> |
translations.lockRule.label | <UnlockIcon /> |
translations.lockGroupDisabled.label | <LockIcon /> |
translations.lockRuleDisabled.label | <LockIcon /> |
translations.shiftActionDown.label | <ChevronDownIcon /> |
translations.shiftActionUp.label | <ChevronUpIcon /> |
MUI/Material (@react-querybuilder/material)
Icon package: @mui/icons-material
| Key | Icon |
|---|---|
translations.removeGroup.label | <Close /> |
translations.removeRule.label | <Close /> |
translations.cloneRule.label | <ContentCopy /> |
translations.cloneRuleGroup.label | <ContentCopy /> |
translations.lockGroup.label | <LockOpen /> |
translations.lockRule.label | <LockOpen /> |
translations.lockGroupDisabled.label | <Lock /> |
translations.lockRuleDisabled.label | <Lock /> |
translations.shiftActionDown.label | <ShiftDown /> |
translations.shiftActionUp.label | <ShiftUp /> |