Skip to content

Supabase Realtime Subscriptions — Live Data Updates with WebSockets

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Supabase Realtime Subscriptions. We cover key concepts, practical examples, and best practices to help you master this topic.

Supabase Realtime uses Websocket connections to listen for PostgreSQL database changes, enabling live updates for collaborative apps, dashboards, and notification systems without polling.

What You'll Learn

By the end of this lesson you will enable realtime on a table, subscribe to INSERT, UPDATE, and DELETE changes, filter events by column values, and use broadcast and presence features.

Why It Matters

Realtime updates transform static web applications into responsive, collaborative experiences. Users see changes instantly as other users or backend processes modify the database.

Real-World Use

DodaZIP uses Supabase Realtime to show file compression progress. When a backend worker updates the processing status in the database, the user's browser receives the update within milliseconds via WebSocket.

flowchart LR
    A[User A Browser] -->|Subscribe| R[Realtime Engine]
    B[Backend Worker] -->|UPDATE row| DB[(PostgreSQL)]
    DB -->|CDC| R
    R -->|WebSocket push| A
    R -->|WebSocket push| C[User B Browser]
    style R fill:#3ecf8e,color:#fff

Enabling Realtime

Realtime must be enabled per table in the Supabase dashboard or via SQL.

-- Enable realtime for a table via SQL
-- This runs on the Supabase database
ALTER PUBLICATION supabase_realtime ADD TABLE processing_jobs;

-- You can also enable in the dashboard:
-- Database > Replication > select table > toggle realtime

-- Verify enabled tables
SELECT * FROM pg_publication_tables 
WHERE pubname = 'supabase_realtime';
# enable_realtime.py
# Understanding realtime enablement

def realtime_checklist():
    items = [
        "Enable realtime for table in Dashboard > Database > Replication",
        "OR run: ALTER PUBLICATION supabase_realtime ADD TABLE table_name",
        "Enable RLS on the table (realtime respects RLS)",
        "Create appropriate RLS policies for realtime access",
        "Subscribe from client using the Supabase SDK",
    ]
    
    print("Realtime Enablement Checklist:")
    for item in items:
        print(f"  [ ] {item}")

realtime_checklist()

Subscribing to Changes

Subscribe to database changes from the client SDK.

# subscribe_changes.py
# Subscribe to database changes

import os
from supabase import create_client, Client

url = os.getenv("SUPABASE_URL", "https://example.supabase.co")
key = os.getenv("SUPABASE_ANON_KEY", "your-anon-key")
supabase: Client = create_client(url, key)

def subscribe_to_changes():
    channel = supabase.channel("job-updates")
    
    def handle_insert(payload):
        print(f"New job created:")
        print(f"  Record: {payload['new']}")
    
    def handle_update(payload):
        print(f"Job updated:")
        print(f"  Old: {payload['old']}")
        print(f"  New: {payload['new']}")
    
    def handle_delete(payload):
        print(f"Job deleted:")
        print(f"  Old: {payload['old']}")
    
    channel.on(
        "postgres_changes",
        {
            "event": "*",
            "schema": "public",
            "table": "processing_jobs",
        },
        lambda payload: print(f"Change: {payload}")
    ).subscribe()
    
    print("Subscribed to processing_jobs changes")
    print("Listening for INSERT, UPDATE, DELETE events...")
    
    return channel

subscribe_to_changes()

Filtering Events

Listen for specific events or rows that match conditions.

# filtered_subscription.py
# Subscribe with filters

def subscribe_filtered():
    supabase = create_client(
        os.getenv("SUPABASE_URL", "https://example.supabase.co"),
        os.getenv("SUPABASE_ANON_KEY", "your-anon-key")
    )
    
    # Subscribe only to INSERT events
    channel_insert = supabase.channel("new-jobs")
    channel_insert.on(
        "postgres_changes",
        {"event": "INSERT", "schema": "public", "table": "processing_jobs"},
        lambda p: print(f"New job: {p['new']}")
    ).subscribe()
    
    # Subscribe with column filter (using PostgreSQL filter)
    # Only receive changes where status = 'completed'
    channel_completed = supabase.channel("completed-jobs")
    channel_completed.on(
        "postgres_changes",
        {
            "event": "UPDATE",
            "schema": "public",
            "table": "processing_jobs",
            "filter": "status=eq.completed"
        },
        lambda p: print(f"Job completed: {p['new']}")
    ).subscribe()
    
    print("Filtered subscriptions active:")
    print("  1. All INSERT events on processing_jobs")
    print("  2. UPDATE events where status = 'completed'")

