Overview
Environment variables allow you to store secrets, API keys, and configuration outside your code. Supabase provides both built-in environment variables and custom secrets management.
Never commit secrets to version control. Always use environment variables for sensitive data.
Built-in Environment Variables
Supabase automatically provides these environment variables to all functions:
Variable Description Example SUPABASE_URLYour project’s API URL https://xxx.supabase.coSUPABASE_ANON_KEYYour project’s anon key eyJhbGc...SUPABASE_SERVICE_ROLE_KEYService role key (use with caution) eyJhbGc...SUPABASE_DB_URLPostgreSQL connection string postgresql://...
Access Built-in Variables
Deno . serve ( async ( req ) => {
// Access built-in variables
const supabaseUrl = Deno . env . get ( 'SUPABASE_URL' )
const supabaseKey = Deno . env . get ( 'SUPABASE_ANON_KEY' )
console . log ( 'Project URL:' , supabaseUrl )
return new Response ( 'OK' )
})
Built-in variables are automatically configured and don’t need to be set manually.
Custom Environment Variables
Set Secrets
Set custom secrets using the CLI:
supabase secrets set OPENAI_API_KEY=sk-proj-xxx
Set multiple secrets:
supabase secrets set OPENAI_API_KEY=sk-proj-xxx STRIPE_SECRET=sk_test_xxx
Set from File
Set multiple secrets from a file:
# Create secrets file
cat > .env.production << EOF
OPENAI_API_KEY=sk-proj-xxx
STRIPE_SECRET=sk_test_xxx
RESEND_API_KEY=re_xxx
EOF
# Set all secrets at once
supabase secrets set --env-file .env.production
Don’t commit .env.production or any secrets file to git. Add them to .gitignore.
List Secrets
View all configured secrets (values are hidden):
Output:
Name Value (truncated)
OPENAI_API_KEY sk-proj-***
STRIPE_SECRET sk_test_***
RESEND_API_KEY re_***
SUPABASE_URL https://***
SUPABASE_ANON_KEY eyJh***
Unset Secrets
Remove a secret:
supabase secrets unset OPENAI_API_KEY
Using Environment Variables
In Your Functions
Access environment variables with Deno.env.get():
Deno . serve ( async ( req ) => {
// Get environment variable
const apiKey = Deno . env . get ( 'OPENAI_API_KEY' )
if ( ! apiKey ) {
return new Response (
JSON . stringify ({ error: 'OPENAI_API_KEY not configured' }),
{ status: 500 }
)
}
// Use the API key
const response = await fetch ( 'https://api.openai.com/v1/completions' , {
headers: {
'Authorization' : `Bearer ${ apiKey } ` ,
'Content-Type' : 'application/json'
},
method: 'POST' ,
body: JSON . stringify ({ prompt: 'Hello' })
})
const data = await response . json ()
return new Response ( JSON . stringify ( data ))
})
With Type Safety
Create a helper for type-safe environment variables:
// supabase/functions/_shared/env.ts
export function getEnv ( key : string ) : string {
const value = Deno . env . get ( key )
if ( ! value ) {
throw new Error ( `Environment variable ${ key } is not set` )
}
return value
}
export function getOptionalEnv ( key : string , defaultValue : string ) : string {
return Deno . env . get ( key ) ?? defaultValue
}
Use in your function:
import { getEnv , getOptionalEnv } from '../_shared/env.ts'
Deno . serve ( async ( req ) => {
// Will throw if not set
const apiKey = getEnv ( 'OPENAI_API_KEY' )
// Optional with default
const model = getOptionalEnv ( 'OPENAI_MODEL' , 'gpt-3.5-turbo' )
// Use variables
console . log ( 'Using model:' , model )
})
Local Development
Create Local Environment File
Create .env.local for local development:
# supabase/.env.local
OPENAI_API_KEY = sk-proj-xxx
STRIPE_SECRET = sk_test_xxx
RESEND_API_KEY = re_xxx
Add to .gitignore:
echo "supabase/.env.local" >> .gitignore
Use Local Environment Variables
Serve functions with local environment:
supabase functions serve --env-file ./supabase/.env.local
Or for all commands:
supabase start --env-file ./supabase/.env.local
Example Local Setup
Create a complete local environment:
# Copy example to actual file
cp supabase/.env.example supabase/.env.local
# Edit with your local secrets
vim supabase/.env.local
# Start services
supabase start
# Serve function with local env
supabase functions serve --env-file ./supabase/.env.local
Real-World Examples
OpenAI Integration
Securely use OpenAI API:
Deno . serve ( async ( req ) => {
const apiKey = Deno . env . get ( 'OPENAI_API_KEY' )
if ( ! apiKey ) {
return new Response (
JSON . stringify ({ error: 'OpenAI API key not configured' }),
{ status: 500 , headers: { 'Content-Type' : 'application/json' } }
)
}
const { prompt } = await req . json ()
const response = await fetch ( 'https://api.openai.com/v1/chat/completions' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ apiKey } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
model: 'gpt-4' ,
messages: [{ role: 'user' , content: prompt }]
})
})
const data = await response . json ()
return new Response ( JSON . stringify ( data ), {
headers: { 'Content-Type' : 'application/json' }
})
})
Set the secret:
supabase secrets set OPENAI_API_KEY=sk-proj-xxx
Stripe Webhooks
Verify Stripe webhook signatures:
import Stripe from 'https://esm.sh/stripe@14?target=denonext'
const stripe = new Stripe ( Deno . env . get ( 'STRIPE_API_KEY' ) as string , {
apiVersion: '2024-11-20'
})
const cryptoProvider = Stripe . createSubtleCryptoProvider ()
Deno . serve ( async ( request ) => {
const signature = request . headers . get ( 'Stripe-Signature' )
const body = await request . text ()
try {
const event = await stripe . webhooks . constructEventAsync (
body ,
signature ! ,
Deno . env . get ( 'STRIPE_WEBHOOK_SIGNING_SECRET' ) ! ,
undefined ,
cryptoProvider
)
console . log ( `Event received: ${ event . id } ` )
// Process event
return new Response ( JSON . stringify ({ ok: true }), { status: 200 })
} catch ( err ) {
console . error ( 'Webhook signature verification failed:' , err )
return new Response ( err . message , { status: 400 })
}
})
Set secrets:
supabase secrets set STRIPE_API_KEY=sk_test_xxx
supabase secrets set STRIPE_WEBHOOK_SIGNING_SECRET=whsec_xxx
Email with Resend
Send emails securely:
const RESEND_API_KEY = Deno . env . get ( 'RESEND_API_KEY' )
Deno . serve ( async ( req ) => {
if ( ! RESEND_API_KEY ) {
return new Response ( 'RESEND_API_KEY not configured' , { status: 500 })
}
const { to , subject , html } = await req . json ()
const response = await fetch ( 'https://api.resend.com/emails' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ RESEND_API_KEY } `
},
body: JSON . stringify ({
from: '[email protected] ' ,
to ,
subject ,
html
})
})
const data = await response . json ()
return new Response ( JSON . stringify ( data ), {
headers: { 'Content-Type' : 'application/json' }
})
})
Database Connection
Connect directly to PostgreSQL:
import { Pool } from 'https://deno.land/x/[email protected] /mod.ts'
const pool = new Pool (
{
tls: { enabled: false },
database: 'postgres' ,
hostname: Deno . env . get ( 'DB_HOSTNAME' ),
user: Deno . env . get ( 'DB_USER' ),
port: 6543 ,
password: Deno . env . get ( 'DB_PASSWORD' )
},
1
)
Deno . serve ( async ( _req ) => {
try {
const connection = await pool . connect ()
try {
const result = await connection . queryObject `SELECT * FROM animals`
const animals = result . rows
return new Response ( JSON . stringify ( animals , null , 2 ), {
headers: { 'Content-Type' : 'application/json' }
})
} finally {
connection . release ()
}
} catch ( err ) {
console . error ( err )
return new Response ( String ( err ?. message ?? err ), { status: 500 })
}
})
CI/CD Integration
GitHub Actions
Set secrets in GitHub Actions:
name : Deploy Functions
on :
push :
branches :
- main
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- uses : supabase/setup-cli@v1
with :
version : latest
- name : Set secrets
env :
SUPABASE_ACCESS_TOKEN : ${{ secrets.SUPABASE_ACCESS_TOKEN }}
run : |
supabase secrets set OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" --project-ref ${{ secrets.PROJECT_ID }}
supabase secrets set STRIPE_SECRET="${{ secrets.STRIPE_SECRET }}" --project-ref ${{ secrets.PROJECT_ID }}
- name : Deploy
env :
SUPABASE_ACCESS_TOKEN : ${{ secrets.SUPABASE_ACCESS_TOKEN }}
run : |
supabase functions deploy --project-ref ${{ secrets.PROJECT_ID }}
Configure GitHub secrets:
Go to Settings > Secrets and variables > Actions
Add each secret:
SUPABASE_ACCESS_TOKEN
PROJECT_ID
OPENAI_API_KEY
STRIPE_SECRET
Environment-Specific Secrets
Use different secrets for staging and production:
- name : Deploy to staging
if : github.ref == 'refs/heads/staging'
run : |
supabase secrets set OPENAI_API_KEY="${{ secrets.STAGING_OPENAI_KEY }}" --project-ref ${{ secrets.STAGING_PROJECT_ID }}
supabase functions deploy --project-ref ${{ secrets.STAGING_PROJECT_ID }}
- name : Deploy to production
if : github.ref == 'refs/heads/main'
run : |
supabase secrets set OPENAI_API_KEY="${{ secrets.PROD_OPENAI_KEY }}" --project-ref ${{ secrets.PROD_PROJECT_ID }}
supabase functions deploy --project-ref ${{ secrets.PROD_PROJECT_ID }}
Best Practices
Never Hardcode Secrets
// Don't do this!
const apiKey = 'sk-proj-1234567890abcdef'
fetch ( 'https://api.openai.com/v1/chat/completions' , {
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
})
Validate Environment Variables
Check variables at startup:
const REQUIRED_ENV_VARS = [
'OPENAI_API_KEY' ,
'STRIPE_SECRET' ,
'RESEND_API_KEY'
]
function validateEnv () {
const missing = REQUIRED_ENV_VARS . filter ( key => ! Deno . env . get ( key ))
if ( missing . length > 0 ) {
throw new Error (
`Missing required environment variables: ${ missing . join ( ', ' ) } `
)
}
}
validateEnv ()
Deno . serve ( async ( req ) => {
// All required variables are set
})
Use .env.example
Provide a template for developers:
# supabase/.env.example
OPENAI_API_KEY = sk-proj-your-key-here
STRIPE_SECRET = sk_test_your-secret-here
RESEND_API_KEY = re_your-key-here
Developers copy and fill in their values:
cp supabase/.env.example supabase/.env.local
# Edit .env.local with actual values
Rotate Secrets Regularly
Update secrets periodically:
# Generate new API key
# Update secret
supabase secrets set OPENAI_API_KEY=sk-proj-new-key
# Verify
supabase secrets list
Troubleshooting
Secret Not Available
If a secret isn’t available:
Check if set :
Set the secret :
supabase secrets set KEY=value
Redeploy the function :
supabase functions deploy function-name
Local vs Production Differences
If behavior differs between local and production:
Compare secrets :
# Local
cat supabase/.env.local
# Production
supabase secrets list
Ensure consistency between environments
Test with production values locally when debugging
Next Steps
Debugging Debug and monitor your Edge Functions
Deploy Functions Learn deployment strategies