Browser client
Repositories in the browser that follow the session.
import { createClient } from "better-supabase/client";
import { betterSupabase } from "./index";
export const bs = createClient(betterSupabase, {
env: {
url: import.meta.env.VITE_SUPABASE_URL,
publishableKey: import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
},
});
const customers = await bs.db.customers
.findMany({ select: ["id", "name"] })
.orThrow();bs.dbcarries the current session's actor and claims, so plugins likeactorandtenantbehave as they do on the server. It is rebuilt when the user changes.bs.queriesholds TanStack Query options.bs.authis a small store (current(),subscribe()) for UI state:loading,signed-out, orsigned-inwith the user and decoded claims. The claims are for display; RLS is what enforces access.bs.supabaseis the supabase-js client, for auth flows, storage and anything else.
Storage
storage | Use it for |
|---|---|
cookies (default) | apps with a server (Next.js, SvelteKit, …): the session is shared via @supabase/ssr cookies |
local | SPAs without a server: the session lives in localStorage |
With cookies, cookies: { encode: "tokens-only" } keeps the user object
out of the cookie, and auth.userStorage sets where auth-js keeps it instead
(localStorage by default). Use the same encode as the server; see
Encoding.
With local, the cookies options are ignored and no cookie is written, so
a server (a loader, an API route or the proxy) never sees the session. Send
the access token as an Authorization: Bearer header to call your own API;
the server adapters read the bearer token before the cookie. Pick one mode
per app: switching moves the session, so users sign in again once.
For React Native and Expo, use
better-supabase/client/native with your own
supabase-js client. It has the same bs.db, bs.queries and bs.auth, never
imports @supabase/ssr, stores the session in the device keychain with
secureStorage, and refreshes it only in the foreground with
autoRefreshOnForeground.
The publishable key and URL are validated like on the server: a secret key or a legacy JWT key is rejected before any request is made.
Edge Functions
functions<Contracts>(bs.supabase) calls Edge Functions defined with
defineFunction, typed from their own code, and returns a Result:
import { functions } from "better-supabase/client";
import type { CreateCustomer } from "../supabase/functions/create-customer/handler.ts";
const fns = functions<{ "create-customer": CreateCustomer }>(bs.supabase);
const customer = await fns
.invoke("create-customer", { name: "Acme", organizationId })
.orThrow();See Edge Functions for the function side and
the errors invoke returns.
Last updated on