query-kit-encoder
query-kit-encoder is the frontend-side companion package for
query-kit-core.
It helps frontend developers build filter query strings, payload objects, and
URL query parameters that are intentionally shaped for query-kit-core.
This package does not parse or execute queries. It only constructs request input for the backend package.
Table of contents
- What this package does
- When to install it
- Installation
- How it relates to query-kit-core
- Exports
- Quick start
- SC builder
- MC builder
- Output modes
- Payload shape
- URLSearchParams output
- Sorting and pagination
- Relations and includepopulate
- Aggregation
- SC logical expressions
- SC case expressions
- Value formatting rules
- Error cases and limitations
- Recommended frontend usage pattern
What this package does
query-kit-encoder builds request input for APIs that use query-kit-core.
It supports:
SCquery string generationMCquery string generation- payload objects compatible with
query-kit-core URLSearchParamsoutput for HTTP requests- sort, page, limit, offset, fields
- include / populate directives
- aggregation, groupBy, and having
SClogical groupsSCcase expressions
It is designed for:
ReactNext.jsVueNuxtAngular- plain TypeScript frontend apps
When to install it
Install query-kit-encoder when:
- your frontend needs to build filter query strings
- your frontend needs to build payload objects for a backend endpoint
- your backend already uses
query-kit-core - you want one consistent query contract between frontend and backend
Do not install this package just to parse or validate filters on the
backend. That is the job of query-kit-core.
Installation
pnpm
pnpm add query-kit-encoder
npm
npm install query-kit-encoder
This package has no ORM dependency, because it does not execute queries. It only builds strings and payloads.
How it relates to query-kit-core
The two packages are complementary but independent:
query-kit-encoder- frontend/client package
- builds query strings and payloads
query-kit-core- backend/runtime package
- parses, validates, normalizes, and converts those queries
Typical architecture:
- frontend app → install
query-kit-encoder - backend app → install
query-kit-core
Exports
Main exports:
createSCQueryBuilder()createMCQueryBuilder()SCQueryBuilderMCQueryBuilder- shared types from
types.ts
Example:
import {
createSCQueryBuilder,
createMCQueryBuilder,
} from 'query-kit-encoder';
Quick start
SC quick start
import { createSCQueryBuilder } from 'query-kit-encoder';
const payload = createSCQueryBuilder()
.where('product.status', 'eq', 'active')
.where('product.price', 'between', [100, 500])
.sortDesc('product.createdAt')
.limit(20)
.page(2)
.fields('id', 'name', 'price', 'status')
.include('category')
.buildPayload();
Result:
{
filterString: 'product.status:eq:active;product.price:between:100,500',
sortString: 'product.createdAt:desc',
page: 2,
size: 20,
offset: undefined,
fields: ['id', 'name', 'price', 'status'],
relations: ['category'],
customInclude: ['category']
}
MC quick start
import { createMCQueryBuilder } from 'query-kit-encoder';
const query = createMCQueryBuilder()
.where('product.status', 'eq', 'active')
.where('product.tags', 'in', ['new', 'hot'])
.sortDesc('product.createdAt')
.populate('category')
.build();
Result:
product.status:$eq:active;product.tags:$in:new,hot;@sort:-product.createdAt;@populate:category
SC builder
SC builds SQL-style filter strings compatible with the scfilter format in
query-kit-core.
Available methods
where(field, operator, value)orWhere(field, operator, value)andGroup(callback)orGroup(callback)not(callback)sortBy(field, direction)sortAsc(field)sortDesc(field)limit(value)page(value)offset(value)fields(...fields)include(...relations)relations(relations)aggregate(fn, field, alias)groupBy(...fields)having(field, operator, value)case(outputField, callback)build()buildPayload()toURLSearchParams()
Basic example
const query = createSCQueryBuilder()
.where('status', 'eq', 'active')
.where('price', 'gte', 100)
.where('price', 'lte', 500)
.build();
Output:
status:eq:active;price:gte:100;price:lte:500
Supported SC operators
eqneqgtgteltltebetweenlikeiLikenotLikecontainsstartsWithendsWithregexinnotInanyallsizeisNullisNotNullexistsnotExistsdateyearmonthday
Examples:
createSCQueryBuilder().where('status', 'eq', 'active').build();
status:eq:active
createSCQueryBuilder().where('price', 'between', [100, 200]).build();
price:between:100,200
createSCQueryBuilder().where('tags', 'in', ['new', 'hot']).build();
tags:in:new,hot
MC builder
MC builds Mongo-style filter strings compatible with the mcfilter format in
query-kit-core.
Available methods
where(field, operator, value)sortBy(field, direction)sortAsc(field)sortDesc(field)limit(value)page(value)offset(value)fields(...fields)include(...relations)populate(...relations)relations(relations, keyword?)aggregate(fn, field, alias)groupBy(...fields)having(field, operator, value)build()buildPayload()toURLSearchParams()
Basic example
const query = createMCQueryBuilder()
.where('status', 'eq', 'active')
.where('age', 'gte', 18)
.build();
Output:
status:$eq:active;age:$gte:18
Supported MC operators
eqor$eqneqor$neqgtor$gtgteor$gteltor$ltlteor$lteinor$innotInor$notInallor$allregexor$regexexistsor$existssizeor$sizeelemMatchor$elemMatch
Examples:
createMCQueryBuilder().where('status', 'eq', 'active').build();
status:$eq:active
createMCQueryBuilder().where('tags', '$in', ['new', 'hot']).build();
tags:$in:new,hot
createMCQueryBuilder()
.where('meta', 'elemMatch', { published: true })
.build();
meta:$elemMatch:{"published":true}
Output modes
Both builders support three main output modes.
build()
Returns one inline query string with directives embedded in filterString.
Example:
const query = createSCQueryBuilder()
.where('status', 'eq', 'active')
.sortDesc('createdAt')
.limit(10)
.build();
Output:
status:eq:active;@sort:-createdAt;@limit:10
buildPayload()
Returns a payload object compatible with query-kit-core.
By default:
- sort is external
- pagination is external
- fields are external
- relations are external
This is usually the cleanest format for frontend state and API request bodies.
toURLSearchParams()
Returns URLSearchParams for use in browser requests.
Example:
const params = createMCQueryBuilder()
.where('status', 'eq', 'active')
.sortDesc('createdAt')
.limit(10)
.toURLSearchParams();
Typical output:
filter=status%3A%24eq%3Aactive&sort=-createdAt&size=10
Payload shape
buildPayload() returns:
type QueryPayload = {
filterString: string;
sortString?: string;
page?: number;
size?: number;
offset?: number;
fields?: string[];
relations?: RelationDirective;
customInclude?: RelationDirective;
};
This shape is intentionally aligned with query-kit-core.
URLSearchParams output
toURLSearchParams() accepts two option objects:
1) payload options
Controls whether sort/pagination/fields/relations are inlined into
filterString or kept external.
type PayloadBuildOptions = {
inlineSort?: boolean;
inlinePagination?: boolean;
inlineFields?: boolean;
inlineRelations?: boolean;
};
2) URL param key options
type URLSearchParamsOptions = {
filterKey?: string;
sortKey?: string;
pageKey?: string;
sizeKey?: string;
offsetKey?: string;
fieldsKey?: string;
includeKey?: string;
relationSerializer?: (relations: RelationDirective) => string;
};
Example with custom keys:
const params = createSCQueryBuilder()
.where('status', 'eq', 'active')
.toURLSearchParams({}, {
filterKey: 'q',
sortKey: 'orderBy',
sizeKey: 'limit',
});
Sorting and pagination
Sorting
Methods:
sortBy(field, 'asc' | 'desc')sortAsc(field)sortDesc(field)
SC external sort
createSCQueryBuilder()
.where('status', 'eq', 'active')
.sortDesc('createdAt')
.sortAsc('name')
.buildPayload();
Output:
{
sortString: 'createdAt:desc;name:asc'
}
SC inline sort
createSCQueryBuilder()
.where('status', 'eq', 'active')
.sortDesc('createdAt')
.build();
Output:
status:eq:active;@sort:-createdAt
MC sort
MC uses the same comma style for inline and external sort:
-createdAt,name
Pagination
Methods:
limit(number)page(number)offset(number)
Example:
createMCQueryBuilder()
.where('status', 'eq', 'active')
.limit(20)
.page(2)
.offset(40)
.build();
Output:
status:$eq:active;@limit:20;@page:2;@offset:40
Relations and include/populate
Simple string relations
SC
createSCQueryBuilder()
.where('status', 'eq', 'active')
.include('profile', 'orders.items')
.build();
Output:
status:eq:active;@include:profile,orders.items
MC
createMCQueryBuilder()
.where('status', 'eq', 'active')
.populate('profile', 'orders.items')
.build();
Output:
status:$eq:active;@populate:profile,orders.items
Complex relation directives
For payload mode, you can pass nested relation objects:
const payload = createSCQueryBuilder()
.where('status', 'eq', 'active')
.relations([
{
path: 'profile',
fields: ['id', 'avatar'],
},
{
path: 'orders',
nested: [
{
path: 'items',
fields: ['id', 'sku'],
},
],
},
])
.buildPayload();
Important:
- complex relation objects are supported in
buildPayload() - inline relation directives only support simple string relation paths
toURLSearchParams()needsrelationSerializerfor complex relation objects
Aggregation
Both builders support:
aggregate(fn, field, alias)groupBy(...fields)having(field, operator, value)
Supported aggregate functions:
countsumavgminmax
Example:
const query = createSCQueryBuilder()
.where('status', 'eq', 'active')
.aggregate('sum', 'amount', 'totalAmount')
.groupBy('status')
.having('totalAmount', 'gte', 100)
.build();
Output:
status:eq:active;@aggregate:sum(amount):totalAmount;@groupBy:status;@having:totalAmount:gte:100
MC example:
const query = createMCQueryBuilder()
.where('status', 'eq', 'active')
.aggregate('count', '*', 'total')
.groupBy('status')
.having('total', 'gte', 1)
.build();
Output:
status:$eq:active;@aggregate:count(*):total;@groupBy:status;@having:total:$gte:1
SC logical expressions
SC supports logical expression building.
Available methods:
where(...)orWhere(...)andGroup(callback)orGroup(callback)not(callback)
Example:
const query = createSCQueryBuilder()
.where('status', 'eq', 'active')
.orGroup((group) => {
group.where('price', 'gte', 100).where('price', 'lte', 500);
})
.build();
Output:
status:eq:active|(price:gte:100;price:lte:500)
Negation example:
const query = createSCQueryBuilder()
.not((group) => {
group.where('deletedAt', 'exists', true);
})
.build();
Output:
!(deletedAt:exists:true)
SC case expressions
SC supports case-expression generation through .case(...).
Example:
const query = createSCQueryBuilder()
.case('priority', (expr) => {
expr
.when('amount', 'gte', 1000, 'high')
.when('amount', 'lt', 1000, 'low')
.else('unknown');
})
.build();
Output:
case:priority;when:amount:gte:1000:then:high;when:amount:lt:1000:then:low;else:unknown
Value formatting rules
Primitive values
- strings are escaped for
:,,,;, and\ - numbers become numeric text
- booleans become
true/false nullbecomesnullDatebecomes ISO string
List operators
These operators expect arrays:
SC:between,in,notIn,any,allMC:in,notIn,all
elemMatch
MC elemMatch requires an object and is serialized as JSON.
Error cases and limitations
SC limitations
- empty logical groups throw an error
betweenrequires exactly two values- inline relation directives only support string paths
MC limitations
elemMatchmust receive an object- array operators require arrays
- inline relation directives only support string paths
URLSearchParams limitation
If relations contains nested relation objects, you must provide a custom
relationSerializer:
const params = builder.toURLSearchParams({}, {
relationSerializer: (relations) => JSON.stringify(relations),
});
Recommended frontend usage pattern
For most applications:
- keep builder state in UI filters
- map UI filters into builder calls
- use
buildPayload()for structured API requests - use
toURLSearchParams()for GET endpoints - use
build()only when you intentionally want one inline filter string
Recommended split:
- frontend →
query-kit-encoder - backend →
query-kit-core
This keeps request generation on the client and parsing/execution on the server.