supabase-troubleshooting

Troubleshoot common Supabase issues including auth errors, RLS policies, connection problems, and performance. Use when debugging Supabase issues, fixing errors, or optimizing performance.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "supabase-troubleshooting" with this command: npx skills add adaptationio/skrillz/adaptationio-skrillz-supabase-troubleshooting

Supabase Troubleshooting Skill

Debug and fix common Supabase issues.

Quick Diagnosis

SymptomLikely Cause
Empty data returnedRLS policies blocking access
"Not authorized"Missing or invalid auth token
"JWT expired"Token needs refresh
Slow queriesMissing indexes or RLS subqueries
Connection refusedWrong URL or service down
"Duplicate key"Unique constraint violation
Function timeoutCold start or heavy computation

Authentication Issues

"Invalid login credentials"

// Check email format
const { data, error } = await supabase.auth.signInWithPassword({
  email: email.toLowerCase().trim(),  // Normalize email
  password
})

// Check if user exists
const { data } = await supabase
  .from('auth.users')  // Admin only
  .select('*')
  .eq('email', email)

"Email not confirmed"

// Option 1: Resend confirmation
const { error } = await supabase.auth.resend({
  type: 'signup',
  email: 'user@example.com'
})

// Option 2: Admin confirm (server-side)
await supabaseAdmin.auth.admin.updateUserById(userId, {
  email_confirm: true
})

"JWT expired"

// Client auto-refreshes, but you can force it
const { data, error } = await supabase.auth.refreshSession()

// Listen for token refresh
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'TOKEN_REFRESHED') {
    // Update any cached tokens
  }
})

Session Not Persisting

// Check storage configuration
const supabase = createClient(url, key, {
  auth: {
    persistSession: true,  // Default is true
    storage: localStorage  // Or custom storage
  }
})

// Check for multiple instances
// Only create one Supabase client per app

Row Level Security (RLS) Issues

Empty Data Returned

-- Check if RLS is enabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';

-- Check existing policies
SELECT * FROM pg_policies
WHERE tablename = 'your_table';

-- Test policy with your user ID
SET request.jwt.claims = '{"sub": "your-user-uuid", "role": "authenticated"}';
SELECT * FROM your_table;

"violates row-level security policy"

// Check you're authenticated
const { data: { user } } = await supabase.auth.getUser()
console.log('Current user:', user?.id)

// Check the RLS policy requirements
// Common issue: auth.uid() doesn't match row's user_id

Debug RLS Policies

-- Enable detailed RLS debugging (temporary)
SET log_statement = 'all';
SET log_min_duration_statement = 0;

-- Test as specific user
SET request.jwt.claims = '{
  "sub": "user-uuid",
  "role": "authenticated",
  "aal": "aal1"
}';

-- Run your query
SELECT * FROM posts;

-- Check auth functions
SELECT auth.uid();
SELECT auth.role();
SELECT auth.jwt();

RLS Performance Issues

-- Bad: Calls auth.uid() for each row
CREATE POLICY "slow" ON posts
USING (auth.uid() = user_id);

-- Good: Caches auth.uid() result
CREATE POLICY "fast" ON posts
USING ((SELECT auth.uid()) = user_id);

-- Add indexes for RLS columns
CREATE INDEX idx_posts_user_id ON posts(user_id);

Database Issues

"duplicate key value violates unique constraint"

// Use upsert instead of insert
const { data, error } = await supabase
  .from('table')
  .upsert({ id: existingId, ...values }, {
    onConflict: 'id'
  })
  .select()

"foreign key violation"

// Ensure referenced row exists
const { data: parent } = await supabase
  .from('parent_table')
  .select('id')
  .eq('id', parentId)
  .single()

if (!parent) {
  // Create parent first or handle error
}

Query Returning Stale Data

// Disable cache for specific queries
const { data } = await supabase
  .from('table')
  .select('*')
  .eq('id', id)
  .single()
  .throwOnError()  // Actually throws on error

// Force fresh data
const { data } = await supabase
  .from('table')
  .select('*', { head: false, count: 'exact' })

Connection Issues

"Failed to fetch" / Network Error

// Check Supabase URL
console.log('URL:', process.env.NEXT_PUBLIC_SUPABASE_URL)

// Check API key
console.log('Key prefix:', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.slice(0, 20))

// Test connection
const { data, error } = await supabase.from('_health').select('*')
console.log('Health check:', { data, error })

Local Development Connection

# Check Docker is running
docker ps

# Check Supabase status
supabase status

# Restart if needed
supabase stop
supabase start

CORS Issues

// Edge function must include CORS headers
const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
}

// Always handle OPTIONS preflight
if (req.method === 'OPTIONS') {
  return new Response('ok', { headers: corsHeaders })
}

Storage Issues

Upload Failing

const { data, error } = await supabase.storage
  .from('bucket')
  .upload('path', file)

