| name | supabase-realtime |
| description | Subscribe to realtime changes in Supabase using WebSocket connections. Use for listening to database changes, presence tracking, and broadcast messaging. |
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:
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_KEY="your-anon-or-service-role-key"
Additional tools:
websocatorwscatfor WebSocket connectionsjqfor JSON processing
Install websocat:
# 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:
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:
#!/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:
# 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:
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:
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):
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:
# 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:
#!/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:
# 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:
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:
UNTRACK_MESSAGE='{
"topic": "realtime:lobby",
"event": "presence",
"payload": {
"type": "presence",
"event": "untrack"
},
"ref": "3"
}'
Practical Patterns
Continuous Listener Script
#!/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
#!/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
#!/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
{
"event": "phx_reply",
"payload": {
"response": {
"postgres_changes": [
{
"id": "12345",
"event": "*",
"schema": "public",
"table": "users"
}
]
},
"status": "ok"
},
"ref": "1",
"topic": "realtime:public:users"
}
INSERT Event
{
"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
{
"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
{
"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:
#!/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:
- Go to Database > Replication
- Enable replication for tables you want to listen to
- 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