subscribe_filtered()

Broadcast and Presence

Send custom messages and track user presence.

# broadcast_presence.py
# Broadcast and presence features

def broadcast_example():
    supabase = create_client(
        os.getenv("SUPABASE_URL", "https://example.supabase.co"),
        os.getenv("SUPABASE_ANON_KEY", "your-anon-key")
    )
    
    # Broadcast a custom message to all channel subscribers
    channel = supabase.channel("chat-room")
    
    def handle_broadcast(payload):
        print(f"Broadcast received:")
        print(f"  Event: {payload['event']}")
        print(f"  Data: {payload}")

    channel.on(
        "broadcast",
        {"event": "cursor-position"},
        handle_broadcast
    ).subscribe()
    
    # Send a broadcast
    channel.send_broadcast(
        "cursor-position",
        {"x": 150, "y": 300, "user": "alice"}
    )
    
    print("Broadcast sent: cursor-position { x: 150, y: 300 }")
    print()
    
    # Presence tracking
    channel.track({"user_id": "alice", "status": "online"})
    print("Presence tracked: alice is online")

broadcast_example()

Common Mistakes

  1. Not enabling the table for realtime: Subscribing to a table that is not enabled for realtime silently returns no events.

  2. RLS blocking realtime events: Realtime respects RLS. If the user cannot SELECT a row, they will not receive realtime updates for it.

  3. Not cleaning up subscriptions: Forgetting to unsubscribe when leaving a page causes memory leaks and unnecessary WebSocket connections.

  4. Over-filtering on the server: Heavy server-side filters can cause performance issues. Filter at the client level when possible.

  5. Reconnecting after disconnect: The Supabase SDK auto-reconnects, but you must handle stale data or missed events during the reconnection period.

Practice Questions

  1. How do you enable realtime for a table in Supabase? Use the Dashboard (Database > Replication) or run ALTER PUBLICATION supabase_realtime ADD TABLE table_name.

  2. What events can you subscribe to? INSERT, UPDATE, DELETE, or use * for all events.

  3. How do you filter realtime events by column value? Use the filter parameter in the subscription options, e.g., status=eq.completed.

  4. What is the difference between broadcast and postgres_changes? Broadcast sends custom messages directly. Postgres_changes listens for actual database row changes.

  5. Challenge: Build a collaborative typing indicator that shows when other users are typing, using presence and broadcast features.

FAQ

Does realtime work with RLS?

Yes. Realtime respects RLS policies. Users only receive changes for rows they have SELECT access to.

How many concurrent WebSocket connections can I have?

Supabase free tier allows 200 concurrent connections. Paid tiers have higher limits.

What happens if the WebSocket disconnects?

The SDK automatically reconnects. During reconnection, missed events are not replayed.

Can I subscribe to multiple tables?

Yes. Create separate channels for each table or use a single channel with multiple event listeners.

Does realtime work across different regions?

Yes. Realtime uses a distributed system that works across Supabase regions.

Mini Project

Create a realtime dashboard that shows live file processing updates. The dashboard subscribes to processing_jobs changes and displays status updates as they happen.

def realtime_dashboard():
    print("Realtime Dashboard initialized")
    print("Connected to Supabase Realtime")
    print()
    
    events = [
        {"id": 1, "file": "report.pdf", "status": "uploading", "progress": 15},
        {"id": 1, "file": "report.pdf", "status": "processing", "progress": 47},
        {"id": 2, "file": "photos.zip", "status": "uploading", "progress": 5},
        {"id": 1, "file": "report.pdf", "status": "compressing", "progress": 78},
        {"id": 2, "file": "photos.zip", "status": "processing", "progress": 34},
        {"id": 1, "file": "report.pdf", "status": "completed", "progress": 100},
        {"id": 2, "file": "photos.zip", "status": "compressing", "progress": 62},
        {"id": 2, "file": "photos.zip", "status": "completed", "progress": 100},
    ]
    
    for event in events:
        status_icon = {"uploading": "UP", "processing": "PR", "compressing": "CP", "completed": "DN"}
        icon = status_icon.get(event["status"], "??")
        print(f"  [{icon}] {event['file']:20s} {event['status']:12s} {event['progress']:3d}%")

realtime_dashboard()

What's Next

Next: Storage Buckets for file uploads and management.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro