1. Install
The SDK ships as a single npm package. Add it to any TypeScript or JavaScript project.
npmnpm install @chisel-to/sdk
For React projects, that same install gives you a React adapter on a subpath. There is nothing extra to add unless you want the React hooks:
npm install @chisel-to/sdk react react-dom
For React Native, no native modules are required — the SDK uses fetch and WebSocket, both of which RN polyfills out of the box.
2. Create the client
Grab your project's base URL and a publishable API key from Connect in the dashboard. Construct one client per process and reuse it everywhere:
lib/chisel.tsimport { ChiselClient } from "@chisel-to/sdk";
export const chisel = new ChiselClient({
baseUrl: "https://api.chisel.to/v1/my-project",
apiKey: process.env.CHISEL_PUBLISHABLE_KEY!,
});
apiKey here is a publishable key. It identifies your project, not a person.
User identity is handled separately by the auth module below.
3. Auth
The auth module wraps email/password sign-up and sign-in, social login redirects, and refresh token rotation. Tokens are persisted in localStorage on the web and in-memory on Node by default — pass a custom storage for React Native (more below).
// Register
const { user, tokens } = await chisel.auth.register({
email: "ada@example.com",
password: "correct horse battery staple",
name: "Ada Lovelace",
});
// Log in
await chisel.auth.login({ email: "ada@example.com", password: "..." });
// Who am I? — returns the bare user object (or null if signed out).
const me = await chisel.auth.me();
// Log out — revokes the refresh token server-side and clears local state.
await chisel.auth.logout();
Subscribing to auth changes
The client emits user changes so your UI can react:
const unsubscribe = chisel.auth.onUserChanged((user) => {
console.log("Signed in as", user?.email ?? "guest");
});
Social login
Build a redirect URL for any provider you've enabled in your project's Auth settings:
const url = chisel.auth.socialRedirectUrl("google", {
redirectUri: "https://my-app.com/auth/callback",
});
// then on the callback page — tokens arrive in the URL fragment:
await chisel.auth.completeFromUrl(window.location.href);
4. Typed resources
Every table you've defined in your project becomes a typed resource. You can either generate exact types from your project (see Generated types) or describe the shape inline:
interface Highscore {
id: number;
user_id: number;
score: number;
created_at: string;
}
const highscores = chisel.resource<Highscore>("highscores");
// List with filtering + pagination
const top = await highscores.list({
filter: { score: { gte: 1000 } },
sort: "-score",
perPage: 25,
});
// Get one
const one = await highscores.get(42);
// Create
const created = await highscores.create({ score: 9001 });
// Update
await highscores.update(42, { score: 9999 });
// Delete
await highscores.remove(42);
Filter operators
Filter values accept primitives or operator objects:
await orders.list({
filter: {
status: "paid",
total: { gt: 100 },
created_at: { gte: "2026-01-01" },
email: { like: "%@example.com" },
country: { in: ["GB", "DE", "FR"] },
},
});
5. Realtime
Realtime rides on a single WebSocket. Subscribe to a channel and you get typed events for inserts, updates and deletes — plus presence if the channel allows it.
const channel = chisel.realtime.channel<Highscore>("highscores");
const unsub = channel.on("insert", (row) => {
console.log("New highscore", row);
});
channel.on("update", (row, prev) => { /* ... */ });
channel.on("delete", (row) => { /* ... */ });
channel.on("presence", (members) => { /* ... */ });
await channel.join();
// Later:
unsub();
await channel.leave();
6. File uploads
The files module handles multipart uploads against your project's storage driver (S3, R2, local — whatever's configured).
const file = document.querySelector<HTMLInputElement>("#avatar")!.files![0];
const asset = await chisel.files.upload({
data: file,
filename: file.name,
contentType: file.type,
});
console.log(asset.url);
React Native passes { uri } rather than a Blob:
await chisel.files.upload({
data: { uri: photo.uri },
filename: "avatar.jpg",
contentType: "image/jpeg",
});
7. Push notifications
Register a device token with the project — APNs, FCM, Expo or web push.
await chisel.push.registerDevice({
token: "ExponentPushToken[...]",
platform: "expo",
deviceId: "device-abc-123",
locale: "en-US",
});
8. In-app purchases
Validate Apple JWS or Google Play purchase tokens server-side and read entitlements back as a stream.
await chisel.iap.verifyApple({ jws: "..." });
await chisel.iap.verifyPlay({ productId: "pro_monthly", purchaseToken: "..." });
const unsub = chisel.iap.onEntitlementsChanged((entitlements) => {
const isPro = entitlements.some((e) => e.productId === "pro_monthly");
});
9. React adapter
Import from the /react subpath to get hooks and a provider. They are tree-shaken away from the main bundle if you don't use them.
import { ChiselProvider, useChisel, useUser, useResource } from "@chisel-to/sdk/react";
import { chisel } from "./lib/chisel";
export function App() {
return (
<ChiselProvider client={chisel}>
<Leaderboard />
</ChiselProvider>
);
}
function Leaderboard() {
const { user } = useUser();
const { data, loading, error } = useResource<Highscore>("highscores", {
filter: { user_id: user?.id ?? 0 },
sort: "-score",
perPage: 10,
});
if (loading) return <p>Loading…</p>;
if (error) return <p>{error.message}</p>;
return (
<ul>
{data.map((row) => (
<li key={row.id}>{row.score}</li>
))}
</ul>
);
}
The provider also exposes useAuth() for sign in / out and useChannel() for realtime subscriptions that auto-clean on unmount.
10. Node / scripts
The same client works in Node 18+ scripts. Use a service-role API key when you want to bypass row-level auth from a backend job:
import { ChiselClient } from "@chisel-to/sdk";
const chisel = new ChiselClient({
baseUrl: process.env.CHISEL_BASE_URL!,
apiKey: process.env.CHISEL_SERVICE_KEY!,
});
const orders = chisel.resource<Order>("orders");
const stale = await orders.list({ filter: { status: "pending", created_at: { lt: cutoff } } });
11. React Native
Drop in an AsyncStorage adapter so tokens survive app launches:
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ChiselClient, type TokenStorage } from "@chisel-to/sdk";
const asyncStorage: TokenStorage = {
get: (k) => AsyncStorage.getItem(k),
set: (k, v) => AsyncStorage.setItem(k, v),
delete: (k) => AsyncStorage.removeItem(k),
};
export const chisel = new ChiselClient({
baseUrl: "https://api.chisel.to/v1/my-project",
apiKey: "ck_live_pub_...",
storage: asyncStorage,
});
12. Errors
Every method throws a typed error subclass — so you can branch on category instead of parsing messages:
import { ChiselApiError, ChiselValidationError, ChiselNetworkError } from "@chisel-to/sdk";
try {
await chisel.resource("orders").create({ /* ... */ });
} catch (e) {
if (e instanceof ChiselValidationError) {
for (const [field, msgs] of Object.entries(e.fieldErrors)) {
console.warn(field, msgs);
}
} else if (e instanceof ChiselApiError) {
console.warn(e.status, e.code, e.message);
} else if (e instanceof ChiselNetworkError) {
console.warn("Offline?", e.cause);
} else {
throw e;
}
}
13. Generated types
Each project's Connect page hands you a sdk.ts file that re-exports a typed
ChiselClient with every resource's row shape filled in from your schema. Drop it next to
your client, replace the resource<T>() calls with the generated accessors, and your IDE
will know every column on every table.
You can also feed the project's OpenAPI 3.1 spec or llms.txt to your AI tools so they author the same calls — see the AI coding tools guide.