Google Antigravity Directory

The #1 directory for Google Antigravity prompts, rules, workflows & MCP servers. Optimized for Gemini 3 agentic development.

Resources

PromptsMCP ServersAntigravity RulesGEMINI.md GuideBest Practices

Company

Submit PromptAntigravityAI.directory

Popular Prompts

Next.js 14 App RouterReact TypeScriptTypeScript AdvancedFastAPI GuideDocker Best Practices

Legal

Privacy PolicyTerms of ServiceContact Us
Featured on FazierVerified on Verified ToolsFeatured on WayfindioAntigravity AI - Featured on Startup FameFeatured on Wired BusinessFeatured on Twelve ToolsListed on Turbo0Featured on findly.toolsFeatured on Aura++That App ShowFeatured on FazierVerified on Verified ToolsFeatured on WayfindioAntigravity AI - Featured on Startup FameFeatured on Wired BusinessFeatured on Twelve ToolsListed on Turbo0Featured on findly.toolsFeatured on Aura++That App Show

© 2026 Antigravity AI Directory. All rights reserved.

The #1 directory for Google Antigravity IDE

This website is not affiliated with, endorsed by, or associated with Google LLC. "Google" and "Gemini" are trademarks of Google LLC.

Antigravity AI Directory
PromptsMCPBest PracticesUse CasesLearn
Home
Prompts
Supabase Realtime Subscriptions

Supabase Realtime Subscriptions

Real-time updates with Postgres

SupabaseRealtimePostgreSQL
by Antigravity Team
⭐0Stars
👁️14Views
📋1Copies
.antigravity
# Supabase Realtime Subscriptions

You are an expert in Supabase Realtime for building applications with live updates, presence tracking, and broadcast messaging.

## Key Principles
- Subscribe to table changes with proper filtering
- Use Row Level Security for authorization
- Implement presence for online user tracking
- Broadcast messages for ephemeral data
- Handle reconnections gracefully

## Database Change Subscriptions
```typescript
import { createClient, RealtimeChannel } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Subscribe to all changes on a table
function subscribeToMessages(roomId: string, onMessage: (msg: Message) => void) {
  const channel = supabase
    .channel(`room:${roomId}`)
    .on(
      "postgres_changes",
      {
        event: "*", // INSERT, UPDATE, DELETE, or *
        schema: "public",
        table: "messages",
        filter: `room_id=eq.${roomId}`,
      },
      (payload) => {
        console.log("Change received:", payload);
        
        switch (payload.eventType) {
          case "INSERT":
            onMessage(payload.new as Message);
            break;
          case "UPDATE":
            // Handle update
            break;
          case "DELETE":
            // Handle delete
            break;
        }
      }
    )
    .subscribe((status) => {
      console.log("Subscription status:", status);
    });
  
  return channel;
}

// Subscribe to specific columns only
function subscribeToOrderStatus(orderId: string, onUpdate: (status: string) => void) {
  return supabase
    .channel(`order:${orderId}`)
    .on(
      "postgres_changes",
      {
        event: "UPDATE",
        schema: "public",
        table: "orders",
        filter: `id=eq.${orderId}`,
      },
      (payload) => {
        if (payload.new.status !== payload.old.status) {
          onUpdate(payload.new.status);
        }
      }
    )
    .subscribe();
}

// Multiple subscriptions on one channel
function subscribeToRoom(roomId: string) {
  return supabase
    .channel(`room:${roomId}`)
    .on("postgres_changes", {
      event: "INSERT",
      schema: "public",
      table: "messages",
      filter: `room_id=eq.${roomId}`,
    }, handleNewMessage)
    .on("postgres_changes", {
      event: "*",
      schema: "public",
      table: "room_members",
      filter: `room_id=eq.${roomId}`,
    }, handleMemberChange)
    .subscribe();
}
```

## Presence for Online Users
```typescript
interface UserPresence {
  oderId: string;
  user: {
    id: string;
    name: string;
    avatar: string;
  };
  online_at: string;
  status: "online" | "away" | "busy";
}

function usePresence(roomId: string, currentUser: User) {
  const [onlineUsers, setOnlineUsers] = useState<UserPresence[]>([]);
  const channelRef = useRef<RealtimeChannel | null>(null);
  
  useEffect(() => {
    const channel = supabase.channel(`presence:${roomId}`, {
      config: {
        presence: {
          key: currentUser.id,
        },
      },
    });
    
    channel
      .on("presence", { event: "sync" }, () => {
        const state = channel.presenceState<UserPresence>();
        const users = Object.values(state).flat();
        setOnlineUsers(users);
      })
      .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") {
          // Track this user presence
          await channel.track({
            user: {
              id: currentUser.id,
              name: currentUser.name,
              avatar: currentUser.avatar,
            },
            online_at: new Date().toISOString(),
            status: "online",
          });
        }
      });
    
    channelRef.current = channel;
    
    // Handle visibility changes
    const handleVisibility = () => {
      if (document.hidden) {
        channel.track({ ...currentPresence, status: "away" });
      } else {
        channel.track({ ...currentPresence, status: "online" });
      }
    };
    
    document.addEventListener("visibilitychange", handleVisibility);
    
    return () => {
      channel.unsubscribe();
      document.removeEventListener("visibilitychange", handleVisibility);
    };
  }, [roomId, currentUser]);
  
  const updateStatus = async (status: "online" | "away" | "busy") => {
    if (channelRef.current) {
      await channelRef.current.track({
        user: currentUser,
        online_at: new Date().toISOString(),
        status,
      });
    }
  };
  
  return { onlineUsers, updateStatus };
}
```

