# Canonical Supabase Clients in DeskcommCRM: Admin, Server, and Browser Patterns

> Discover the three canonical Supabase clients in DeskcommCRM Admin Server and Browser Each client enforces strict security for Supabase operations

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: architecture
- Published: 2026-09-12

---

**DeskcommCRM defines three canonical Supabase client wrappers—Admin, Server, and Browser—that enforce strict security boundaries by separating service-role operations, RLS-compliant server requests, and realtime browser interactions.**

The **melgarafael/DeskcommCRM** repository implements a defense-in-depth strategy for PostgreSQL access through specialized client factories. These **canonical Supabase clients** isolate privileged webhook handlers from tenant-scoped API routes and client-side realtime subscriptions, ensuring each execution context uses the appropriate credentials and respects Row-Level Security (RLS) policies defined in the codebase architecture.

## The Three Canonical Supabase Client Types

DeskcommCRM exposes distinct client constructors in `lib/supabase/` to prevent credential leakage between execution contexts.

### Admin Client (`createAdminClient`)

Located in [`lib/supabase/admin.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/admin.ts), the Admin client instantiates a **service-role** Supabase client using `SUPABASE_SERVICE_ROLE_KEY`. This client bypasses RLS entirely, requiring developers to manually filter by `organization_id` from trusted sources such as cookies, JWTs, or webhook secrets.

Use this client exclusively for server-only code including WAHA/Nuvemshop webhook processors, background cron workers, tenant provisioning scripts, and health-check endpoints that require unrestricted database access.

### Server Client (`createClient` from [`lib/supabase/server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/server.ts))

Exported from [`lib/supabase/server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/server.ts), the Server client uses the `NEXT_PUBLIC_SUPABASE_ANON_KEY` to enforce RLS automatically. When used within Next.js Server Components or API route handlers (`app/api/**`), this client automatically scopes queries to the current user's `organization_id` through PostgreSQL RLS policies.

This is the default choice for standard REST API endpoints and server-side data fetching where tenant isolation must be maintained.

### Browser Client (`createClient` from [`lib/supabase/browser.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/browser.ts))

Defined in [`lib/supabase/browser.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/browser.ts), the Browser client wraps `@supabase/ssr`'s `createBrowserClient` for use in Client Components (`"use client"`). It stores sessions in a SameSite-Strict cookie named `sb-deskcomm-auth` and handles Realtime authentication through a dedicated token endpoint at `/api/v1/auth/realtime-token`.

Use this for live UI updates, client-side CRUD operations that respect RLS, and browser-based authentication flows.

## Security Implementation and Execution Contexts

Each canonical client enforces specific security rules aligned with DeskcommCRM's multi-tenant architecture.

### Privileged Operations and Manual Filtering

The Admin client's service-role privileges require explicit safeguards. Because RLS is bypassed, every query must manually specify `organization_id` derived from trusted authentication contexts. Failure to filter results in cross-tenant data exposure.

### Automatic Tenant Isolation

The Server client relies on the anon key and PostgreSQL RLS policies to enforce tenant boundaries without manual intervention. When a request reaches an API handler using `createClient()` from [`lib/supabase/server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/server.ts), the database automatically filters rows based on the authenticated user's organization membership.

### Realtime Authentication Flow

Browser-based Supabase Realtime connections require special handling. The `prepareRealtimeAuthentication()` function fetches short-lived tokens from `/api/v1/auth/realtime-token`, allowing websockets to authenticate without exposing long-lived credentials in browser memory.

## Practical Code Examples

### Processing Webhooks with the Admin Client

Use the Admin client when handling external webhooks that require bypassing RLS to write data across tenant boundaries:

```typescript
import { createAdminClient } from '@/lib/supabase/admin';

export async function POST(req: Request) {
  // Trusted source – no RLS, must filter org manually
  const supabase = createAdminClient();
  const { data, error } = await supabase
    .from('leads')
    .insert({ organization_id: 42, name: 'Novo Lead' });

  if (error) return fail(500, error.message);
  return ok(data);
}

```

### API Routes with Automatic RLS

Standard endpoints use the Server client to respect tenant isolation automatically:

```typescript
import { createClient } from '@/lib/supabase/server';

export async function GET() {
  // Uses anon key, RLS automatically scopes to the user’s org
  const supabase = createClient();
  const { data, error } = await supabase
    .from('contacts')
    .select('*');

  if (error) return fail(400, error.message);
  return ok(data);
}

```

### Realtime UI with Browser Client

Client Components leverage the Browser client for live data subscriptions:

```tsx
'use client';
import { createClient, prepareRealtimeAuthentication } from '@/lib/supabase/browser';
import { useEffect, useState } from 'react';

export default function LeadFeed() {
  const [leads, setLeads] = useState<any[]>([]);

  useEffect(() => {
    (async () => {
      await prepareRealtimeAuthentication();   // ensure socket has a valid token
      const supabase = createClient();

      const channel = supabase
        .channel('public:leads')
        .on('postgres_changes', { event: '*', schema: 'public', table: 'leads' }, payload => {
          setLeads(prev => [...prev, payload.new]);
        })
        .subscribe();

      return () => supabase.removeChannel(channel);
    })();
  }, []);

  return <ul>{leads.map(l => <li key={l.id}>{l.name}</li>)}</ul>;
}

```

## Environment Configuration

All three clients rely on centralized environment variables defined in [`lib/env.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/env.ts). This file exports `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, and `SUPABASE_SERVICE_ROLE_KEY`, ensuring consistent configuration across the Admin, Server, and Browser client factories.

## Summary

- **Three specialized clients** cover all execution contexts: Admin for privileged operations, Server for RLS-compliant API routes, and Browser for realtime UI interactions.
- **Admin client** requires explicit manual filtering of `organization_id` when bypassing RLS in [`lib/supabase/admin.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/admin.ts), preventing accidental cross-tenant data leaks.
- **Server client** automatically enforces tenant isolation via Row-Level Security policies when using [`lib/supabase/server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/server.ts).
- **Browser client** manages secure SameSite-Strict cookies (`sb-deskcomm-auth`) and dedicated realtime tokens fetched from `/api/v1/auth/realtime-token` for websocket connections.

## Frequently Asked Questions

### When should I use the Admin client versus the Server client?

Use the **Admin client** only for server-only code requiring RLS bypass, such as webhook handlers, background workers, or tenant provisioning scripts. Use the **Server client** for standard API routes and Server Components where data access must respect tenant isolation policies enforced by PostgreSQL RLS.

### How does the Browser client handle realtime authentication?

The Browser client calls `prepareRealtimeAuthentication()` to fetch a short-lived token from `/api/v1/auth/realtime-token`, enabling secure websocket subscriptions while maintaining the primary session in a SameSite-Strict cookie named `sb-deskcomm-auth` as implemented in [`lib/supabase/browser.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/supabase/browser.ts).

### Why does DeskcommCRM separate these clients into different files?

According to the source code in `lib/supabase/`, separating concerns into [`admin.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/admin.ts), [`server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/server.ts), and [`browser.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/browser.ts) prevents credential leakage between execution contexts and enforces the security architecture described in CLAUDE.md and AGENTS.md. This separation ensures service-role keys never leak to browser bundles or Server Component contexts where they aren't required.

### What security risks exist if I forget to filter by organization_id in the Admin client?

Since the Admin client uses the service role key and bypasses RLS, omitting manual `organization_id` filters exposes data across all tenants in the database. This violates the multi-tenant security model and can lead to unauthorized cross-organization data access.