Recipes

Build a Prisma Filter UI with React Query Builder

Build a product filter and preview the Prisma where object before validating it on your server.

What this recipe builds

  • A product filter with list, number, and yes/no fields.
  • Filter rules that users can review before sending them.
  • A clear separation between browser filter state and server-side Prisma execution.
Loading the interactive recipe demo...

Install and imports

Install / imports
npm install @vojtechportes/react-query-builder

Fields and initial query

Typed field configuration
import type {
DenormalizedQuery,
IBuilderFieldProps,
} from '@vojtechportes/react-query-builder';
export const fields: IBuilderFieldProps[] = [
{
field: 'category',
label: 'Category',
type: 'LIST',
value: [
{ label: 'Books', value: 'BOOKS' },
{ label: 'Games', value: 'GAMES' },
],
},
{ field: 'price', label: 'Price', type: 'NUMBER' },
{ field: 'inStock', label: 'In stock', type: 'BOOLEAN' },
];
export const initialQuery: DenormalizedQuery = [
{
type: 'GROUP',
value: 'AND',
isNegated: false,
children: [{ field: 'inStock', operator: 'EQUAL', value: true }],
},
];

Builder implementation

React implementation
import '@vojtechportes/react-query-builder/styles.css';
import React from 'react';
import {
Builder,
type DenormalizedQuery,
type IBuilderFieldProps,
} from '@vojtechportes/react-query-builder';
import { formatQuery } from '@vojtechportes/react-query-builder/formatQuery';
const fields: IBuilderFieldProps[] = [
{
field: 'category',
label: 'Category',
type: 'LIST',
value: [
{ label: 'Books', value: 'BOOKS' },
{ label: 'Games', value: 'GAMES' },
],
},
{
field: 'price',
label: 'Price',
type: 'NUMBER',
operators: ['EQUAL', 'LARGER_EQUAL', 'SMALLER_EQUAL'],
},
{ field: 'inStock', label: 'In stock', type: 'BOOLEAN' },
];
const initialQuery: DenormalizedQuery = [
{
type: 'GROUP',
value: 'AND',
isNegated: false,
children: [{ field: 'inStock', operator: 'EQUAL', value: true }],
},
];
export const PrismaFilter = () => {
const [query, setQuery] = React.useState(initialQuery);
const submit = async () => {
const formatted = formatQuery(query, 'Prisma', {
fields,
wrapWhereClause: true,
});
const { where } = JSON.parse(formatted) as { where: unknown };
await fetch('/api/products/search', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ where }),
});
};
return (
<>
<Builder fields={fields} data={query} onChange={setQuery} />
<button onClick={submit}>Search products</button>
</>
);
};

Export the Prisma where input

Export the Prisma where input
const where = formatQuery(query, 'Prisma', {
fields,
wrapWhereClause: true,
});
Expected output
{ "where": { "AND": [{ "inStock": { "equals": true } }] } }

Validation and safety

  • Treat the browser-produced object as untrusted input.
  • On the server, accept only supported fields and operators, then validate value types and query depth.
  • On the server, add filters that restrict records to the current user or organization.

Production notes

  • Wait briefly after the user stops editing before updating expensive previews, and require a Submit action for expensive searches.
  • Keep Prisma Client and database credentials on the server.

Related guides

Frequently asked questions

Where should the Prisma query run?

On the server. Send the Builder data to your API, check it there, and then create and run the Prisma query.

Can users change a required filter, such as their organization ID?

You can lock the rule in the UI, but the server must add or verify required filters because browser data can be changed.

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