Back to Blog
Development Guides

Building Secure Admin Dashboards with Supabase and React

AyzaDevLabs Team27 May 20266 min read

When your application starts growing, you inevitably need an admin dashboard to track metrics, analyze signups, and monitor revenue. While low-code tools like Retool are great for internal operations, sometimes a custom-built React dashboard deployed on Vercel is faster, cleaner, and exactly what you need.

However, building a custom admin dashboard connected to a Supabase backend presents a unique security challenge: How do you query all data (bypassing Row Level Security) without exposing your highly privileged service role key in the frontend?

Here is the step-by-step architecture for securely building an admin dashboard with Supabase.

The Security Problem

By default, Supabase enforces Row Level Security (RLS). When a user logs in, they only see the data they are permitted to see.

An admin dashboard needs to see *everything*—total users, all bills, revenue aggregates, etc.

The dangerous temptation is to initialize the Supabase client in your React dashboard using the `service_role` key. Never do this. Anything in a frontend React app is visible to users who inspect the page source. Exposing your service role key gives anyone total read/write access to your entire database, bypassing all security rules.

The Secure Architecture: Supabase Edge Functions

The correct way to handle admin queries is to use a secure proxy. We will build a small Supabase Edge Function that runs securely on the server.

* Dashboard (Browser): Makes a request to the Edge Function with a secret admin token. * Edge Function (Server): Verifies the token, uses the `service_role` key (safely hidden on the server) to query the database, and returns the aggregated data. * Database: Returns the data, bypassing RLS safely.

### Step 1: Create the Edge Function

Create a new Edge Function in your Supabase project (e.g., `admin-dashboard-stats`). This TypeScript function will securely fetch your data.

```typescript // supabase/functions/admin-dashboard-stats/index.ts import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' import { corsHeaders } from '../_shared/cors.ts'

Deno.serve(async (req) => { // Handle CORS preflight requests if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })

// Simple secret check — verify the dashboard is authorized const authHeader = req.headers.get('Authorization') if (authHeader !== `Bearer ${Deno.env.get('ADMIN_DASHBOARD_SECRET')}`) { return new Response('Unauthorized', { status: 401 }) }

// Initialize Supabase client WITH the service_role key const supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! )

// Fetch all necessary admin data simultaneously const [users, tenants, farmerBills, buyerBills, orders, farmers] = await Promise.all([ supabase.from('users').select('id,name,email,business_name,city,state,plan_type,created_at,is_active'), supabase.from('tenants').select('id,name,created_at,subscription_tier,is_active'), supabase.from('farmer_bills').select('id,created_at,final_payable'), supabase.from('buyer_bills').select('id,created_at,final_payable'), supabase.from('orders').select('id,amount,payment_status,created_at'), supabase.from('farmers').select('id,created_at'), ])

// Return the aggregated data return new Response(JSON.stringify({ users: users.data, tenants: tenants.data, farmerBills: farmerBills.data, buyerBills: buyerBills.data, orders: orders.data, farmers: farmers.data, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }) }) ```

### Step 2: Set Environment Variables

You need to define the secret password that your dashboard will use to communicate with this function.

In your Supabase Dashboard, go to Settings → Edge Functions → Secrets, and add a new secret: `ADMIN_DASHBOARD_SECRET` = `some-long-random-secret-you-make-up`

*(Note: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` are automatically injected into the Edge Function environment by Supabase.)*

### Step 3: Deploy the Function

Deploy your new function using the Supabase CLI: ```bash supabase functions deploy admin-dashboard-stats ```

### Step 4: Update Your React Dashboard

Now, your custom React dashboard simply makes a standard `fetch` request to your new Edge Function URL, passing the secret token in the Authorization header:

```javascript const fetchDashboardStats = async () => { const response = await fetch('https://your-project-id.supabase.co/functions/v1/admin-dashboard-stats', { method: 'POST', headers: { 'Authorization': `Bearer some-long-random-secret-you-make-up`, 'Content-Type': 'application/json' } }); const data = await response.json(); // Update your React state and render your charts! } ```

By following this pattern, your service role key never leaves the server, your database remains secure, and you get a blazingly fast, custom-built admin dashboard to monitor your business metrics.