shipanysaas
Documentation
Back
  • Getting started
    • Install and run
    • Project structure
    • Configuration
    • Commands
  • Coding agents
    • Agent skills
  • Authentication
    • Email sign-in
    • OAuth providers
    • Two-step sign-in and passkeys
  • Database
    • Migrations
    • Row-level security
    • Database tests
    • Reading and writing data
  • Features
    • Teams and invitations
    • Email
    • File uploads
    • Blog, docs and changelog
  • Billing
    • Stripe and Lemon Squeezy
    • Pricing plans
    • Webhooks
  • Live demo
  • Reading and writing data

    Which Supabase client to use where, and the server action and route handler wrappers the kit uses for writes.

    Pick the client

    WhereClientRow-level security
    Server components, server actions, route handlersgetSupabaseServerClient() from @repo/supabase/server-clientApplies, as the signed-in user
    Client componentsuseSupabase() from @repo/supabase/hooks/use-supabase, usually with React QueryApplies, 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 rightsgetSupabaseServerAdminClient() from @repo/supabase/server-admin-clientSkipped: 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
    }
    

    Writes: server actions

    Server actions use next-safe-action through @repo/next/safe-action:

    ClientUse for
    authActionClientSigned-in users. Redirects to sign-in otherwise and passes ctx.user
    captchaActionClientSigned-in users, plus a Cloudflare Turnstile check
    publicActionClientActions 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.

    Route handlers

    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.

    The agent skills for this

    /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.