Back to Playground

Query Builder

Filters that read the schema — and run live.

Quick chips for simple cases, nested AND/OR canvas for advanced. Every edit is evaluated instantly against 300 seeded faker records — the same shapes as the DataTable demo.

Mode
Chips + Tree
Operators
Per type
Dataset
300 faker
Results
Live
Demo schema

Quick filters

Active
Department
Email
Joined
Name
Role
Salary
Status

No quick filters active — chips turn forest when filled.

Live results

All 300 seeded records — add a filter to narrow

100% match
EmployeeRoleDepartmentStatusSalary

Ross Abbott

kirk78@gmail.com

ManagerSupportInactive$46,540

Felicia Brekke

miriam.crooks33@yahoo.com

AnalystDesignInactive$146,219

Horacio Braun

wallace_casper@yahoo.com

ManagerSupportActive$208,020

Alia Jerde PhD

ole_volkman90@gmail.com

DesignerDesignInactive$59,071

Peggy Schroeder

sydnie_tromp@hotmail.com

DeveloperSupportInactive$116,298

Justyn Larson

nicolas12@yahoo.com

ManagerSupportActive$88,365

Showing 6 of 300 matches · evaluated live on every edit · payload below is what you’d send as queryParams.filters

Filter payload

no filters
{
  "logic": "AND",
  "conditions": [],
  "search": ""
}
Wire fetchData to a real API and this exact payload goes out with the request — the DataTable demo already speaks this contract.

Quick filters: chips turn forest when filled, X clears. Good for 1-3 filters.

Advanced: AND/OR rail per group, add condition or inner group (max depth 2). Field change resets the operator.

Live engine: every operator from the schema runs client-side against seeded rows — deterministic between server and browser.

Usage

TSX
import { z } from 'zod'
import { QueryBuilder } from '@/components/queryBuilder/QueryBuilder'

const schema = z.object({
  name: z.string().describe(JSON.stringify({ label: 'Name' })),
  status: z.enum(['Active','Inactive']).describe(JSON.stringify({
    label: 'Status', inputType: 'select',
    options: [{ label: 'Active', value: 'Active' }, { label: 'Inactive', value: 'Inactive' }]
  })),
  salary: z.coerce.number().describe(JSON.stringify({ label: 'Salary' })),
  joinedAt: z.string().describe(JSON.stringify({ label: 'Joined', inputType: 'date' })),
})

export default function FilterPanel() {
  return (
    <QueryBuilder
      schema={schema}
      onQueryChange={(filters) => console.log(filters)}
      defaultOpen
      ui={{ showModeToggle: true, persistMode: false }}
    />
  )
}

What is a schema-driven QueryBuilder?

A schema-driven QueryBuilder is a visual filter composer whose fields, input types, and operators are derived from a Zod schema instead of being hand-written per screen. The schema is the single source of truth: add a field to the schema and the builder picks it up with the right input control and operator set — no filter UI code to maintain.

This playground is a working demonstration of that pattern. Both demo schemas (employee and product) are plain Zod objects with field metadata; the builder turns them into typed filter fields, and a small evaluation engine runs the resulting query against seeded faker records in real time.

How it works

zodToFilterFields maps each schema key to a FilterField — type, label, and allowed operators. Quick mode renders one chip per field with a fixed operator; advanced mode renders a tree of conditions and nested AND/OR groups (max depth 2). Every edit emits a JSON payload shaped like { logic, conditions, search } — the same contract the DataTable demo accepts as queryParams.filters.

The payload feeds a pure evaluation engine (evaluateFilters.ts) that implements every operator — text contains, number comparisons, boolean flags, multiselect includes, date before/after/between — and recurses through groups. The 300 demo rows are generated with per-row faker seeds, so server and browser evaluate against identical data.

Features

  • Zod schema mapped to typed filter fields
  • Quick chip filters and advanced AND/OR groups
  • Type-aware operators per field
  • Live filtering over 300 seeded faker records
  • Live results table with match percentage
  • JSON payload preview matching the DataTable contract

When to use a component like this

A QueryBuilder earns its place when users need to compose their own filters — admin panels, ERP list screens, CRM segments, support queues, audit logs. Deriving the builder from a schema keeps it in sync with the API contract: the JSON it produces is the JSON your backend already speaks, and new fields appear in the UI the moment they are added to the schema.

Built with React, TypeScript and Zod. Related demos: the React DataTable playground, which consumes this filter payload, and the SchemaForm builder, which uses the same schema-driven pattern for data entry.