Compile for Apex
Generate SOQL for static or dynamic Apex without mixing it with API execution.
Apex builders produce SOQL for code that runs inside Salesforce Apex. They do not execute Apex, deploy classes, or resolve Apex variables from TypeScript.
Enter Apex after shaping the query
import { apexBind } from "@kysoql/core";
import { db } from "./db";
const compiled = db
.selectFrom("Account")
.select(["Id", "Name"])
.apex()
.where("Id", "=", apexBind<string>("accountId"))
.withUserMode()
.compile();
console.log(compiled.soql);
// SELECT Id, Name FROM Account WHERE Id = :accountId WITH USER_MODEDefine accountId in the Apex caller. The TypeScript generic only checks that
the bind is compatible with the field. It does not create or validate the Apex
variable.
Finish ordinary selections, sorting, grouping, and HAVING before calling
.apex() or .dynamicApex(). The Apex builder deliberately exposes a narrower
set of methods after the switch.
Choose the access mode intentionally
withUserMode() emits WITH USER_MODE and withSystemMode() emits
WITH SYSTEM_MODE. Calling one replaces the other.
Kysoql does not decide which mode is appropriate for your security model. The
mode also shares Salesforce's filtering WITH slot with data categories,
RecordVisibilityContext, and UserId.
Lock records
import { apexBind } from "@kysoql/core";
import { db } from "./db";
const locked = db
.selectFrom("Account")
.select(["Id", "Name"])
.apex()
.where("Id", "=", apexBind<string>("accountId"))
.forUpdate()
.compile();forUpdate() is available on Apex record queries. It cannot be combined with
ORDER BY or ALL ROWS. Salesforce controls the actual transaction and lock
behaviour.
Include deleted records
import { db } from "./db";
const compiled = db
.selectFrom("Account")
.select(["Id", "Name"])
.apex()
.allRows()
.compile();allRows() is available on Apex record, aggregate, and count builders. For REST
queries, use executeAll()
instead.
Aggregate and count queries
Build the aggregate structure before entering Apex:
import { apexBind } from "@kysoql/core";
import { db } from "./db";
const report = db
.selectFrom("Opportunity")
.select(({ fn }) => fn.sum("Amount").as("totalAmount"))
.groupBy("StageName")
.select("StageName")
.apex()
.where("IsClosed", "=", apexBind<boolean>("closed"))
.limit(apexBind<number>("rowLimit"))
.compile();The specialised Apex aggregate builders accept supported filters and binds but do not reopen the full aggregate-building surface after the context switch.
Static and dynamic Apex
Use .apex() for the static compile context. Use .dynamicApex() when you need
the dynamic-only features Kysoql models, such as bound Database.QueryOptions.
Neither method is a JavaScript execution adapter. Your Apex code still owns bind values, access level, and the API used to execute the resulting SOQL.
- Apex permits only the supported bounded
FIELDS(STANDARD)form. - Knowledge article queries reject Kysoql bind expressions.
- Relationship depth and syntax still depend on the target Apex API version.
Continue with Apex bindings or the Core API reference.