DO NOT EXPECT MUCH FROM THIS THIS IS CLAUDE-WRITTEN AS AN EXPERIMENT.
A Svelte 5 port of react-querybuilder, powered by shadcn-svelte and bits-ui. Builds visual rule trees and exports them to 23 formats (SQL, parameterized SQL, MongoDB, JSON Logic, CEL, SpEL, JSONata, Elasticsearch, Cypher, GraphQL, SPARQL, Gremlin, LDAP, Drizzle, Prisma, Sequelize, natural language, and more) by reusing @react-querybuilder/core directly — so format and parser behavior is byte-identical to the React library.
<script>
import { QueryBuilder, formatQuery } from 'svelte-query-builder';
const fields = [
{ name: 'firstName', label: 'First name' },
{ name: 'age', label: 'Age', inputType: 'number' },
];
let query = $state({ combinator: 'and', rules: [] });
</script>
<QueryBuilder bind:query {fields} />
<pre>{formatQuery(query, 'sql')}</pre>
pnpm add svelte-query-builder
@react-querybuilder/core is bundled as a runtime dependency.
query+onQueryChange or bind:query.formatQuery (sql, parameterized, parameterized_named, mongodb, jsonlogic, cel, spel, jsonata, elasticsearch, cypher, sparql, gremlin, ldap, drizzle, prisma, sequelize, natural_language, json, …).independentCombinators={true} for the per-rule combinator layout.disabled, muted flags propagate down the tree; toggle UI via showLockButtons / showMuteButtons.validator; per-rule and per-group valid/invalid classnames are applied.valueSources on a field.all / some / none / atLeast / atMost / exactly with threshold).showShiftActions.enableDragAndDrop (or use <QueryBuilderDnD> from svelte-query-builder/dnd).controls={{ valueEditor: MyEditor, … }}.header, footer, ruleGroupHeader, ruleGroupBody for layout customization.controlClassnames for per-element class overrides, or use the data-rqb-element="…" data attributes for pure-CSS theming.createAsyncOptionList() helper with AbortController-based cancellation.import { formatQuery } from 'svelte-query-builder';
$effect(async () => {
const { sql, params } = formatQuery(query, 'parameterized');
const stmt = await connection.prepare(`SELECT * FROM users WHERE ${sql}`);
results = await stmt.query(...params);
});
<script>
import { QueryBuilder } from 'svelte-query-builder';
import MyDateRangeEditor from './MyDateRangeEditor.svelte';
</script>
<QueryBuilder bind:query {fields} controls={{ valueEditor: MyDateRangeEditor }} />
<QueryBuilder bind:query {fields}>
{#snippet header({ schema })}
<div class="my-toolbar">Level {schema.qbId}</div>
{/snippet}
{#snippet footer({ query })}
<pre>{JSON.stringify(query, null, 2)}</pre>
{/snippet}
</QueryBuilder>
<script>
import { QueryBuilder } from 'svelte-query-builder';
</script>
<QueryBuilder bind:query {fields} enableDragAndDrop />
Or import the wrapper:
<script>
import { QueryBuilderDnD } from 'svelte-query-builder/dnd';
</script>
<QueryBuilderDnD bind:query {fields} />
<script>
import { QueryBuilder } from 'svelte-query-builder';
function validator(q) {
const map = {};
function walk(node) {
if ('rules' in node) node.rules.forEach((r) => typeof r !== 'string' && walk(r));
else if (node.value === '' || node.value == null)
map[node.id] = { valid: false, reasons: ['empty'] };
}
walk(q);
return map;
}
</script>
<QueryBuilder bind:query {fields} {validator} />
import { parseSQL } from 'svelte-query-builder/parseSQL';
import { parseMongoDB } from 'svelte-query-builder/parseMongoDB';
const query = parseSQL(`firstName like 'Ste%' and age > 28`, { fields });
If you don't want the default shadcn-svelte UI, import from /headless and supply your own controls:
import { createQueryBuilderState } from 'svelte-query-builder/headless';
QueryBuilder — the main componentQueryBuilderDnD — drag-and-drop variant (svelte-query-builder/dnd)Rule, RuleGroup, ValueSelector, ValueEditor, ActionElement, NotToggle, ShiftActions, DragHandle, MatchModeEditor, InlineCombinator) are exported and can be wrapped or extended.createQueryBuilderState({ getQuery, defaultQuery, onQueryChange, … }) — runes-based state factory used internally by <QueryBuilder>. Use it directly to build your own root component.getQueryBuilderContext() / setQueryBuilderContext().createAsyncOptionList({ fetcher }) — reactive async options helper with cancellation.import { formatQuery, transformQuery } from 'svelte-query-builder';
// Or via subpaths:
import { formatQuery } from 'svelte-query-builder/formatQuery';
import { parseSQL } from 'svelte-query-builder/parseSQL';
import { parseMongoDB } from 'svelte-query-builder/parseMongoDB';
import { parseCEL } from 'svelte-query-builder/parseCEL';
import { parseSpEL } from 'svelte-query-builder/parseSpEL';
import { parseJSONata } from 'svelte-query-builder/parseJSONata';
import { parseJsonLogic } from 'svelte-query-builder/parseJsonLogic';
import { transformQuery } from 'svelte-query-builder/transformQuery';
All types from @react-querybuilder/core are re-exported:
import type {
Field,
Operator,
Combinator,
RuleType,
RuleGroupType,
RuleGroupTypeIC,
RuleGroupTypeAny,
Path,
Classnames,
Translations,
ValidationMap,
QueryValidator,
} from 'svelte-query-builder';
The default components are styled with tailwindcss v4 using OKLch CSS variables. Themes ship as CSS variables on :root and .dark and use the same --background, --foreground, --primary, --border, etc. tokens as shadcn-svelte.
For framework-agnostic styling, every node carries a data-rqb-element="…" attribute:
[data-rqb-element='rule'] { padding: 0.5rem; }
[data-rqb-element='rule-group'] { border-radius: 0.5rem; }
Per-element class overrides go via controlClassnames:
<QueryBuilder
bind:query
{fields}
controlClassnames={{
queryBuilder: 'my-qb',
rule: 'flex items-center gap-2 p-2',
ruleGroup: 'border-l-2 pl-2',
}}
/>
This library wraps @react-querybuilder/core (zero React deps — pure TypeScript with immer, numeric-quantity, @ts-jison/lexer, @ts-jison/parser) and provides a Svelte 5 component layer that:
$state-backed reactive state (one instance per QueryBuilder, scoped via context)$state.snapshot() before handing off to immer-backed mutators (since immer can't freeze Svelte's $state proxies)controls and controlClassnames extension points from react-querybuilder so existing patterns translatev0.0.1 — feature-complete port of all major react-querybuilder features. See the demo route in this repo for a live walkthrough.
| Feature | Status |
|---|---|
| 23 output formats | ✓ via @react-querybuilder/core |
| 6 parsers | ✓ via subpath imports |
| Independent combinators | ✓ |
| Lock / disable / mute | ✓ |
| Validation | ✓ |
| Value sources | ✓ |
| Match modes | ✓ |
| Between / multiselect editors | ✓ |
| Shift actions | ✓ |
| Async option lists | ✓ |
| Accessibility | ✓ |
| Custom controls + snippets | ✓ |
| Drag and drop | ✓ |
pnpm install
pnpm dev # start the demo
pnpm test # vitest unit tests
pnpm check # svelte-check
pnpm build # build the library
MIT. Built on @react-querybuilder/core (MIT) and svelte-dnd-action (MIT).