if (error) {
  // Check error type
  if (error.message.includes('exceeded')) {
    console.log('File too large')
  } else if (error.message.includes('mime type')) {
    console.log('Invalid file type')
  } else if (error.message.includes('row-level security')) {
    console.log('RLS policy blocking upload')
  }
}

Storage RLS Debug

-- Check storage policies
SELECT * FROM pg_policies
WHERE tablename = 'objects'
AND schemaname = 'storage';

-- Common fix: Allow authenticated uploads to user folder
CREATE POLICY "Upload to own folder"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
  bucket_id = 'uploads'
  AND auth.uid()::text = (storage.foldername(name))[1]
);

Edge Function Issues

Function Timeout (504)

// Check for blocking operations
// Bad: Long synchronous operation
const result = heavyComputation()

// Good: Keep operations async and fast
const result = await heavyComputationAsync()

// Check limits:
// - CPU time: 2 seconds
// - Wall clock: 150s (400s on Pro)

Function Error (500)

// Always wrap in try-catch
try {
  const result = await riskyOperation()
  return new Response(JSON.stringify({ data: result }))
} catch (error) {
  console.error('Function error:', error)
  return new Response(
    JSON.stringify({ error: error.message }),
    { status: 500 }
  )
}

Secrets Not Available

# Check secrets are set
supabase secrets list

# Set missing secrets
supabase secrets set API_KEY=value

# Note: Changes apply immediately, no redeploy needed

Realtime Issues

Not Receiving Events

// 1. Check table has realtime enabled
// SQL: ALTER PUBLICATION supabase_realtime ADD TABLE your_table;

// 2. Check subscription status
const channel = supabase
  .channel('test')
  .on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, callback)
  .subscribe((status, err) => {
    console.log('Subscription status:', status, err)
  })

// 3. Check RLS allows SELECT
// Realtime respects RLS policies

"Tried to subscribe multiple times"

// Don't call subscribe() twice on same channel
const channel = supabase.channel('my-channel')
channel.subscribe()  // OK
channel.subscribe()  // Error!

// Cleanup before resubscribing
await supabase.removeChannel(channel)
const newChannel = supabase.channel('my-channel').subscribe()

Too Many Connections

// Clean up channels on unmount
useEffect(() => {
  const channel = supabase.channel('data')
    .on('postgres_changes', {...}, callback)
    .subscribe()

  return () => {
    supabase.removeChannel(channel)  // Important!
  }
}, [])

Performance Optimization

Slow Queries

-- Use EXPLAIN ANALYZE to find bottlenecks
EXPLAIN ANALYZE SELECT * FROM posts WHERE user_id = 'uuid';

-- Add missing indexes
CREATE INDEX CONCURRENTLY idx_posts_user_id ON posts(user_id);

-- Check for sequential scans
SELECT relname, seq_scan, idx_scan
FROM pg_stat_user_tables
ORDER BY seq_scan DESC;

High Connection Count

# Check connections
supabase inspect db role-connections --linked

# Use connection pooling for serverless
# Use transaction mode, not session mode

Optimize RLS Queries

-- Avoid subqueries in hot path
-- Bad:
USING (user_id IN (SELECT user_id FROM team_members WHERE team_id = ...))

-- Good: Use security definer function
CREATE FUNCTION get_user_teams()
RETURNS SETOF uuid
LANGUAGE sql STABLE SECURITY DEFINER
AS $$ SELECT team_id FROM team_members WHERE user_id = auth.uid() $$;

USING (team_id IN (SELECT get_user_teams()))

Common Error Codes

PostgREST Errors (PGRST*)

CodeDescriptionSolution
PGRST116No rows found (single expected)Use maybeSingle() instead of single()
PGRST205Table not found in schema cacheCheck table name spelling, verify table exists
PGRST301Multiple rows (single expected)Add unique constraint or better filter

PostgreSQL Errors

CodeDescriptionSolution
22P02Invalid input syntax (e.g., bad UUID)Validate input format before query
23503Foreign key violationEnsure parent row exists first
23505Unique constraint violationUse upsert or check existence
42501Insufficient privilege (RLS)Fix RLS policies or use service role
42703Column does not existCheck column name spelling
42P01Relation (table) doesn't existCheck table name and schema
08P01Protocol violationCheck query syntax

Auth Errors

ErrorDescriptionSolution
Invalid API keyWrong or missing apikey headerCheck SUPABASE_ANON_KEY
JWT expiredAccess token expiredCall refreshSession()
Invalid login credentialsWrong email/passwordVerify credentials

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

finnhub-api

No summary provided by upstream source.

Repository SourceNeeds Review
General

auto-updater

No summary provided by upstream source.

Repository SourceNeeds Review
General

todo-management

No summary provided by upstream source.

Repository SourceNeeds Review