# Supabase Realtime

> Subscribe to realtime changes in Supabase using WebSocket connections. Use for listening to database changes, presence tracking, and broadcast messaging.

- Skill: `nice-wolf-studio/supabase-realtime` (Agent Skill)
- Install (CLI): `npx skillmds add nice-wolf-studio/supabase-realtime`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nice-wolf-studio/supabase-realtime/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: nice-wolf-studio (https://skillmd.com/u/nice-wolf-studio)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/nice-wolf-studio/supabase-realtime

---


# Supabase Realtime

## Overview

This skill provides guidance for working with Supabase Realtime features. Realtime allows you to listen to database changes, broadcast messages, and track presence using WebSocket connections.

**Note:** Realtime operations require WebSocket support, which is more complex in bash. This skill focuses on practical patterns and examples using available tools.

## Prerequisites

**Required environment variables:**
```bash
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_KEY="your-anon-or-service-role-key"
```

**Additional tools:**
- `websocat` or `wscat` for WebSocket connections
- `jq` for JSON processing

**Install websocat:**
```bash
# macOS
brew install websocat

# Linux
wget https://github.com/vi/websocat/releases/download/v1.12.0/websocat.x86_64-unknown-linux-musl
chmod +x websocat.x86_64-unknown-linux-musl
sudo mv websocat.x86_64-unknown-linux-musl /usr/local/bin/websocat
```

## WebSocket Connection

**Connect to Supabase Realtime:**
```bash
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_KEY="your-anon-key"

# Extract WebSocket URL (replace https:// with wss://)
WS_URL=$(echo "$SUPABASE_URL" | sed 's/https:/wss:/')

# Connect to realtime
websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0"
```

## Database Change Subscriptions

### Subscribe to Table Changes

**Listen to all changes on a table:**
```bash
#!/bin/bash

SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_KEY="your-anon-key"
WS_URL=$(echo "$SUPABASE_URL" | sed 's/https:/wss:/')

# Create subscription message
SUB_MESSAGE='{
  "topic": "realtime:public:users",
  "event": "phx_join",
  "payload": {},
  "ref": "1"
}'

# Connect and subscribe
echo "$SUB_MESSAGE" | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0"
```

**Subscribe to specific events:**
```bash
# Listen for INSERT events only
SUB_MESSAGE='{
  "topic": "realtime:public:users",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "INSERT",
          "schema": "public",
          "table": "users"
        }
      ]
    }
  },
  "ref": "1"
}'

echo "$SUB_MESSAGE" | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0"
```

**Subscribe to UPDATE events:**
```bash
SUB_MESSAGE='{
  "topic": "realtime:public:products",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "UPDATE",
          "schema": "public",
          "table": "products"
        }
      ]
    }
  },
  "ref": "1"
}'
```

**Subscribe to DELETE events:**
```bash
SUB_MESSAGE='{
  "topic": "realtime:public:posts",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "DELETE",
          "schema": "public",
          "table": "posts"
        }
      ]
    }
  },
  "ref": "1"
}'
```

**Subscribe to all events (*, INSERT, UPDATE, DELETE):**
```bash
SUB_MESSAGE='{
  "topic": "realtime:public:orders",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "*",
          "schema": "public",
          "table": "orders"
        }
      ]
    }
  },
  "ref": "1"
}'
```

### Filter Subscriptions

**Listen to changes matching a filter:**
```bash
# Only listen to changes where status = 'active'
SUB_MESSAGE='{
  "topic": "realtime:public:users",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "*",
          "schema": "public",
          "table": "users",
          "filter": "status=eq.active"
        }
      ]
    }
  },
  "ref": "1"
}'
```

## Broadcast Messaging

### Send Broadcast Message

**Broadcast a message to a channel:**
```bash
#!/bin/bash

SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_KEY="your-anon-key"
WS_URL=$(echo "$SUPABASE_URL" | sed 's/https:/wss:/')

# Join channel first
JOIN_MESSAGE='{
  "topic": "realtime:chat-room-1",
  "event": "phx_join",
  "payload": {
    "config": {
      "broadcast": {
        "self": true
      }
    }
  },
  "ref": "1"
}'

# Broadcast message
BROADCAST_MESSAGE='{
  "topic": "realtime:chat-room-1",
  "event": "broadcast",
  "payload": {
    "type": "message",
    "event": "new_message",
    "payload": {
      "user": "Alice",
      "message": "Hello, World!"
    }
  },
  "ref": "2"
}'

# Send messages
{
  echo "$JOIN_MESSAGE"
  sleep 1
  echo "$BROADCAST_MESSAGE"
} | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0"
```

### Listen to Broadcast Messages

**Receive broadcast messages:**
```bash
# Join channel and listen
JOIN_MESSAGE='{
  "topic": "realtime:chat-room-1",
  "event": "phx_join",
  "payload": {
    "config": {
      "broadcast": {
        "self": false
      }
    }
  },
  "ref": "1"
}'

echo "$JOIN_MESSAGE" | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0"
```

## Presence Tracking

### Track Presence

**Join channel with presence:**
```bash
PRESENCE_MESSAGE='{
  "topic": "realtime:lobby",
  "event": "phx_join",
  "payload": {
    "config": {
      "presence": {
        "key": "user-123"
      }
    }
  },
  "ref": "1"
}'

# Track presence state
TRACK_MESSAGE='{
  "topic": "realtime:lobby",
  "event": "presence",
  "payload": {
    "type": "presence",
    "event": "track",
    "payload": {
      "user_id": "123",
      "username": "Alice",
      "status": "online"
    }
  },
  "ref": "2"
}'
```

### Untrack Presence

**Leave presence:**
```bash
UNTRACK_MESSAGE='{
  "topic": "realtime:lobby",
  "event": "presence",
  "payload": {
    "type": "presence",
    "event": "untrack"
  },
  "ref": "3"
}'
```

## Practical Patterns

### Continuous Listener Script

```bash
#!/bin/bash
# listen-to-changes.sh

SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_KEY="your-anon-key"
WS_URL=$(echo "$SUPABASE_URL" | sed 's/https:/wss:/')
TABLE="users"

echo "Listening for changes on $TABLE table..."

# Subscribe to changes
SUB_MESSAGE='{
  "topic": "realtime:public:'"$TABLE"'",
  "event": "phx_join",
  "payload": {
    "config": {
      "postgres_changes": [
        {
          "event": "*",
          "schema": "public",
          "table": "'"$TABLE"'"
        }
      ]
    }
  },
  "ref": "1"
}'

# Listen continuously
echo "$SUB_MESSAGE" | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0" | \
while IFS= read -r line; do
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $line" | jq '.'
done
```

### Process Changes with Handler

```bash
#!/bin/bash
# process-changes.sh

handle_insert() {
    local record="$1"
    echo "New record inserted:"
    echo "$record" | jq '.payload.record'

    # Your custom logic here
    # Example: Send notification, update cache, etc.
}

handle_update() {
    local old_record="$1"
    local new_record="$2"
    echo "Record updated:"
    echo "Old: $(echo "$old_record" | jq -c '.')"
    echo "New: $(echo "$new_record" | jq -c '.')"
}

handle_delete() {
    local record="$1"
    echo "Record deleted:"
    echo "$record" | jq '.payload.old_record'
}

# Listen and process
websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0" | \
while IFS= read -r line; do
    event_type=$(echo "$line" | jq -r '.payload.data.type // empty')

    case "$event_type" in
        "INSERT")
            handle_insert "$(echo "$line" | jq '.payload.data')"
            ;;
        "UPDATE")
            handle_update \
                "$(echo "$line" | jq '.payload.data.old_record')" \
                "$(echo "$line" | jq '.payload.data.record')"
            ;;
        "DELETE")
            handle_delete "$(echo "$line" | jq '.payload.data')"
            ;;
    esac
done
```

### Multi-Table Listener

```bash
#!/bin/bash
# listen-multiple-tables.sh

TABLES=("users" "posts" "comments")

for table in "${TABLES[@]}"; do
    (
        echo "Starting listener for $table"
        SUB_MESSAGE='{
          "topic": "realtime:public:'"$table"'",
          "event": "phx_join",
          "payload": {
            "config": {
              "postgres_changes": [{"event": "*", "schema": "public", "table": "'"$table"'"}]
            }
          },
          "ref": "1"
        }'

        echo "$SUB_MESSAGE" | websocat "${WS_URL}/realtime/v1/websocket?apikey=${SUPABASE_KEY}&vsn=1.0.0" | \
        while IFS= read -r line; do
            echo "[$table] $line"
        done
    ) &
done

wait
```

## Message Format

### Subscription Confirmation
```json
{
  "event": "phx_reply",
  "payload": {
    "response": {
      "postgres_changes": [
        {
          "id": "12345",
          "event": "*",
          "schema": "public",
          "table": "users"
        }
      ]
    },
    "status": "ok"
  },
  "ref": "1",
  "topic": "realtime:public:users"
}
```

### INSERT Event
```json
{
  "event": "postgres_changes",
  "payload": {
    "data": {
      "commit_timestamp": "2023-01-01T12:00:00Z",
      "record": {
        "id": 123,
        "name": "John Doe",
        "email": "john@example.com"
      },
      "schema": "public",
      "table": "users",
      "type": "INSERT"
    },
    "ids": [12345]
  },
  "topic": "realtime:public:users"
}
```

### UPDATE Event
```json
{
  "event": "postgres_changes",
  "payload": {
    "data": {
      "commit_timestamp": "2023-01-01T12:00:00Z",
      "old_record": {
        "id": 123,
        "name": "John Doe"
      },
      "record": {
        "id": 123,
        "name": "Jane Doe"
      },
      "schema": "public",
      "table": "users",
      "type": "UPDATE"
    }
  }
}
```

### DELETE Event
```json
{
  "event": "postgres_changes",
  "payload": {
    "data": {
      "commit_timestamp": "2023-01-01T12:00:00Z",
      "old_record": {
        "id": 123,
        "name": "John Doe"
      },
      "schema": "public",
      "table": "users",
      "type": "DELETE"
    }
  }
}
```

## Alternative: REST Polling

For simpler use cases where WebSockets are impractical, consider polling:

```bash
#!/bin/bash
# poll-changes.sh

source "$(dirname "${BASH_SOURCE[0]}")/../../scripts/supabase-api.sh"

LAST_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

while true; do
    # Get records created/updated since last check
    new_records=$(supabase_get "/rest/v1/users?updated_at=gt.${LAST_TIMESTAMP}&order=updated_at.asc")

    if [[ "$new_records" != "[]" ]]; then
        echo "New changes detected:"
        echo "$new_records" | jq '.'

        # Update timestamp
        LAST_TIMESTAMP=$(echo "$new_records" | jq -r '.[-1].updated_at')
    fi

    # Poll every 5 seconds
    sleep 5
done
```

## Realtime Configuration

**Enable Realtime in Supabase Dashboard:**
1. Go to Database > Replication
2. Enable replication for tables you want to listen to
3. Choose which events to publish (INSERT, UPDATE, DELETE)

**Row Level Security:**
Realtime respects RLS policies. Users only receive changes for rows they have access to.

## Limitations

- WebSocket connections require persistent connection management
- Bash is not ideal for WebSocket handling (consider Node.js/Python for production)
- Connection drops require reconnection logic
- Realtime is subject to connection limits based on your Supabase plan

## Use Cases

**Good for Realtime in bash:**
- Development/debugging tools
- Simple monitoring scripts
- Log streaming
- Testing realtime functionality

**Better in other languages:**
- Production chat applications
- Complex presence tracking
- Multi-channel coordination
- Auto-reconnection requirements

## API Documentation

Full Supabase Realtime documentation: https://supabase.com/docs/guides/realtime

