Learn

Navigate through learn topics

API Keys and Environment Setup

Safe handling of Supabase credentials and setting up your development environment

Last updated: 8/15/2025

Supabase uses API keys to control access to your database. Understanding the different key types and keeping them secure is crucial for your app's safety.

API Key Types

Anon Key

Safe for client-side code when RLS is properly configured.

// This key can be public in your frontend
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

Capabilities:

  • Read data allowed by RLS policies
  • Write data allowed by RLS policies
  • Sign up new users
  • Sign in existing users

Limitations:

  • Cannot bypass RLS policies
  • Cannot access admin functions
  • Limited to what your RLS policies allow

Service Role Key

Server-only secret with full database access.

// This key must NEVER be exposed publicly
const SUPABASE_SERVICE_ROLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

Capabilities:

  • Full read/write access to all tables
  • Bypasses all RLS policies
  • Can manage users and authentication
  • Can call any database function

Security Rule: Keep this server-only, never in client code.

Environment Variable Setup

Local Development

Create a .env.local file (never commit this):

# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Add to your .gitignore:

# .gitignore
.env.local
.env*.local
*.env

Shareable Template

Create .env.example for your team (safe to commit):

# .env.example
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

Production Deployment

Set environment variables in your hosting platform:

Vercel:

  • Project Settings (Environment Variables)
  • Add each variable with appropriate values

Netlify:

  • Site Settings (Environment Variables)
  • Build environment variables

Railway/Render/Fly:

  • Platform-specific environment variable settings

Client Initialization

Frontend Client (Safe for Browsers)

// lib/supabase.js
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);

Server Client (Backend Only)

// lib/supabase-admin.js
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;

export const supabaseAdmin = createClient(
  supabaseUrl, 
  supabaseServiceKey,
  {
    auth: {
      autoRefreshToken: false,
      persistSession: false
    }
  }
);

Using Different Clients

Client-Side Operations (RLS Protected)

import { supabase } from '@/lib/supabase';

// This respects RLS policies
const { data: userPosts } = await supabase
  .from('posts')
  .select('*')
  .eq('author_id', user.id);

// User authentication
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password'
});

Server-Side Operations (Admin Access)

import { supabaseAdmin } from '@/lib/supabase-admin';

// API route or server function
export async function GET(request) {
  // This bypasses RLS - be careful!
  const { data: allPosts } = await supabaseAdmin
    .from('posts')
    .select('*');
    
  return Response.json(allPosts);
}

// Admin user management
const { data: users } = await supabaseAdmin.auth.admin.listUsers();

Environment-Specific Setup

Multiple Environments

Create separate Supabase projects for different environments:

# .env.development
NEXT_PUBLIC_SUPABASE_URL=https://dev-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=dev_anon_key...

# .env.staging  
NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=staging_anon_key...

# .env.production
NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=prod_anon_key...

Loading Environment Variables

// config/supabase.js
const getSupabaseConfig = () => {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
  
  if (!url || !anonKey) {
    throw new Error('Missing Supabase environment variables');
  }
  
  return { url, anonKey };
};

export const { url: supabaseUrl, anonKey: supabaseAnonKey } = getSupabaseConfig();

Framework-Specific Examples

Next.js (App Router)

// app/lib/supabase.js
import { createClient } from '@supabase/supabase-js';

export function createClientComponentClient() {
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
  );
}

// app/lib/supabase-server.js
export function createServerComponentClient() {
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.SUPABASE_SERVICE_ROLE_KEY,
    {
      auth: {
        persistSession: false
      }
    }
  );
}

React (Vite)

// src/lib/supabase.js
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);

Node.js/Express

// server/config/supabase.js
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_ROLE_KEY
);

module.exports = { supabase };

Security Best Practices

Never Log Secrets

// Bad: secrets in logs
console.log('Supabase config:', {
  url: process.env.NEXT_PUBLIC_SUPABASE_URL,
  key: process.env.SUPABASE_SERVICE_ROLE_KEY // DON'T DO THIS
});

// Good: hide sensitive values
console.log('Supabase config:', {
  url: process.env.NEXT_PUBLIC_SUPABASE_URL,
  keyLength: process.env.SUPABASE_SERVICE_ROLE_KEY?.length
});

Validate Environment Variables

// utils/env-validation.js
const requiredEnvVars = [
  'NEXT_PUBLIC_SUPABASE_URL',
  'NEXT_PUBLIC_SUPABASE_ANON_KEY'
];

const validateEnv = () => {
  const missing = requiredEnvVars.filter(
    envVar => !process.env[envVar]
  );
  
  if (missing.length > 0) {
    throw new Error(`Missing environment variables: ${missing.join(', ')}`);
  }
};

// Call during app startup
validateEnv();

Use Different Keys for Different Environments

Never reuse production keys in development or staging:

// Different projects = different keys = data isolation
const configs = {
  development: {
    url: 'https://dev-project.supabase.co',
    key: 'dev-specific-key'
  },
  production: {
    url: 'https://prod-project.supabase.co', 
    key: 'prod-specific-key'
  }
};

Testing Your Setup

Verify Connection

// Test basic connection
const testConnection = async () => {
  try {
    const { data, error } = await supabase
      .from('_test')
      .select('*')
      .limit(1);
      
    if (error && error.code === '42P01') {
      console.log('✅ Connected to Supabase (table not found is expected)');
    } else {
      console.log('✅ Connected to Supabase');
    }
  } catch (err) {
    console.error('❌ Supabase connection failed:', err.message);
  }
};

Verify Environment Separation

// Make sure you're not accidentally using prod data in dev
const verifyEnvironment = async () => {
  const { data } = await supabase
    .from('organisations')
    .select('name')
    .limit(5);
    
  console.log('Connected to environment with orgs:', 
    data?.map(org => org.name)
  );
};

Common Setup Issues

Missing Environment Variables

Error: supabaseUrl is required

Fix: Check your .env file exists and variables are named correctly.

Wrong Variable Names

# Wrong
SUPABASE_URL=...
SUPABASE_ANON_KEY=...

# Right
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...

Service Key in Frontend

Error: This looks like a service_role key

Fix: Only use anon keys in client-side code.

Proper environment setup is the foundation of secure Supabase usage. Take time to get this right from the start (it prevents security issues and deployment headaches later).

Related Topics

Continue building your security knowledge: