Getting Started

Locking and Read-only

Locking can be applied at the builder, rule, or group level. The key distinction is that rules lock only themselves, while groups can lock either just their own controls or their entire subtree. Object-based readOnly configs also support targeted read-only for specific controls.

GUI Locking

Set lockable on Builder to render lock controls directly in the UI. The built-in controls update the same readOnly fields that are already part of the query data model, so the resulting lock state is preserved in output. If a node already has readOnly.targets, the lock toggle preserves those targets and only changes whether the lock is enabled and, for groups, whether it inherits to descendants.

GUI locking
import '@vojtechportes/react-query-builder/styles.css';
<Builder
fields={fields}
data={data}
lockable
onChange={setData}
/>;
// Rules cycle through:
// unlocked -> locked
//
// Groups cycle through:
// unlocked -> locked group only -> locked group and descendants
//
// The emitted query stores those states in readOnly:
// rule: readOnly: true
// group: readOnly: true
// group + descendants: readOnly: { enabled: true, inheritToChildren: true }
//
// If a node already uses readOnly.targets, the lock toggle preserves those
// targets and only changes enabled/inheritToChildren.
  • Rules cycle through two states: unlocked and locked.
  • Groups cycle through three states: unlocked, locked group only, and locked group with descendants.
  • The default group cycle maps to false, true, and { enabled: true, inheritToChildren: true }.
  • When a parent group inherits a lock to descendants, child lock controls render disabled because descendants cannot override that inherited state.
  • When cloneable is also enabled, the clone button renders immediately to the left of the lock button.

Targeted read-only

Use object-based readOnly configs when you want to keep specific controls visible but non-editable instead of locking the entire rule or group.

Targeted read-only
const data: DenormalizedQuery = [
{
type: 'GROUP',
value: 'AND',
isNegated: false,
readOnly: {
enabled: true,
targets: ['combinator'],
},
children: [
{
field: 'CUSTOMER_COUNTRY',
operator: 'EQUAL',
value: 'CZ',
readOnly: {
enabled: true,
targets: ['field', 'operator'],
},
},
{
field: 'ORDER_TOTAL',
operator: 'BETWEEN',
value: [1000, 5000],
readOnly: {
enabled: true,
targets: ['value'],
},
},
],
},
];
  • Rule targets are field, operator, and value.
  • Group targets are combinator and negation.
  • If enabled is true and targets is omitted, the whole node is read-only.
  • If enabled is false, the config stays dormant until the node is locked again.

How each level behaves

  • <Builder readOnly /> locks the entire builder. No rules or groups remain editable.
  • rule.readOnly = true locks only that rule. Siblings and parent groups are unaffected.
  • rule.readOnly = { enabled: true, targets: ['field'] } keeps the field visible but non-editable, and also prevents deleting that rule.
  • group.readOnly = true locks only that group's own controls. Its child rules and child groups stay editable by default.
  • group.readOnly = { enabled: true, targets: ['combinator'] } keeps the group combinator visible but non-editable without forcing descendant rules to become read-only.
  • group.readOnly = { enabled: true, inheritToChildren: true } locks the group and all descendant rules and groups.
Locking examples
import '@vojtechportes/react-query-builder/styles.css';
const data: DenormalizedQuery = [
{
type: 'GROUP',
value: 'AND',
isNegated: false,
children: [
{
field: 'STATUS',
operator: 'EQUAL',
value: 'ACTIVE',
readOnly: {
enabled: true,
targets: ['field', 'operator'],
},
},
{
type: 'GROUP',
value: 'OR',
isNegated: false,
readOnly: {
enabled: true,
targets: ['combinator'],
},
children: [
{
field: 'COUNTRY',
operator: 'EQUAL',
value: 'CZ',
},
],
},
{
type: 'GROUP',
value: 'AND',
isNegated: false,
readOnly: {
enabled: true,
inheritToChildren: true,
},
children: [
{
field: 'IS_VAT_PAYER',
operator: 'EQUAL',
value: true,
},
],
},
],
},
];
<Builder fields={fields} data={data} onChange={setData} />;

Custom Lock Control

The default lock button can be replaced through components.LockToggle.

LockToggle override
import '@vojtechportes/react-query-builder/styles.css';
const components = {
LockToggle: MyLockToggle,
};
<Builder
fields={fields}
data={data}
lockable
components={components}
onChange={setData}
/>;
// LockToggle receives:
// state: 'unlocked' | 'self' | 'all'
// nodeType: 'rule' | 'group'
// disabled?: boolean
// onChange?: (nextState) => void

Custom Clone Control

The default clone button can be replaced through components.CloneButton.

CloneButton override
import '@vojtechportes/react-query-builder/styles.css';
const components = {
CloneButton: MyCloneButton,
};
<Builder
fields={fields}
data={data}
cloneable
components={components}
onChange={setData}
/>;
// CloneButton receives:
// nodeType: 'rule' | 'group'
// disabled?: boolean
// onClick?: () => void

What "locked" means in the UI

  • Read-only targets stay visible. They become disabled, not hidden.
  • Locked rules cannot change field, operator, or value, and cannot be deleted.
  • Targeted rule read-only also blocks deleting that rule, even if only one target such as field is protected.
  • Locked groups cannot change their group operator, negation, add actions, or delete action.
  • By default, groups also cannot be deleted when that would remove protected descendants indirectly.
  • Set Builder.readOnlyProtectsDelete= to disable that subtree delete protection while keeping direct node-level read-only deletion rules.
  • Locked rules and groups are removed from drag-and-drop interactions.
  • Clone controls render only for editable rules and groups. Cloned nodes preserve their read-only configuration.
  • A locked group without inheritance still renders editable descendants when those descendants are not otherwise locked.

Inheritance and precedence

  • Inheritance only applies to groups, never to rules.
  • inheritToChildren has an effect only when enabled is true.
  • Once a parent group inherits read-only to descendants, child nodes cannot opt back into editability with readOnly: false.
  • Descendants may still add their own local readOnly flags, but they cannot override an inherited lock from an ancestor.
  • Group target inheritance preserves only the group-level semantics of the inherited lock. It does not turn rule-specific controls such as field or value into inherited targets.
API reference

Related recipes

© Vojtěch Václav Porteš 2026 - All library contents are available under the MIT license.
Loading privacy preferences...