Execute Supabase secondary workflow: Auth + Storage + Realtime. Use when implementing secondary use case, or complementing primary workflow. Trigger with phrases like "supabase auth storage...
Implement the three pillars that turn a Supabase database into a full application backend: user authentication (email/password, OAuth, magic links, session lifecycle), file storage (uploads, downloads, signed URLs, bucket-level RLS policies), and real-time subscriptions (Postgres change events, client-to-client broadcast, presence tracking). Every operation integrates with Row-Level Security through auth.uid().
Each pillar below carries a lean skeleton in this file; the full, copy-paste walkthroughs live in references/ so this file stays scannable.
@supabase/supabase-js v2 installed (npm install @supabase/supabase-js)SUPABASE_URL and SUPABASE_ANON_KEY available from project Settings > APIpip install supabase (wraps postgrest-py, gotrue-py, storage3, realtime-py)Read the file (Read), edit or create the client and route/component code (Write, Edit), and grep the project (Grep) to reuse an existing Supabase client before creating a new one. Use Bash(npm:*) to install the SDK and Bash(supabase:*) to run migrations/policies.
Initialize the client once, then wire the flows your app needs. The skeleton:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!)
// Email/password
await supabase.auth.signUp({ email, password })
await supabase.auth.signInWithPassword({ email, password })
// OAuth โ redirect the user to data.url
const { data } = await supabase.auth.signInWithOAuth({ provider: 'google' })
// React to session changes (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED)
supabase.auth.onAuthStateChange((event, session) => { /* update UI */ })
Full auth walkthrough โ OAuth callback, magic link, session lifecycle, password reset: references/auth.md. Python: references/python-examples.md.
Public buckets serve via CDN URLs; private buckets require signed URLs. The skeleton:
// Upload to the signed-in user's own folder (RLS enforces ownership)
await supabase.storage.from('avatars').upload(`${userId}/avatar.png`, file, { upsert: true })
// Public URL (public bucket) vs. time-limited signed URL (private bucket)
supabase.storage.from('avatars').getPublicUrl(`${userId}/avatar.png`)
await supabase.storage.from('documents').createSignedUrl('reports/q4.pdf', 3600)
Full storage walkthrough โ download, list, delete, and the bucket RLS policies that enforce per-user access: references/storage.md. Python: references/python-examples.md.
Three channel types: database change listeners, client-to-client broadcast, and presence. The skeleton:
const channel = supabase
.channel('chat-room')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => console.log('New:', payload.new))
.subscribe()
// One-time table setup: ALTER PUBLICATION supabase_realtime ADD TABLE messages;
supabase.removeChannel(channel) // clean up
Full realtime walkthrough โ UPDATE/DELETE filters, RLS-scoped subscriptions, broadcast, and presence tracking: references/realtime.md. Python: references/python-examples.md.
onAuthStateChange, password resetauth.uid() and storage.foldername()| Error | Cause | Solution |
|---|---|---|
AuthApiError: User already registered |
Duplicate email signup | Use signInWithPassword or check existence first |
AuthApiError: Invalid login credentials |
Wrong email or password | Verify credentials; check email confirmation status |
AuthApiError: Email not confirmed |
User has not clicked confirmation link | Resend with resend({ type: 'signup', email }) |
StorageApiError: Bucket not found |
Bucket does not exist | Create via dashboard or INSERT INTO storage.buckets |
StorageApiError: new row violates row-level security |
RLS policy blocking the operation | Verify storage.objects policies match the user and bucket |
StorageApiError: The resource already exists |
File exists and upsert: false |
Set upsert: true to overwrite or use a unique path |
Realtime: channel error or TIMED_OUT |
Network issues or Realtime not enabled | Check ALTER PUBLICATION supabase_realtime ADD TABLE for the target table |
Realtime: too many channels |
Exceeded concurrent channel limit | Unsubscribe unused channels with removeChannel() |
The end-to-end flow โ sign in, upload an avatar to the user's RLS-guarded folder, resolve its public URL, and subscribe to live profile updates โ is in references/examples.md. Each step composes the three skeletons above with no new API surface.
For common Supabase errors and debugging, see supabase-common-errors.
For database queries and CRUD operations, see supabase-crud-core.