# Supabase JavaScript SDK The Supabase JavaScript SDK is a comprehensive isomorphic client library for interacting with Supabase services. It provides a unified interface to Supabase's Backend-as-a-Service platform, enabling developers to build applications with authentication, real-time PostgreSQL database operations, file storage, vector embeddings, and serverless edge functions. The SDK works across Node.js (v20+), Deno, modern browsers, React Native, Bun, and Cloudflare Workers, making it suitable for both client-side and server-side applications. The monorepo contains six core packages: `@supabase/supabase-js` (the main unified SDK), `@supabase/auth-js` (authentication), `@supabase/postgrest-js` (database queries via PostgREST), `@supabase/realtime-js` (WebSocket-based real-time subscriptions), `@supabase/storage-js` (file and vector storage), and `@supabase/functions-js` (edge function invocation). Each package can be used independently or through the main supabase-js client which combines all functionality into a single cohesive API. ## Creating a Supabase Client Initialize the main Supabase client to access all services including auth, database, storage, realtime, and functions through a single entry point. ```typescript import { createClient } from '@supabase/supabase-js' // Basic initialization const supabase = createClient( 'https://xyzcompany.supabase.co', 'public-anon-key' ) // With full configuration options const supabase = createClient( 'https://xyzcompany.supabase.co', 'public-anon-key', { db: { schema: 'public', timeout: 30000, // 30 second timeout for database operations }, auth: { autoRefreshToken: true, persistSession: true, detectSessionInUrl: true, storage: localStorage, // or AsyncStorage for React Native storageKey: 'supabase-auth', }, realtime: { params: { eventsPerSecond: 10, }, }, global: { headers: { 'x-custom-header': 'my-app' }, fetch: customFetch, // Custom fetch implementation for Cloudflare Workers }, } ) // Access individual services const { data: user } = await supabase.auth.getUser() const { data: rows } = await supabase.from('users').select('*') const { data: files } = await supabase.storage.from('avatars').list() const { data: result } = await supabase.functions.invoke('hello') ``` ## Authentication - Sign Up Create new user accounts with email/password, phone, or third-party OAuth providers. Supports email confirmation, phone OTP verification, and metadata storage. ```typescript // Email/password signup const { data, error } = await supabase.auth.signUp({ email: 'user@example.com', password: 'securepassword123', options: { data: { first_name: 'John', last_name: 'Doe', avatar_url: 'https://example.com/avatar.png', }, emailRedirectTo: 'https://myapp.com/welcome', }, }) if (error) { console.error('Signup error:', error.message) } else { console.log('User created:', data.user?.id) console.log('Session:', data.session?.access_token) // Note: User may need to confirm email before session is active } // Phone signup with OTP const { data, error } = await supabase.auth.signUp({ phone: '+1234567890', password: 'securepassword123', }) // Verify phone OTP const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({ phone: '+1234567890', token: '123456', type: 'sms', }) ``` ## Authentication - Sign In Authenticate users with various methods including email/password, magic links, OAuth providers, and single sign-on (SSO). ```typescript // Email/password sign in const { data, error } = await supabase.auth.signInWithPassword({ email: 'user@example.com', password: 'securepassword123', }) if (data.session) { console.log('Logged in! Access token:', data.session.access_token) console.log('User:', data.user?.email) } // Magic link (passwordless) sign in const { error } = await supabase.auth.signInWithOtp({ email: 'user@example.com', options: { emailRedirectTo: 'https://myapp.com/dashboard', }, }) // OAuth sign in (Google, GitHub, etc.) const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: 'https://myapp.com/auth/callback', scopes: 'email profile', queryParams: { access_type: 'offline', prompt: 'consent', }, }, }) // SSO sign in with SAML const { data, error } = await supabase.auth.signInWithSSO({ domain: 'company.com', options: { redirectTo: 'https://myapp.com/dashboard', }, }) // Anonymous sign in const { data, error } = await supabase.auth.signInAnonymously({ options: { data: { guest_preference: 'dark_mode' }, }, }) ``` ## Authentication - Session Management Manage user sessions including retrieval, refresh, and sign out operations with proper security handling. ```typescript // Get current session const { data: { session }, error } = await supabase.auth.getSession() if (session) { console.log('Access token:', session.access_token) console.log('Refresh token:', session.refresh_token) console.log('Expires at:', new Date(session.expires_at! * 1000)) } // Get current user (validates JWT with server) const { data: { user }, error } = await supabase.auth.getUser() if (user) { console.log('User ID:', user.id) console.log('Email:', user.email) console.log('Metadata:', user.user_metadata) } // Listen to auth state changes const { data: { subscription } } = supabase.auth.onAuthStateChange( (event, session) => { console.log('Auth event:', event) // SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, etc. if (event === 'SIGNED_IN') { console.log('User signed in:', session?.user.email) } else if (event === 'SIGNED_OUT') { console.log('User signed out') } else if (event === 'TOKEN_REFRESHED') { console.log('Token refreshed') } } ) // Update user attributes const { data, error } = await supabase.auth.updateUser({ email: 'newemail@example.com', password: 'newpassword123', data: { display_name: 'John Updated' }, }) // Sign out const { error } = await supabase.auth.signOut() // Sign out from all devices const { error } = await supabase.auth.signOut({ scope: 'global' }) // Cleanup subscription when done subscription.unsubscribe() ``` ## Authentication - Multi-Factor Authentication (MFA) Enable two-factor authentication using TOTP (authenticator apps), phone SMS, or WebAuthn for enhanced security. ```typescript // Enroll TOTP factor const { data, error } = await supabase.auth.mfa.enroll({ factorType: 'totp', friendlyName: 'My Authenticator App', }) if (data) { console.log('Scan QR code:', data.totp.qr_code) // Display to user console.log('Or enter secret:', data.totp.secret) console.log('Factor ID:', data.id) } // Challenge and verify MFA const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({ factorId: 'factor-uuid-here', }) const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({ factorId: 'factor-uuid-here', challengeId: challengeData.id, code: '123456', // From authenticator app }) // Combined challenge and verify const { data, error } = await supabase.auth.mfa.challengeAndVerify({ factorId: 'factor-uuid-here', code: '123456', }) // List enrolled factors const { data: factors, error } = await supabase.auth.mfa.listFactors() console.log('TOTP factors:', factors?.totp) console.log('Phone factors:', factors?.phone) // Get current assurance level const { data: aal, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel() console.log('Current level:', aal?.currentLevel) // 'aal1' or 'aal2' console.log('Next level:', aal?.nextLevel) // Unenroll a factor const { error } = await supabase.auth.mfa.unenroll({ factorId: 'factor-uuid-here' }) ``` ## Database - Select Queries Query data from PostgreSQL tables with filtering, sorting, pagination, and relationship loading through PostgREST. ```typescript // Basic select all columns const { data, error } = await supabase .from('users') .select('*') // Select specific columns const { data, error } = await supabase .from('users') .select('id, email, created_at') // Select with relationships (foreign key joins) const { data, error } = await supabase .from('posts') .select(` id, title, content, author:users(id, name, email), comments(id, body, created_at) `) // Filtering with operators const { data, error } = await supabase .from('products') .select('*') .eq('category', 'electronics') // equals .neq('status', 'discontinued') // not equals .gt('price', 100) // greater than .lte('stock', 50) // less than or equal .like('name', '%phone%') // pattern matching .ilike('description', '%SALE%') // case-insensitive pattern .in('brand', ['Apple', 'Samsung']) // in array .is('deleted_at', null) // is null .contains('tags', ['featured']) // array contains .range('price', 10, 100) // between range // Ordering and pagination const { data, error, count } = await supabase .from('posts') .select('*', { count: 'exact' }) .order('created_at', { ascending: false }) .range(0, 9) // First 10 records (0-indexed) console.log('Total count:', count) console.log('Page data:', data) // Get single record const { data, error } = await supabase .from('users') .select('*') .eq('id', 'user-uuid') .single() // Maybe single (returns null if not found, no error) const { data, error } = await supabase .from('users') .select('*') .eq('email', 'maybe@exists.com') .maybeSingle() ``` ## Database - Insert, Update, Delete Perform data mutations with conflict handling, returning updated data, and batch operations. ```typescript // Insert single record const { data, error } = await supabase .from('users') .insert({ email: 'new@example.com', name: 'New User', metadata: { signup_source: 'web' }, }) .select() .single() console.log('Created user:', data) // Insert multiple records const { data, error } = await supabase .from('products') .insert([ { name: 'Product A', price: 29.99, category: 'electronics' }, { name: 'Product B', price: 49.99, category: 'electronics' }, { name: 'Product C', price: 19.99, category: 'accessories' }, ]) .select() // Upsert (insert or update on conflict) const { data, error } = await supabase .from('settings') .upsert( { user_id: 'user-uuid', theme: 'dark', language: 'en' }, { onConflict: 'user_id' } ) .select() .single() // Update records const { data, error } = await supabase .from('posts') .update({ title: 'Updated Title', updated_at: new Date().toISOString(), }) .eq('id', 'post-uuid') .select() .single() // Update multiple records const { data, error } = await supabase .from('products') .update({ on_sale: true, discount: 0.2 }) .eq('category', 'clearance') .select() // Delete records const { error } = await supabase .from('comments') .delete() .eq('id', 'comment-uuid') // Delete with returning deleted data const { data: deleted, error } = await supabase .from('temp_records') .delete() .lt('created_at', '2024-01-01') .select() console.log('Deleted records:', deleted?.length) ``` ## Database - RPC (Stored Procedures) Call PostgreSQL functions and stored procedures with arguments, filters, and various return types. ```typescript // Call function without arguments const { data, error } = await supabase.rpc('get_server_time') console.log('Server time:', data) // Call function with arguments const { data, error } = await supabase.rpc('calculate_order_total', { order_id: 'order-uuid', include_tax: true, }) console.log('Order total:', data) // Call function returning table (set-returning function) const { data, error } = await supabase .rpc('search_products', { search_term: 'laptop' }) .eq('in_stock', true) .order('relevance', { ascending: false }) .limit(10) // Bulk processing with array arguments const { data, error } = await supabase.rpc('process_batch', { ids: ['id1', 'id2', 'id3'], operation: 'archive', }) // Read-only function call (uses GET instead of POST) const { data, error } = await supabase.rpc( 'get_dashboard_stats', { user_id: 'user-uuid' }, { get: true } ) // Get count only (HEAD request) const { count, error } = await supabase .rpc('list_active_users', {}, { head: true, count: 'exact' }) console.log('Active users count:', count) ``` ## Realtime - Database Changes (Postgres CDC) Subscribe to real-time database changes using Postgres Change Data Capture for INSERT, UPDATE, and DELETE events. ```typescript // Subscribe to all changes in a table const channel = supabase .channel('db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'messages' }, (payload) => { console.log('Change type:', payload.eventType) console.log('New data:', payload.new) console.log('Old data:', payload.old) } ) .subscribe((status) => { if (status === 'SUBSCRIBED') { console.log('Listening for database changes!') } }) // Subscribe to specific events with filters const channel = supabase .channel('user-posts') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts', filter: 'user_id=eq.user-uuid', }, (payload) => { console.log('New post:', payload.new) // Update UI with new post } ) .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'posts', filter: 'user_id=eq.user-uuid', }, (payload) => { console.log('Post updated:', payload.new) } ) .on( 'postgres_changes', { event: 'DELETE', schema: 'public', table: 'posts' }, (payload) => { console.log('Post deleted, ID:', payload.old.id) } ) .subscribe() // Cleanup when done await supabase.removeChannel(channel) ``` ## Realtime - Broadcast Send ephemeral messages between clients in real-time for features like cursor sharing, typing indicators, and live notifications. ```typescript // Create broadcast channel const channel = supabase.channel('room-1', { config: { broadcast: { ack: true, // Wait for server acknowledgment self: false, // Don't receive own messages }, }, }) // Listen for broadcast messages channel.on('broadcast', { event: 'cursor-move' }, (payload) => { console.log('User moved cursor:', payload.payload) // { userId: 'user-1', x: 150, y: 200 } }) channel.on('broadcast', { event: 'typing' }, (payload) => { console.log('User is typing:', payload.payload.userId) }) // Subscribe and send messages channel.subscribe(async (status) => { if (status === 'SUBSCRIBED') { // Send cursor position const ack = await channel.send({ type: 'broadcast', event: 'cursor-move', payload: { userId: 'current-user', x: 150, y: 200 }, }) console.log('Message acknowledged:', ack) // Send typing indicator await channel.send({ type: 'broadcast', event: 'typing', payload: { userId: 'current-user', typing: true }, }) } }) // Broadcast replay for private channels (get historical messages) const replayChannel = supabase.channel('private-room', { config: { private: true, broadcast: { replay: { since: Date.now() - 12 * 60 * 60 * 1000, // Last 12 hours limit: 25, }, }, }, }) replayChannel .on('broadcast', { event: 'message' }, (payload) => { if (payload.meta?.replayed) { console.log('Historical message:', payload.payload) } else { console.log('New message:', payload.payload) } }) .subscribe() ``` ## Realtime - Presence Track and synchronize online user state across clients with automatic join/leave detection and custom presence data. ```typescript // Create presence channel const channel = supabase.channel('online-users', { config: { presence: { key: 'user-uuid', // Unique key for this client }, }, }) // Handle presence sync channel.on('presence', { event: 'sync' }, () => { const state = channel.presenceState() console.log('All online users:', state) // { 'user-1': [{ status: 'online', ... }], 'user-2': [{ status: 'away', ... }] } const onlineUsers = Object.keys(state).length console.log('Online count:', onlineUsers) }) // Handle user join channel.on('presence', { event: 'join' }, ({ key, newPresences }) => { console.log('User joined:', key) console.log('Their state:', newPresences) }) // Handle user leave channel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => { console.log('User left:', key) }) // Subscribe and track presence channel.subscribe(async (status) => { if (status === 'SUBSCRIBED') { // Track this user's presence const trackStatus = await channel.track({ userId: 'current-user', status: 'online', lastSeen: new Date().toISOString(), currentPage: '/dashboard', }) console.log('Track status:', trackStatus) } }) // Update presence state await channel.track({ userId: 'current-user', status: 'away', lastSeen: new Date().toISOString(), }) // Remove presence (go offline) await channel.untrack() // Cleanup await supabase.removeChannel(channel) ``` ## Storage - File Operations Upload, download, list, move, and delete files with support for public/private buckets and signed URLs. ```typescript // Upload file const file = document.querySelector('input[type="file"]').files[0] const { data, error } = await supabase.storage .from('avatars') .upload(`users/${userId}/avatar.png`, file, { cacheControl: '3600', contentType: 'image/png', upsert: true, // Overwrite if exists }) console.log('Uploaded to:', data?.path) // Upload from Blob/ArrayBuffer const imageBlob = new Blob([arrayBuffer], { type: 'image/jpeg' }) const { data, error } = await supabase.storage .from('images') .upload('photos/sunset.jpg', imageBlob) // Download file const { data, error } = await supabase.storage .from('documents') .download('reports/annual-2024.pdf') if (data) { const url = URL.createObjectURL(data) // Use url for display or download } // Get public URL (for public buckets) const { data } = supabase.storage .from('public-assets') .getPublicUrl('images/logo.png', { transform: { width: 200, height: 200, resize: 'cover', }, }) console.log('Public URL:', data.publicUrl) // Create signed URL (for private buckets) const { data, error } = await supabase.storage .from('private-docs') .createSignedUrl('contracts/agreement.pdf', 3600) // 1 hour expiry console.log('Signed URL:', data?.signedUrl) // List files in a folder const { data, error } = await supabase.storage .from('uploads') .list('users/123', { limit: 100, offset: 0, sortBy: { column: 'created_at', order: 'desc' }, }) // Move file const { error } = await supabase.storage .from('uploads') .move('temp/file.pdf', 'permanent/file.pdf') // Delete files const { error } = await supabase.storage .from('uploads') .remove(['temp/file1.pdf', 'temp/file2.pdf']) ``` ## Storage - Bucket Management Create, configure, and manage storage buckets with public/private access control and pagination. ```typescript // Create a new bucket const { data, error } = await supabase.storage.createBucket('documents', { public: false, fileSizeLimit: 10485760, // 10MB allowedMimeTypes: ['application/pdf', 'image/*'], }) // Get bucket details const { data, error } = await supabase.storage.getBucket('documents') console.log('Bucket:', data?.name) console.log('Public:', data?.public) // List all buckets with pagination const { data, error } = await supabase.storage.listBuckets({ limit: 10, offset: 0, sortColumn: 'created_at', sortOrder: 'desc', search: 'prod', }) // Update bucket settings const { data, error } = await supabase.storage.updateBucket('documents', { public: true, fileSizeLimit: 52428800, // 50MB }) // Empty a bucket (delete all files) const { error } = await supabase.storage.emptyBucket('temp-uploads') // Delete a bucket (must be empty) const { error } = await supabase.storage.deleteBucket('old-bucket') ``` ## Storage - Vector Embeddings Store and query high-dimensional vector embeddings for semantic search, AI recommendations, and similarity matching. ```typescript // Create vector bucket const { data, error } = await supabase.storage.vectors.createBucket('embeddings') // Create vector index const bucket = supabase.storage.vectors.from('embeddings') const { error } = await bucket.createIndex({ indexName: 'documents', dataType: 'float32', dimension: 1536, // OpenAI embedding dimension distanceMetric: 'cosine', // 'cosine' | 'euclidean' | 'dotproduct' metadataConfiguration: { nonFilterableMetadataKeys: ['raw_text'], // Exclude from filtering }, }) // Insert vectors const index = bucket.index('documents') const { error } = await index.putVectors({ vectors: [ { key: 'doc-1', data: { float32: embedding1 }, // 1536-dimensional array metadata: { title: 'Introduction', category: 'docs', page: 1 }, }, { key: 'doc-2', data: { float32: embedding2 }, metadata: { title: 'Getting Started', category: 'docs', page: 2 }, }, ], }) // Query similar vectors (semantic search) const { data, error } = await index.queryVectors({ queryVector: { float32: queryEmbedding }, topK: 10, filter: { category: 'docs' }, returnDistance: true, returnMetadata: true, }) if (data) { data.matches.forEach((match) => { console.log(`${match.key}: distance=${match.distance}`) console.log('Metadata:', match.metadata) }) } // Get vectors by key const { data, error } = await index.getVectors({ keys: ['doc-1', 'doc-2'], returnData: true, returnMetadata: true, }) // List/scan all vectors const { data, error } = await index.listVectors({ maxResults: 500, returnMetadata: true, }) // Delete vectors await index.deleteVectors({ keys: ['doc-1', 'doc-2'] }) // Delete index and bucket await bucket.deleteIndex('documents') await supabase.storage.vectors.deleteBucket('embeddings') ``` ## Edge Functions Invoke Supabase Edge Functions (Deno-based serverless functions) with various request/response types and error handling. ```typescript // Basic function invocation const { data, error } = await supabase.functions.invoke('hello-world', { body: { name: 'John' }, }) console.log('Response:', data) // { message: 'Hello John!' } // With custom headers and response type const { data, error } = await supabase.functions.invoke('generate-pdf', { body: { template: 'invoice', orderId: '12345' }, headers: { 'x-custom-header': 'custom-value', }, }) // Stream response const { data, error } = await supabase.functions.invoke('stream-chat', { body: { message: 'Tell me a story' }, }) if (data instanceof ReadableStream) { const reader = data.getReader() const decoder = new TextDecoder() while (true) { const { done, value } = await reader.read() if (done) break console.log('Chunk:', decoder.decode(value)) } } // Invoke with specific region const { data, error } = await supabase.functions.invoke('process-data', { body: { dataId: 'xyz' }, region: 'us-east-1', }) // Handle different response types const { data, error } = await supabase.functions.invoke('get-image', { body: { imageId: '123' }, }) if (data instanceof Blob) { const imageUrl = URL.createObjectURL(data) // Use imageUrl in element } // Error handling const { data, error } = await supabase.functions.invoke('protected-function', { body: { action: 'secret' }, }) if (error) { if (error instanceof FunctionsHttpError) { console.log('Function returned an error:', error.message) } else if (error instanceof FunctionsRelayError) { console.log('Relay error:', error.message) } else if (error instanceof FunctionsFetchError) { console.log('Network error:', error.message) } } ``` ## TypeScript Support with Generated Types Leverage TypeScript for full type safety by generating types from your database schema. ```typescript // Generate types using Supabase CLI: // npx supabase gen types typescript --project-id your-project-id > database.types.ts import { createClient } from '@supabase/supabase-js' import { Database } from './database.types' // Create typed client const supabase = createClient( 'https://xyzcompany.supabase.co', 'public-anon-key' ) // Full type inference for queries const { data: users } = await supabase .from('users') .select('id, email, profile:profiles(avatar_url, bio)') .eq('status', 'active') // TypeScript knows: users is { id: string, email: string, profile: { avatar_url: string, bio: string } }[] // Type-safe inserts const { data, error } = await supabase .from('posts') .insert({ title: 'My Post', content: 'Content here', user_id: 'user-uuid', // TypeScript error if you add invalid fields or wrong types }) .select() .single() // Type-safe RPC calls const { data } = await supabase.rpc('search_posts', { search_term: 'typescript', limit_count: 10, }) // Access different schemas const { data } = await supabase .schema('analytics') .from('events') .select('*') // Override types when needed const { data } = await supabase .from('complex_view') .select('*') .overrideTypes<{ custom_field: string; computed: number }[]>() ``` ## Server-Side Usage and Admin Operations Use the SDK in server-side contexts with service role keys for admin operations that bypass Row Level Security. ```typescript import { createClient } from '@supabase/supabase-js' // Server-side client with service role (bypasses RLS) const supabaseAdmin = createClient( 'https://xyzcompany.supabase.co', 'service-role-secret-key', { auth: { autoRefreshToken: false, persistSession: false, }, } ) // Admin auth operations const { data, error } = await supabaseAdmin.auth.admin.listUsers({ page: 1, perPage: 100, }) // Create user as admin const { data: newUser, error } = await supabaseAdmin.auth.admin.createUser({ email: 'user@example.com', password: 'securepassword', email_confirm: true, user_metadata: { role: 'customer' }, }) // Update user as admin const { data, error } = await supabaseAdmin.auth.admin.updateUserById( 'user-uuid', { email: 'newemail@example.com', user_metadata: { verified: true }, ban_duration: 'none', // Unban user } ) // Delete user const { error } = await supabaseAdmin.auth.admin.deleteUser('user-uuid') // Database operations bypassing RLS const { data, error } = await supabaseAdmin .from('internal_logs') .insert({ action: 'system_backup', timestamp: new Date() }) // Custom access token for server components const supabaseWithToken = createClient( 'https://xyzcompany.supabase.co', 'public-anon-key', { global: { headers: { Authorization: `Bearer ${customJwt}`, }, }, } ) ``` The Supabase JavaScript SDK provides a complete toolkit for building modern applications with real-time database subscriptions, secure authentication flows, file storage with CDN delivery, vector search capabilities, and serverless function execution. The unified API design makes it straightforward to combine these features - for example, authenticating users, storing their files, listening for real-time updates on their data, and invoking edge functions all through a single client instance. Common integration patterns include using auth state changes to update realtime channel subscriptions, combining storage uploads with database metadata records, and using vector embeddings with PostgreSQL full-text search for hybrid search implementations. For production deployments, the SDK supports Row Level Security (RLS) policies for fine-grained access control, automatic token refresh for long-running sessions, and optimistic UI updates with realtime confirmations. The TypeScript-first design with generated database types ensures type safety across the entire data layer, catching errors at compile time rather than runtime. Whether building a real-time collaborative app, a content management system, or an AI-powered search application, the Supabase JS SDK provides the foundational building blocks with consistent patterns and comprehensive error handling.