| Where | Client | Row-level security |
|---|---|---|
| Server components, server actions, route handlers | getSupabaseServerClient() from @repo/supabase/server-client | Applies, as the signed-in user |
| Client components | useSupabase() from @repo/supabase/hooks/use-supabase, usually with React Query | Applies, as the signed-in user |
Phone app API routes (/api/v1) | The client that enhanceRouteHandler passes in (bearer token or cookie) | Applies, as the signed-in user |
| Server code that must act beyond the user's rights | getSupabaseServerAdminClient() from @repo/supabase/server-admin-client | Skipped: check permissions yourself |
Fetch data in server components where you can. With the normal clients, you do not add "is this user allowed?" checks for reads: the policies already filter the rows.
import { getSupabaseServerClient } from '@repo/supabase/server-client';
async function ProjectsList() {
const client = getSupabaseServerClient();
const { data } = await client.from('projects').select('*');
// only the projects of accounts this user belongs to
}
Server actions use next-safe-action through @repo/next/safe-action:
| Client | Use for |
|---|---|
authActionClient | Signed-in users. Redirects to sign-in otherwise and passes ctx.user |
captchaActionClient | Signed-in users, plus a Cloudflare Turnstile check |
publicActionClient | Actions anyone may call, such as the contact form |
'use server';
import * as z from 'zod';
import { authActionClient } from '@repo/next/safe-action';
import { getSupabaseServerClient } from '@repo/supabase/server-client';
const CreateProjectSchema = z.object({
accountId: z.string().uuid(),
name: z.string().min(1),
});
export const createProjectAction = authActionClient
.inputSchema(CreateProjectSchema)
.action(async ({ parsedInput }) => {
const client = getSupabaseServerClient();
// RLS decides whether this user may insert into this account
await client.from('projects').insert({
account_id: parsedInput.accountId,
name: parsedInput.name,
});
});
The input schema validates what the browser sent. The insert still goes through row-level security, so a user who sends someone else's accountId is refused by the database.
API routes use enhanceRouteHandler from @repo/next/routes. Options: auth (require a user, default on), schema (a Zod schema for the body) and captcha. The handler receives request, the parsed body, the user and an RLS-bound client.
/server-action-builder, /service-builder and /react-form-builder write actions, services and forms in these patterns. /postgres-expert writes the table and its policies.