Filtering records
Use typed comparisons, grouped logic, lists, and multipicklist filters.
Basic conditions
Repeated where calls are joined with AND:
import { db } from "./db";
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where("Name", "like", "Acme%")
.where("AnnualRevenue", ">=", 100_000);Pass application values through the builder instead of constructing SOQL strings yourself. Kysoql validates and escapes ordinary API values when it compiles the query.
Do not pre-quote strings. Apex compile-only queries have separate typed bind helpers when you need Apex variables rather than SOQL literals.
Operators and values
| Operator | Accepted values |
|---|---|
=, != | Values compatible with the generated field. null is allowed only where the field supports it. |
<, <=, >, >= | Ordered field types with non-null values. |
like | Supported string-like fields and a string pattern. |
in, not in | Non-empty homogeneous lists or compatible ID/reference subqueries. |
includes, excludes | Non-empty generated active values for multipicklist fields. |
Operator names are lowercase in TypeScript. Compiled SOQL uses the normal uppercase Salesforce keywords.
For null checks, use where("Field", "=", null) or "!=". Do not pass the string "null".
Date and time fields use typed literal helpers. Location fields use distance expressions.
Group AND, OR, and NOT
The expression builder can be called as eb(field, operator, value) and also provides and, or, and not:
import { db } from "./db";
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where((eb) =>
eb.and([
eb.or([
eb("Name", "like", "Acme%"),
eb("Name", "like", "Example%"),
]),
eb.not(eb("Name", "like", "%Test%")),
]),
);Keep boolean groups non-empty. If a filter is optional, $if is usually clearer than trying to define an empty and([]) or or([]).
IN and NOT IN
import { db } from "./db";
const ids: readonly string[] = ["001000000000001AAA"];
if (ids.length === 0) {
throw new Error("Choose at least one account ID");
}
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where("Id", "in", ids);Empty lists are rejected. Decide what an empty application input means before building the query. It might mean "no matches", "invalid request", or "skip this optional filter".
Be especially careful with access-control filters. Silently dropping an empty authorization list can widen a query.
For related-record existence checks, use a semi-join or anti-join instead of fetching IDs into JavaScript first.
Multipicklists
This example assumes Account.Interests__c is a generated multipicklist with active values Email and Events:
import { soqlMultiSelectAnd } from "@kysoql/core";
import { db } from "./db";
const eitherValue = db
.selectFrom("Account")
.select(["Id", "Interests__c"])
.where("Interests__c", "includes", ["Email", "Events"]);
const bothValues = db
.selectFrom("Account")
.select(["Id", "Interests__c"])
.where(
"Interests__c",
"includes",
soqlMultiSelectAnd("Email", "Events"),
);A normal array uses Salesforce's OR form. soqlMultiSelectAnd(...) produces the AND form while staying typed against generated active picklist values.
Apex also has a separate bind-left INCLUDES form.
Treat LIKE patterns deliberately
Plain strings keep Salesforce LIKE behaviour, so % and _ remain wildcard characters:
import { db } from "./db";
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where("Name", "like", "Acme_%");When user input should be matched literally, use soql.likeLiteral():
import { soql } from "@kysoql/core";
import { db } from "./db";
const search = "50%_off";
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where("Name", "like", soql.likeLiteral(search));Kysoql validates identifiers and escapes values, but that is not authorization. If users can choose fields or filters, keep an application allowlist for what they are allowed to query. See security and production use.
Raw SOQL escape hatch
Use soql.raw() only for trusted syntax the builder does not model:
import { soql } from "@kysoql/core";
import { db } from "./db";
const query = db
.selectFrom("Account")
.select(["Id", "Name"])
.where(soql.raw("CALENDAR_YEAR(CreatedDate) = 2026"));Raw fragments bypass normal identifier validation and value escaping. Never interpolate request data, search terms, IDs, form values, or other untrusted input into them.
Filter with functions
Expression callbacks expose supported date functions, toLabel, and distance through eb.fn. Beta arithmetic lives under eb.beta so experimental syntax is easy to spot.
Continue with date functions, translated labels, or Beta and pilot features.