Skip to content

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.

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

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

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

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.

Data API

For tables with expose: true (see Data API). Name the table as you named it in table().

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

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.

Errors

Every failed request rejects with a FlatstackError carrying the error’s code, status, message, docsUrl and details, the same as the error codes everywhere else:

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

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)
clerkMiddleware(), createRouteMatcher() getServerSession and matchesPath in middleware.ts for pages, and access rules in flatstack/schema.ts for data (Server-rendered apps)

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

As Markdown: apps/client.md