# The client SDK and React components

`@flatstackdev/client` is how your app's frontend talks to its Flatstack backend: sign-in, your functions, the data API and file storage. `@flatstackdev/react` adds hooks and prebuilt sign-in components. Both run in the browser and hold no secret: the session is an HTTP-only cookie on your app's address, and every request runs under your project's access rules on the server.

```bash
npm install @flatstackdev/client @flatstackdev/react
```

The client has no dependencies and is about 3 KB gzipped; with the React components it's about 6 KB. React 19 is a peer dependency of `@flatstackdev/react`.

## Creating a client

```ts
import { createClient } from "@flatstackdev/client";
import type * as functions from "../flatstack/functions/todos";

export const client = createClient<typeof functions>();
```

On your app's own pages, leave `url` out and requests go to the page's own address. From another address, pass `url: "https://<your app>.flatstack.app"`; cookies are sent with every request (`credentials: "include"`).

The type parameter is optional. Given your functions module, `client.fn` knows each function's input and output, with no code generation and nothing from your server code in the browser bundle: it's a type-only import.

`createClient` also loads `/auth/fs/banner.js`, which shows the impersonation banner on static pages while someone from your team is signed in as a user from the dashboard. Pass `banner: false` to turn it off, for example in tests.

## Sign-in

```ts
await client.auth.signUp({ email, password, name });
await client.auth.signIn({ email, password });
await client.auth.signOut();

const session = await client.auth.getSession(); // { user, session } or null
const stop = client.auth.onChange((session) => render(session));
```

| Method | What it does |
| --- | --- |
| `signUp`, `signIn` | Email and password. Resolve with the new session. |
| `sendMagicLink({ email, callbackURL })` | Emails a sign-in link that lands on `callbackURL`. |
| `sendCode({ email })`, `signInWithCode({ email, code })` | A 6-digit code by email. |
| `signInWithOAuth({ provider, callbackURL })` | Sends the browser to Google, GitHub, Apple or Microsoft. |
| `passkey.signIn()`, `passkey.register()` | Passkeys for your app's own hostname. |
| `requestPasswordReset`, `resetPassword`, `changePassword`, `updateUser` | Account changes. |
| `organisations.list()`, `create()`, `setActive(id)` | Organisations; the active one is `ctx.org` in your server code. |
| `config()` | Which methods and providers your project has on (`/auth/fs/config`). |

Bot protection: when Turnstile is on, pass the widget's token as `captcha` to `signUp`, `signIn`, `sendMagicLink` and `sendCode`.

## Functions

```ts
const todo = await client.fn.createTodo({ title: "Book the venue" });
```

Each call is `POST /fn/<name>` with the input as JSON. See [Functions and routes](functions.md).

## Data API

For tables with `expose: true` (see [Data API](data-api.md)). Name the table as you named it in `table()`.

```ts
const todos = client.data.from<Todo>("todos");

const page = await todos.list({ where: { done: false, dueAt: { lt: new Date() } }, order: "dueAt.desc", limit: 20 });
const todo = await todos.insert({ title: "Book the venue" });
await todos.update(todo.id, { done: true });
await todos.deleteWhere({ done: true });
```

A plain value in `where` means equal; otherwise use `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `in` or `is` (`"null"` or `"notnull"`). `updateWhere` and `deleteWhere` need at least one condition.

## File storage

```ts
const avatars = client.storage.from("avatars");

await avatars.upload(`${user.id}/me.png`, file, { onProgress: (sent, total) => show(sent / total) });
const src = avatars.getUrl(`${user.id}/me.png`, { w: 256, format: "webp" });
const { url } = await avatars.signedUrl(`${user.id}/contract.pdf`, 3600);
const { files, cursor } = await avatars.list({ prefix: `${user.id}/` });
await avatars.remove(`${user.id}/old.png`);
```

Files up to 100 MB go up in one request; larger ones, up to 5 GB, in parts, and a failed part cancels the upload. See [File storage](storage.md).

## Errors

Every failed request rejects with a `FlatstackError` carrying the error's `code`, `status`, `message`, `docsUrl` and `details`, the same as the [error codes](../errors.md) everywhere else:

```ts
import { FlatstackError } from "@flatstackdev/client";

try {
  await client.auth.signIn({ email, password });
} catch (error) {
  if (error instanceof FlatstackError && error.code === "sign_in_locked") showLockedMessage();
}
```

## React

```tsx
import { FlatstackProvider, SignIn, UserButton, useSession } from "@flatstackdev/react";
import "@flatstackdev/react/styles.css"; // optional

export function App() {
  return (
    <FlatstackProvider client={client}>
      <Header />
    </FlatstackProvider>
  );
}

function Header() {
  const { user, loading } = useSession();
  if (loading) return null;
  return user === null ? <SignIn signUpHref="/sign-up" /> : <UserButton profileHref="/account" />;
}
```

| Hook | Gives you |
| --- | --- |
| `useSession()` | `{ session, user, loading }`, current as people sign in and out. |
| `useFn(name)` | `{ call, data, error, loading }` for one of your functions. |
| `useUpload(bucket)` | `{ upload, progress, uploading, error }`, with progress from 0 to 1. |
| `useClient()` | The client from the provider. |

### Sign-in components

| Component | What it shows |
| --- | --- |
| `<SignIn/>` | Every sign-in method your project has on: password, an emailed code, a magic link, a passkey, and a button per OAuth provider. Props: `callbackURL`, `onSignIn`, `signUpHref`, `getCaptcha`. |
| `<SignUp/>` | Name, email and password, and the OAuth providers. Props: `callbackURL`, `onSignUp`, `signInHref`, `getCaptcha`. |
| `<UserButton/>` | The signed-in user's picture or initial, opening their name, a "Manage account" link (`profileHref`) and Sign out. `signedOut` is what to show when nobody is. |
| `<OrganisationSwitcher/>` | The user's organisations, switching the active one, and creating one (`allowCreate`). |
| `<UserProfile/>` | Name, changing the password, and adding a passkey on this device. |

Every module in `@flatstackdev/react` starts with `"use client"`, so a Next.js App Router page or layout can import the provider and components directly.

They read `/auth/fs/config`, so turning a method on or off in Auth settings changes them on your next deploy, with no code change. They carry `fs-auth-*` class names and no styles of their own, so they take your app's design. `@flatstackdev/react/styles.css` is a plain starting point you can theme with its `--fs-auth-*` variables, or leave out.

### Coming from Clerk

| Clerk | Flatstack |
| --- | --- |
| `<ClerkProvider>` | `<FlatstackProvider client={createClient()}>` |
| `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>` | The components of the same name |
| `<OrganizationSwitcher/>` | `<OrganisationSwitcher/>` |
| `useUser()`, `useAuth()` | `useSession()` |
| `useOrganization()` | `session.session.activeOrganizationId`, and `client.auth.organisations` |
| `auth()`, `currentUser()` on the server | `ctx.user` in your functions; in Next.js server components, actions and route handlers, `getServerSession` from `@flatstackdev/client/server` ([Server-rendered apps](frameworks.md#signed-in-users)) |
| `clerkMiddleware()`, `createRouteMatcher()` | `getServerSession` and `matchesPath` in `middleware.ts` for pages, and access rules in `flatstack/schema.ts` for data ([Server-rendered apps](frameworks.md#protecting-pages)) |

Realtime channels and live queries (`client.realtime`, `useLive`) aren't in the client yet.