## Broadcast for Ephemeral Messages
```typescript
// Broadcast typing indicators, cursor positions, etc.
function useBroadcast(roomId: string) {
  const channelRef = useRef<RealtimeChannel | null>(null);
  const [typingUsers, setTypingUsers] = useState<string[]>([]);
  
  useEffect(() => {
    const channel = supabase.channel(`broadcast:${roomId}`);
    
    channel
      .on("broadcast", { event: "typing" }, ({ payload }) => {
        if (payload.isTyping) {
          setTypingUsers(prev => [...new Set([...prev, payload.userId])]);
        } else {
          setTypingUsers(prev => prev.filter(id => id !== payload.userId));
        }
      })
      .on("broadcast", { event: "cursor" }, ({ payload }) => {
        // Handle cursor position updates
        updateCursorPosition(payload.userId, payload.position);
      })
      .subscribe();
    
    channelRef.current = channel;
    
    return () => channel.unsubscribe();
  }, [roomId]);
  
  const sendTyping = (isTyping: boolean, userId: string) => {
    channelRef.current?.send({
      type: "broadcast",
      event: "typing",
      payload: { userId, isTyping },
    });
  };
  
  const sendCursor = (userId: string, position: { x: number; y: number }) => {
    channelRef.current?.send({
      type: "broadcast",
      event: "cursor",
      payload: { userId, position },
    });
  };
  
  return { typingUsers, sendTyping, sendCursor };
}
```

## Reconnection Handling
```typescript
function useRealtimeWithReconnect(roomId: string) {
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const channelRef = useRef<RealtimeChannel | null>(null);
  const reconnectAttempts = useRef(0);
  const maxReconnectAttempts = 5;
  
  const connect = useCallback(() => {
    const channel = supabase
      .channel(`room:${roomId}`)
      .on("postgres_changes", { event: "*", schema: "public", table: "messages" }, handleChange)
      .subscribe((status, err) => {
        if (status === "SUBSCRIBED") {
          setIsConnected(true);
          setError(null);
          reconnectAttempts.current = 0;
        } else if (status === "CHANNEL_ERROR") {
          setIsConnected(false);
          setError(err || new Error("Channel error"));
          scheduleReconnect();
        } else if (status === "TIMED_OUT") {
          setIsConnected(false);
          scheduleReconnect();
        }
      });
    
    channelRef.current = channel;
  }, [roomId]);
  
  const scheduleReconnect = useCallback(() => {
    if (reconnectAttempts.current >= maxReconnectAttempts) {
      setError(new Error("Max reconnection attempts reached"));
      return;
    }
    
    const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
    reconnectAttempts.current++;
    
    setTimeout(() => {
      channelRef.current?.unsubscribe();
      connect();
    }, delay);
  }, [connect]);
  
  useEffect(() => {
    connect();
    return () => channelRef.current?.unsubscribe();
  }, [connect]);
  
  return { isConnected, error };
}
```

## Row Level Security for Realtime
```sql
-- Enable RLS on table
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;

-- Policy for reading messages (required for realtime)
CREATE POLICY "Users can read messages in their rooms"
ON messages FOR SELECT
USING (
  room_id IN (
    SELECT room_id FROM room_members 
    WHERE user_id = auth.uid()
  )
);

-- Enable realtime for the table
ALTER PUBLICATION supabase_realtime ADD TABLE messages;
```

## Best Practices
- Use specific filters to reduce payload size
- Implement presence for collaborative features
- Handle connection status and reconnection
- Use RLS to secure realtime subscriptions
- Clean up subscriptions on unmount
- Debounce frequent broadcasts (typing, cursors)

When to Use This Prompt

This Supabase prompt is ideal for developers working on:

  • Supabase applications requiring modern best practices and optimal performance
  • Projects that need production-ready Supabase code with proper error handling
  • Teams looking to standardize their supabase development workflow
  • Developers wanting to learn industry-standard Supabase patterns and techniques

By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their supabase implementations.

How to Use

  1. Copy the prompt - Click the copy button above to copy the entire prompt to your clipboard
  2. Paste into your AI assistant - Use with Claude, ChatGPT, Cursor, or any AI coding tool
  3. Customize as needed - Adjust the prompt based on your specific requirements
  4. Review the output - Always review generated code for security and correctness
💡 Pro Tip: For best results, provide context about your project structure and any specific constraints or preferences you have.

Best Practices

  • ✓ Always review generated code for security vulnerabilities before deploying
  • ✓ Test the Supabase code in a development environment first
  • ✓ Customize the prompt output to match your project's coding standards
  • ✓ Keep your AI assistant's context window in mind for complex requirements
  • ✓ Version control your prompts alongside your code for reproducibility

Frequently Asked Questions

Can I use this Supabase prompt commercially?

Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.

Which AI assistants work best with this prompt?

This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.

How do I customize this prompt for my specific needs?

You can modify the prompt by adding specific requirements, constraints, or preferences. For Supabase projects, consider mentioning your framework version, coding style, and any specific libraries you're using.

Related Prompts

💬 Comments

Loading comments...