QueryBuilder
A visual rule builder with AND/OR combinators that exports to SQL, JSON, or parameterized queries. Wraps react-querybuilder with mizu styling.
Installation
pnpm add @aspect/query-builder react-querybuilder
Import
import { MizuQueryBuilder, formatQuery } from '@aspect/query-builder';
import type { RuleGroupType } from '@aspect/query-builder';
Basic usage
const fields = [
{ name: 'status', label: 'Status' },
{ name: 'region', label: 'Region' },
{ name: 'deploys', label: 'Deploys', inputType: 'number' },
];
function QueryDemo() {
const [query, setQuery] = React.useState<RuleGroupType>({
combinator: 'and',
rules: [{ field: 'status', operator: '=', value: 'running' }],
});
return (
<>
<MizuQueryBuilder fields={fields} query={query} onQueryChange={setQuery} />
<pre>{formatQuery(query, 'sql')}</pre>
</>
);
}
Field configuration
Fields control what appears in the field selector and how the value editor renders.
const fields = [
// Text input (default)
{ name: 'name', label: 'Name' },
// Number input
{ name: 'deploys', label: 'Deploys', inputType: 'number' },
// Date input
{ name: 'createdAt', label: 'Created', inputType: 'date' },
// Dropdown select
{
name: 'status',
label: 'Status',
valueEditorType: 'select',
values: [
{ name: 'running', label: 'Running' },
{ name: 'idle', label: 'Idle' },
],
},
// Checkbox
{
name: 'active',
label: 'Active',
valueEditorType: 'checkbox',
defaultValue: true,
},
];
Output formats
formatQuery serializes the query tree into different representations:
// SQL WHERE clause (for display only — not injection-safe)
formatQuery(query, 'sql');
// → "status = 'running' AND deploys > 10"
// Parameterized (safe for prepared statements)
formatQuery(query, 'parameterized');
// → { sql: "status = ? AND deploys > ?", params: ["running", 10] }
// JSON
formatQuery(query, 'json');
// JSON without IDs (clean, for API transport)
formatQuery(query, 'json_without_ids');
Use "parameterized" or "parameterized_named" when sending queries to a backend. The "sql" format produces a raw string — only use it for display.
Custom operators
import { MizuQueryBuilder } from '@aspect/query-builder';
const getOperators = (field: string) => {
if (field === 'deploys') {
return [
{ name: '=', label: 'equals' },
{ name: '>', label: 'greater than' },
{ name: '<', label: 'less than' },
];
}
return [
{ name: '=', label: 'equals' },
{ name: '!=', label: 'not equals' },
{ name: 'contains', label: 'contains' },
];
};
<MizuQueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
getOperators={getOperators}
/>;
Custom value editor
Replace the default value editor to use custom widgets per field type:
import { ValueEditor as DefaultValueEditor, ValueEditorProps } from 'react-querybuilder';
const CustomValueEditor = (props: ValueEditorProps) => {
if (props.operator === 'null' || props.operator === 'notNull') {
return null; // no value needed
}
if (props.fieldData.datatype === 'date') {
return <MyDatePicker {...props} />;
}
return <DefaultValueEditor {...props} />;
};
<MizuQueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
controlElements={{ valueEditor: CustomValueEditor }}
/>;
Removing features
Hide the "Add Group" button to prevent nested rule groups:
<MizuQueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
controlElements={{ addGroupAction: () => null }}
/>
Replace the combinator selector with radio buttons or remove it entirely:
controlElements={{
combinatorSelector: () => null, // hide it
addGroupAction: () => null, // no nesting
}}
Internationalization
<MizuQueryBuilder
fields={fields}
query={query}
onQueryChange={setQuery}
translations={{
addRule: { label: 'Agregar regla' },
addGroup: { label: 'Agregar grupo' },
removeRule: { label: 'Eliminar' },
removeGroup: { label: 'Eliminar grupo' },
}}
combinators={[
{ name: 'and', label: 'Y' },
{ name: 'or', label: 'O' },
]}
/>
Bulk editing
The query builder can also be used for bulk edit operations — defining "set to", "increase by", or "decrease by" rules instead of filter conditions:
const getOperatorsForUpdate = (field: string) => {
if (['deploys', 'unit_price'].includes(field)) {
return [
{ name: '=', label: 'set to' },
{ name: '+', label: 'increase by' },
{ name: '-', label: 'decrease by' },
];
}
return [{ name: '=', label: 'set to' }];
};
<MizuQueryBuilder
fields={fields}
query={updateQuery}
onQueryChange={setUpdateQuery}
getOperators={getOperatorsForUpdate}
controlElements={{
addGroupAction: () => null,
combinatorSelector: () => null,
}}
/>;
Props
MizuQueryBuilder accepts all props from react-querybuilder's QueryBuilder component plus:
| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ----------------------------------- |
| className | string | — | Additional CSS class on the wrapper |
All other props (fields, query, onQueryChange, controlElements, getOperators, translations, combinators, etc.) are passed through to react-querybuilder.
CSS class
The wrapper applies .mizu-query-builder which styles all child elements (selects, inputs, buttons, rule groups) using mizu design tokens.
<div class="mizu-query-builder">
<!-- react-querybuilder renders here -->
</div>