npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cerios/salesforce-sobjectly

v1.3.0

Published

Type-safe Salesforce REST API client for TypeScript. Generated SObject types, a typed SOQL builder, CRUD, composite, sObject collections, SOSL, invocable actions/Flow, Tooling API and Bulk API 2.0.

Readme

@cerios/salesforce-sobjectly

A type-safe Salesforce REST API client for TypeScript. It generates types for your org's sObjects and gives you a typed SOQL builder, CRUD, composite requests, sObject collections, SOSL, invocable actions and Flows, the Tooling API and Bulk API 2.0. Requests go through native fetch by default, and any other HTTP client can be plugged in.

npm version npm downloads License: MIT TypeScript

✨ Key Features

  • Generated sObject types: sobjectly generate reads describe metadata and writes a read type, a create input and an update input for every sObject. Required fields, restricted picklists and relationships all come from your org. Picklists can also become named constants or enums, e.g. CaseStatus.Working.
  • Typed SOQL builder: field names are checked and autocompleted. The query result only contains the fields you selected, so record.Phone is a compile error unless you selected Phone.
  • Relationship queries: selectRelated("Account", "Name") and selectChild("Contacts", c => c.select("Email")) produce typed nested results.
  • The whole REST API: sObject CRUD, upsert by external id, query and queryAll with pagination, SOSL, Composite, Composite Graph, sObject Tree, sObject Collections, invocable actions and Flows, the Tooling API, Apex REST and Bulk API 2.0. Anything else is reachable through request<T>().
  • Platform APIs: platform events, approvals, quick actions, UI API, files, reports, query plans and Apex debug-log capture.
  • CI-friendly codegen: sobjectly generate --check fails when the generated file is out of date and ignores formatting-only differences.
  • Bring your own HTTP client: fetch is the default; any other client (axios, undici, Playwright's APIRequestContext, a logging wrapper) fits a one-method interface.
  • Built-in auth: Salesforce CLI login (sfCli), static access token, client credentials, JWT bearer, refresh token or your own provider. Tokens are cached, and the client re-authenticates once on a 401.
  • Safe by default: values are escaped in SOQL, absolute URLs are limited to your instance's origin, tokens are never logged, and retries are opt-in. Mutations are only retried when you ask for it, e.g. on UNABLE_TO_LOCK_ROW, where Salesforce rolled the request back.
  • Small footprint: dual ESM/CJS builds. The only runtime dependency is jiti, which the codegen CLI uses to load TypeScript configs; the client never loads it.

📦 Installation

npm install @cerios/salesforce-sobjectly

It needs Node.js 20.19 or later, and TypeScript 5.4 or later for the types.

🎯 Quick Start

1. Create a config

npx sobjectly init

init asks a few questions and writes sobjectly.config.ts or sobjectly.config.json:

  • the API version, e.g. v66.0;
  • where to write the generated types;
  • which sObjects to generate;
  • how to log in;
  • how to type picklists;
  • the config format: TypeScript (type-checked with defineConfig) or JSON (with a bundled JSON Schema for editor autocomplete).

It can also add the credential variables to .env.example and an npm script to package.json. Both formats are validated when loaded, and all problems are reported at once. A TypeScript config looks like this:

import { defineConfig } from "@cerios/salesforce-sobjectly/codegen";

export default defineConfig({
	apiVersion: "v66.0",
	output: "src/generated/sobjects.ts",
	sobjects: ["Account", "Contact", "Case", "User"],
	// Fill these in from process.env (any variable names), a secret store or elsewhere.
	auth: {
		type: "clientCredentials",
		loginUrl: process.env.SF_LOGIN_URL,
		clientId: process.env.SF_CLIENT_ID,
		clientSecret: process.env.SF_CLIENT_SECRET,
	},
	picklists: "union",
});

The variable names are only suggestions; use whatever names your environment already has. A JSON config uses "${NAME}" placeholders instead. See Auth for every login method.

2. Generate types for your org

npx sobjectly generate --env-file .env

3. Use the typed client

import { SalesforceClient, clientCredentials } from "@cerios/salesforce-sobjectly";
import { API_VERSION, type SObjectRegistry } from "./generated/sobjects";

const sf = new SalesforceClient<SObjectRegistry>({
	apiVersion: API_VERSION, // the same version the types were generated with
	auth: clientCredentials({
		loginUrl: process.env.SF_LOGIN_URL!, // your My Domain URL
		clientId: process.env.SF_CLIENT_ID!,
		clientSecret: process.env.SF_CLIENT_SECRET!,
	}),
	// or reuse the config's auth: auth: await loadAuth() from "@cerios/salesforce-sobjectly/codegen"
});

// CRUD
const accountId = await sf.sobject("Account").create({ Name: "Acme" });
await sf.sobject("Account").update(accountId, { Phone: "+31 20 123 4567" });
const account = await sf.sobject("Account").get(accountId, ["Id", "Name", "Phone"]);

// Typed SOQL
const page = await sf.query(
	sf
		.soql("Contact")
		.select("Id", "Email")
		.selectRelated("Account", "Name")
		.where("Email", "LIKE", "%@acme.com")
		.orderBy("LastName")
		.limit(50),
);
page.records[0].Account?.Name; // string
page.records[0].Phone; // ❌ compile error: Phone was not selected

// Every record, across all pages
for await (const contact of sf.iterate(sf.soql("Contact").select("Id", "Email"))) {
	console.log(contact.Email);
}

📖 Documentation

| Guide | What's in it | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | | Authentication | Salesforce CLI, access tokens, client credentials, JWT bearer, refresh tokens, custom providers | | Code generation | Config reference, type mapping, required fields, picklists, CI usage | | SOQL builder | Selecting, relationships, filters, aggregates, pagination, escaping | | Composite & collections | Composite with references, batch, tree, graph, sObject collections | | Approvals, files, reports & more | Record types, picklists, platform events, approvals, quick actions, UI API, files, reports, query plans | | Bulk API 2.0 | Ingest jobs, query jobs, CSV handling | | Transports | Custom HTTP clients (axios and Playwright examples), streaming, hooks and request logging, retries, timeouts | | Endpoint coverage | Which Salesforce REST resources have typed helpers |

🧭 API Overview

sf.sobject("Account"): basicInfo, describe, create, get, update, delete, upsert, getByExternalId,
                       getDeleted, getUpdated, getBlob, children, parent, listViews, listViewResults,
                       layouts, compactLayouts, approvalLayouts, query, collect, iterate
sf.soql("Account") → SoqlQueryBuilder
sf.query / sf.queryMore / sf.iterate / sf.collect     (queryAll via { includeDeleted: true })
sf.search:      sosl, parameterized, suggestions
sf.composite:   execute, batch, tree, graph   (typed builders; batch also uploads files)
sf.collections: create, update, upsert, delete, retrieve        (≤ 200 per call, or { chunk: true })
sf.actions:     invokeFlow, invokeApex, invokeStandard, invokeCustom, list*/describe*
sf.tooling:     query, sobject, executeAnonymous, runTestsSynchronous, runTestsAsynchronous, request
sf.bulk:        ingest, createIngestJob, query, createQueryJob
sf.events:      publish (platform events)
sf.approvals:   list, submit, approve, reject, pending
sf.files:       upload, download, newVersion, link
sf.reports:     list, describe, run, runAsync, instance, waitForInstance, toRows
sf.uiApi:       objectInfo, picklistValues, record, layout
sf.quickActions / sf.sobject(name).quickActions: list, describe, defaultValues, invoke
sf.sobject(name).recordTypeId(), .picklistValues();  sf.explain(query);  sf.clearCache()
sf.tooling.debugLogs: capture, list, body   (executeAnonymous(apex, { captureLog: true }))
sf.users:       passwordExpired, setPassword, resetPassword
sf.apexRest(), sf.request(), sf.limits(), sf.versions(), sf.describeGlobal(), sf.recordCount(), sf.recentlyViewed()

Flows and invocable actions

const [result] = await sf.actions.invokeFlow<{ accountId: string }, { caseId: string }>("Start_Onboarding", [
	{ accountId },
]);
result.outputValues?.caseId;

Composite with references

const result = await sf.composite.execute(
	(c) => {
		const account = c.create("Account", { Name: "Acme" });
		const contact = c.create("Contact", { LastName: "Doe", AccountId: account.ref("id") });
		return { account, contact };
	},
	{ allOrNone: true },
);
const contactId = result.get(result.refs.contact).id;

Any other endpoint

import type { OrgLimits } from "@cerios/salesforce-sobjectly";

const limits = await sf.request<OrgLimits>({ path: "/limits" });
const tooling = await sf.request({ path: "/tooling/query", query: { q: "SELECT Id FROM ApexClass" } });
const custom = await sf.apexRest<{ ok: boolean }>({ method: "POST", path: "/orders", body: { id: 1 } });

path is relative to /services/data/{apiVersion}. Paths that start with /services/ are used as-is, and absolute URLs must be on your instance's origin.

🔢 API version

There is no default API version: you always choose one.

  • Codegen: apiVersion is required in sobjectly.config.ts, and sobjectly generate fails without it.
  • Client: apiVersion is required in new SalesforceClient({ ... }), both as a TypeScript type and at runtime.

A fixed version means a Salesforce release, or an update of this package, never silently changes the API contract you call. Salesforce freezes behaviour per API version.

The generated file exports the version it was generated with as API_VERSION. Pass that to the client, so your types and your requests always use the same version. To upgrade, change apiVersion in the config and run sobjectly generate again.

🧯 Errors

| Error | When | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SalesforceError | Any non-2xx response. Carries status, errorCode, errors[], body, limitInfo and a path without the query string. | | SalesforceSaveError | A 2xx response in which an item reported success: false (create, collections, actions). | | SalesforcePartialFailureError | A call split into several requests (chunked collections, batched events) failed after earlier chunks succeeded. completedResults holds their results; cause is the original error. | | SalesforceAuthError | The token request failed. Carries the OAuth error and errorDescription, never secrets. | | SalesforceBulkJobError | A Bulk job ended as Failed or Aborted, or timed out. | | ApexExecutionError | Anonymous Apex failed to compile or threw. | | DebugLogCaptureError | The work passed to sf.tooling.debugLogs.capture() threw. logs holds the logs collected anyway; cause is the original error. |

Error codes are typed, with autocomplete for the common ones (SalesforceErrorCode). Check them with the guards:

import { hasErrorCode, isSalesforceError } from "@cerios/salesforce-sobjectly";

try {
	await sf.sobject("Account").update(id, { Name: "Acme" });
} catch (error) {
	if (hasErrorCode(error, "UNABLE_TO_LOCK_ROW")) {
		// retry later
	} else if (isSalesforceError(error, "INSUFFICIENT_ACCESS_OR_READONLY")) {
		// ...
	}
}

🤝 Contributing

Contributions are welcome. See CONTRIBUTING.md. Report security issues as described in SECURITY.md.

📄 License

MIT © Ronald Veth - Cerios

🔗 Links