Skip to main content
Version: Next

Managing operators

Many React Query Builder implementations need to customize operators based on the selected field type. For example, date fields might use "before" as a more intuitive label than the default "<" operator. Number fields could display "less than" for the < operator, while text fields might exclude comparison operators entirely.

Field operators property

One approach uses the field's operators property to specify which operators appear when users select a particular field. However, this method requires defining complete operator lists for each field, potentially creating duplication across fields with similar data types.

import { defaultOperators, Field, QueryBuilder, RuleGroupType } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const fields: Field[] = [
  {
    name: 'name',
    label: 'Name',
    operators: [
      { name: '=', label: 'is' },
      ...defaultOperators.filter(op => ['contains', 'beginsWith', 'endsWith'].includes(op.name)),
    ],
  },
  {
    name: 'birthday',
    label: 'Birthday',
    inputType: 'date',
    operators: [
      { name: '=', label: 'on' },
      { name: '<', label: 'before' },
      { name: '>', label: 'after' },
    ],
  },
  {
    name: 'guitars',
    label: 'Guitars',
    inputType: 'number',
    operators: [
      { name: '=', label: 'equals' },
      { name: '<', label: 'less than' },
      { name: '>', label: 'greater than' },
    ],
  },
  {
    name: 'favoriteMovie',
    label: 'Favorite Movie',
    operators: [{ name: '=', label: 'is' }],
  },
];

const defaultQuery: RuleGroupType = {
  combinator: 'and',
  rules: [
    { field: 'name', operator: 'beginsWith', value: 'Stev' },
    { field: 'birthday', operator: '<', value: '1970-01-01' },
    { field: 'guitars', operator: '>', value: 5 },
    { field: 'favoriteMovie', operator: '=', value: 'Crossroads (1986)' },
  ],
};

export default () => <QueryBuilder fields={fields} defaultQuery={defaultQuery} />;

getOperators prop

While the field operators property works well for individual field customizations, the getOperators function prop centralizes all operator logic in one location.

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.

This 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".

import { defaultOperators, Field, QueryBuilder, RuleGroupType } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const fields: Field[] = [
  { name: 'name', label: 'Name', datatype: 'text' },
  { name: 'birthday', label: 'Birthday', datatype: 'date', inputType: 'date' },
  { name: 'guitars', label: 'Guitars', datatype: 'number', inputType: 'number' },
  {
    name: 'favoriteMovie',
    label: 'Favorite Movie',
    datatype: 'text',
    operators: [{ name: '=', label: 'is' }],
  },
];

const getOperators = (fieldName: string, { fieldData }: { fieldData: Field }) => {
  switch (fieldData.datatype) {
    case 'text':
      return [
        { name: '=', label: 'is' },
        { name: '!=', label: 'is not' },
        ...defaultOperators.filter(op =>
          [
            'contains',
            'beginsWith',
            'endsWith',
            'doesNotContain',
            'doesNotBeginWith',
            'doesNotEndWith',
            'null',
            'notNull',
            'in',
            'notIn',
          ].includes(op.name)
        ),
      ];
    case 'number':
      return [
        ...defaultOperators.filter(op => ['=', '!='].includes(op.name)),
        { name: '<', label: 'less than' },
        { name: '<=', label: 'less than or equal to' },
        { name: '>', label: 'greater than' },
        { name: '>=', label: 'greater than or equal to' },
        ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),
      ];
    case 'date':
      return [
        { name: '=', label: 'on' },
        { name: '!=', label: 'not on' },
        { name: '<', label: 'before' },
        { name: '<=', label: 'on or before' },
        { name: '>', label: 'after' },
        { name: '>=', label: 'on or after' },
        ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),
      ];
  }
  return defaultOperators;
};

const defaultQuery: RuleGroupType = {
  combinator: 'and',
  rules: [
    { field: 'name', operator: 'beginsWith', value: 'Stev' },
    { field: 'birthday', operator: '<', value: '1970-01-01' },
    { field: 'guitars', operator: '>', value: 5 },
    { field: 'favoriteMovie', operator: '=', value: 'Crossroads (1986)' },
  ],
};

export default () => (
  <QueryBuilder fields={fields} defaultQuery={defaultQuery} getOperators={getOperators} />
);