Realtime & Storage
Supabase Realtime broadcasts database changes over WebSockets. Storage provides S3-compatible file storage with RLS policies. Edge Functions run Deno-based serverless code close to users.
Realtime Subscriptions
// Subscribe to database changes (requires replication enabled on the table)
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*', // INSERT | UPDATE | DELETE | *
schema: 'public',
table: 'posts',
filter: `author_id=eq.${userId}`, // server-side filter
},
(payload) => {
switch (payload.eventType) {
case 'INSERT': addPost(payload.new as Post); break;
case 'UPDATE': updatePost(payload.new as Post); break;
case 'DELETE': removePost((payload.old as Post).id); break;
}
}
)
.subscribe((status, err) => {
if (status === 'SUBSCRIBED') console.log('Realtime connected');
if (status === 'CHANNEL_ERROR') console.error('Realtime error', err);
});
// Cleanup on unmount
return () => { supabase.removeChannel(channel); };
// Broadcast — send custom events to other clients in the same channel
const presenceChannel = supabase.channel('room:lobby');
presenceChannel
.on('broadcast', { event: 'cursor-move' }, ({ payload }) => {
updateCursor(payload.userId, payload.x, payload.y);
})
.subscribe();
// Send broadcast
await presenceChannel.send({
type: 'broadcast',
event: 'cursor-move',
payload: { userId: user.id, x: 100, y: 200 },
});Presence — Track Online Users
// Presence — know who is online in real time
const room = supabase.channel('room:project-1', {
config: { presence: { key: user.id } },
});
room
.on('presence', { event: 'sync' }, () => {
const state = room.presenceState<{ name: string; avatar: string }>();
const onlineUsers = Object.values(state).flat();
setOnlineUsers(onlineUsers);
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log('User joined:', key, newPresences);
})
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log('User left:', key, leftPresences);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await room.track({ name: user.name, avatar: user.avatar });
}
});
// Untrack when leaving
await room.untrack();Storage — File Uploads & Access
// Upload a file
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.webp`, file, {
contentType: 'image/webp',
upsert: true, // overwrite if exists
cacheControl: '3600', // Cache-Control max-age
});
// Get public URL (bucket must be set to public)
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(`${userId}/avatar.webp`);
// Signed URL (for private buckets, expires after N seconds)
const { data: { signedUrl } } = await supabase.storage
.from('private-docs')
.createSignedUrl(`${userId}/report.pdf`, 3600);
// List files in a folder
const { data: files } = await supabase.storage
.from('avatars')
.list(userId, { limit: 20, offset: 0, sortBy: { column: 'created_at', order: 'desc' } });
// Delete
await supabase.storage.from('avatars').remove([`${userId}/old-avatar.webp`]);
// Move / rename
await supabase.storage.from('avatars').move(`${userId}/temp.png`, `${userId}/avatar.png`);
// Storage RLS — in Supabase dashboard SQL editor
// CREATE POLICY "user_owns_avatar"
// ON storage.objects FOR ALL
// USING (auth.uid()::text = (storage.foldername(name))[1]);Edge Functions & Database Functions
// supabase/functions/send-email/index.ts — Deno edge function
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
serve(async (req: Request) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! // service role bypasses RLS
);
const { to, subject, body } = await req.json();
// ... call Resend, SendGrid, etc.
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' },
});
});
// Deploy
// npx supabase functions deploy send-email
// Call from client
const { data, error } = await supabase.functions.invoke('send-email', {
body: { to: 'alice@example.com', subject: 'Welcome!', body: 'Hello' },
});
// Database function + trigger — auto-create profile on signup
-- CREATE FUNCTION public.handle_new_user()
-- RETURNS trigger AS $$
-- BEGIN
-- INSERT INTO public.profiles (id, email, created_at)
-- VALUES (new.id, new.email, now());
-- RETURN new;
-- END;
-- $$ LANGUAGE plpgsql SECURITY DEFINER;
--
-- CREATE TRIGGER on_auth_user_created
-- AFTER INSERT ON auth.users
-- FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free