← All guides
TS

TypeScript SDK

@chisel-to/sdk is the official TypeScript client for chisel.to. One package covers Node.js, the browser and React Native, with a first-class React adapter for hooks and providers.

ESM + CJS Zero runtime deps React peer-optional Tree-shakeable Node 18+

1. Install

The SDK ships as a single npm package. Add it to any TypeScript or JavaScript project.

npm
npm 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.ts
import { ChiselClient } from "@chisel-to/sdk";

export const chisel = new ChiselClient({
    baseUrl: "https://api.chisel.to/v1/my-project",
    apiKey:  process.env.CHISEL_PUBLISHABLE_KEY!,
});
The 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.