Getting started
Install Kysoql, authenticate to Salesforce, generate a schema, and execute a typed REST query.
This guide takes you from a new TypeScript project to a typed Salesforce query using Kysoql's native REST transport.
Kysoql is ESM-only and currently requires Node.js >=22.12.0.
1. Install Kysoql
pnpm add @kysoql/core @kysoql/rest @kysoql/auth
pnpm add -D @kysoql/codegen2. Configure Salesforce authentication
For a server-to-server integration, JWT bearer is a good default when your application can protect a private key.
import { SalesforceAuth } from "@kysoql/auth";
const auth = new SalesforceAuth({
loginUrl: "https://login.salesforce.com",
clientId: "external-client-app-id",
});
export const getSalesforceSession = () =>
auth.jwtBearer({
username: "integration@example.com",
privateKey: { type: "file", path: "./salesforce-auth-key.pem" },
});If the PEM comes from a secret manager, use { type: "pem", value }. If your application already has an RS256 CryptoKey, use { type: "crypto-key", key }.
See JWT bearer authentication for the Salesforce external client app setup.
3. Generate your Salesforce schema
Create kysoql.config.ts and list the objects your application will query:
import { defineConfig } from "@kysoql/codegen";
import { getSalesforceSession } from "./salesforce-auth";
export default defineConfig({
auth: getSalesforceSession,
objects: ["Account", "Contact"],
schemaName: "SalesforceSchema",
});Run the generator:
pnpm exec kysoql generateIf the project has a src directory, the generated file goes to src/kysoql/salesforce.generated.ts by default. Otherwise it goes to kysoql/salesforce.generated.ts.
The generated schema reflects the objects, fields, relationships, and capabilities visible to the Salesforce user you authenticated as.
4. Create a REST-backed database
Use the same authentication function as an access-token provider. The REST client keeps the current access token in memory and asks for a replacement if Salesforce reports that the session is invalid.
import { Kysoql } from "@kysoql/core";
import { createRestExecutor } from "@kysoql/rest";
import { getSalesforceSession } from "./salesforce-auth";
import {
salesforceSchemaMetadata,
type SalesforceSchema,
} from "./kysoql/salesforce.generated";
let session = await getSalesforceSession();
const instanceUrl = session.instanceUrl;
const executor = createRestExecutor({
instanceUrl,
apiVersion: "65.0",
accessToken: async ({ refresh }) => {
if (refresh) {
const next = await getSalesforceSession();
if (next.instanceUrl !== instanceUrl) {
throw new Error("Salesforce instance changed. Recreate the REST client.");
}
session = next;
}
return session.accessToken;
},
});
export const db = new Kysoql<SalesforceSchema>({
executor,
schemaMetadata: salesforceSchemaMetadata,
});JWT bearer does not use refresh tokens. When the access token expires, the provider performs another JWT exchange.
5. Run a typed query
import { db } from "./db";
const accounts = await db
.selectFrom("Account")
.select(["Id", "Name"])
.where("Name", "like", "Acme%")
.orderBy("Name")
.limit(25)
.execute();
for (const account of accounts) {
console.log(account.Id, account.Name);
}Kysoql compiles the builder to:
SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%' ORDER BY Name LIMIT 25The result type follows the generated Salesforce schema and the fields you selected. No matching rows returns an empty array.
No. This getting started guide uses @kysoql/rest, which sends queries with native fetch. Use @kysoql/jsforce only when your application already has a JSforce connection you want to reuse.
No. The generated schema catches many mistakes while you write the query, but Salesforce still applies permissions, API-version behaviour, enabled features, query limits, and runtime validation.