Lovable supports real-time features through Supabase Realtime, which is built into every Lovable Cloud project. Subscribe to database changes with supabase.channel(), broadcast messages between users, and track online presence — all without managing WebSocket servers. Ask Lovable to add a Realtime subscription in Agent Mode, and it will generate the correct channel setup, event listeners, and cleanup logic automatically.
Why real-time features need WebSocket integration in Lovable
Standard web applications fetch data once when the page loads and only update when the user manually refreshes. For features like live chat, collaborative editing, notifications, or real-time dashboards, you need data to push from the server to the browser instantly. This is what WebSockets enable — a persistent two-way connection between the client and server. Lovable projects use Supabase as their backend, and Supabase includes a built-in Realtime engine. This means you do not need to set up a separate WebSocket server or install additional packages. Supabase Realtime provides three capabilities: Postgres Changes (subscribe to INSERT, UPDATE, DELETE on database tables), Broadcast (send messages between connected clients), and Presence (track which users are online and share ephemeral state). The most common mistake is trying to use raw WebSocket APIs or third-party libraries like Socket.io in a Lovable project. Since Supabase Realtime is already available through the supabase-js client, using it directly is simpler, more reliable, and does not require additional configuration.
- Using raw WebSocket APIs instead of the built-in Supabase Realtime client
- Missing Realtime enablement on the database table — Supabase requires explicit opt-in per table
- Forgetting to unsubscribe from channels on component unmount — causes memory leaks and duplicate listeners
- Row Level Security (RLS) blocking Realtime subscriptions — the authenticated user must have SELECT access
- Not handling reconnection — network interruptions drop the WebSocket connection silently
Error messages you might see
Realtime subscription failed: relation "public.messages" is not in the publicationThe database table you are subscribing to has not been added to the Supabase Realtime publication. You need to enable Realtime for that specific table in the Supabase dashboard or prompt Lovable to enable it.
WebSocket connection to 'wss://...' failed: Error during WebSocket handshakeThe Supabase Realtime server rejected the connection. This usually means the Supabase URL or anon key is wrong, or the project's Realtime service is not running. Check your environment variables in Cloud tab, then Secrets.
Channel error: {message: 'Invalid JWT', code: 403}The authentication token used for the Realtime subscription is expired or missing. Ensure the user is signed in before subscribing to channels, and that the Supabase client is initialized with valid credentials.
Before you start
- A Lovable project with Supabase connected (Lovable Cloud or self-hosted)
- At least one database table to subscribe to (e.g., a messages table)
- Realtime enabled on the target table in Supabase settings
- Basic understanding of React hooks (useEffect, useState)
How to fix it
Enable Realtime on your database table
Supabase does not broadcast changes for tables unless Realtime is explicitly enabled
Enable Realtime on your database table
Supabase does not broadcast changes for tables unless Realtime is explicitly enabled
Open the Cloud tab in your Lovable project (click the + button next to Preview). Navigate to Database and find the table you want to subscribe to (for example, a 'messages' table). You can prompt Lovable: 'Enable Realtime on the messages table so I can subscribe to new inserts.' Alternatively, if you have access to the Supabase Dashboard, go to Database, then Replication, then add the table to the supabase_realtime publication.
-- Table exists but Realtime is not enabledCREATE TABLE public.messages ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, content text NOT NULL, user_id uuid REFERENCES auth.users(id), created_at timestamptz DEFAULT now());-- Enable Realtime publication for the messages tableALTER PUBLICATION supabase_realtime ADD TABLE public.messages;Expected result: Supabase now broadcasts INSERT, UPDATE, and DELETE events for the messages table to all subscribed clients.
Subscribe to database changes in a React component
This is the core pattern for receiving live data updates without polling or manual refresh
Subscribe to database changes in a React component
This is the core pattern for receiving live data updates without polling or manual refresh
Create a Supabase Realtime channel inside a useEffect hook. Subscribe to postgres_changes events on your table, specifying the event type (INSERT, UPDATE, DELETE, or * for all). Always return a cleanup function that removes the channel when the component unmounts. This prevents memory leaks and duplicate listeners when React re-renders the component.
import { useEffect, useState } from "react";import { supabase } from "@/integrations/supabase/client";function ChatRoom() { const [messages, setMessages] = useState<any[]>([]); useEffect(() => { // Only fetches once — no live updates supabase.from("messages").select("*").then(({ data }) => { if (data) setMessages(data); }); }, []); return <div>{messages.map(m => <p key={m.id}>{m.content}</p>)}</div>;}import { useEffect, useState } from "react";import { supabase } from "@/integrations/supabase/client";function ChatRoom() { const [messages, setMessages] = useState<any[]>([]); useEffect(() => { // Initial fetch supabase.from("messages").select("*").order("created_at").then(({ data }) => { if (data) setMessages(data); }); // Subscribe to new inserts in real time const channel = supabase .channel("messages-realtime") .on( "postgres_changes", { event: "INSERT", schema: "public", table: "messages" }, (payload) => { // Append new message without re-fetching everything setMessages((prev) => [...prev, payload.new]); } ) .subscribe(); // Cleanup: remove channel on unmount to prevent memory leaks return () => { supabase.removeChannel(channel); }; }, []); return <div>{messages.map(m => <p key={m.id}>{m.content}</p>)}</div>;}Expected result: New messages appear instantly in the chat room without refreshing the page. The subscription cleans up when the component unmounts.
Add Broadcast for client-to-client messaging
Broadcast lets you send messages between users without writing to the database — ideal for typing indicators and cursors
Add Broadcast for client-to-client messaging
Broadcast lets you send messages between users without writing to the database — ideal for typing indicators and cursors
Supabase Broadcast sends ephemeral messages through the Realtime server without touching the database. This is perfect for features like typing indicators, cursor positions, and temporary notifications. Create a channel with a custom name and use channel.send() to broadcast events. Other clients subscribed to the same channel receive the messages instantly.
// No real-time typing indicator — users have no idea if someone is typingimport { useEffect, useState } from "react";import { supabase } from "@/integrations/supabase/client";function useTypingIndicator(roomId: string, userId: string) { const [typingUsers, setTypingUsers] = useState<string[]>([]); useEffect(() => { const channel = supabase.channel(`typing-${roomId}`); channel .on("broadcast", { event: "typing" }, (payload) => { // Another user started typing if (payload.payload.userId !== userId) { setTypingUsers((prev) => prev.includes(payload.payload.userId) ? prev : [...prev, payload.payload.userId] ); // Remove after 3 seconds of no activity setTimeout(() => { setTypingUsers((prev) => prev.filter((id) => id !== payload.payload.userId)); }, 3000); } }) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [roomId, userId]); const sendTyping = () => { supabase.channel(`typing-${roomId}`).send({ type: "broadcast", event: "typing", payload: { userId }, }); }; return { typingUsers, sendTyping };}Expected result: When a user types, other users in the same room see a typing indicator. The indicator disappears after 3 seconds of inactivity.
Track online users with Presence
Presence lets you show who is online and share ephemeral user state like cursor position or status
Track online users with Presence
Presence lets you show who is online and share ephemeral user state like cursor position or status
Supabase Presence tracks which users are connected to a channel and synchronizes state across all clients. When a user joins, their state is broadcast to everyone. When they disconnect (close the tab, lose internet), their state is automatically removed. This is the foundation for 'online now' indicators, collaborative cursors, and live user counts. If setting up Presence across multiple pages with different channel configurations feels overwhelming, RapidDev's engineers have implemented this exact pattern across many Lovable projects.
// No way to know which users are currently onlineimport { useEffect, useState } from "react";import { supabase } from "@/integrations/supabase/client";interface OnlineUser { id: string; name: string; online_at: string;}function useOnlineUsers(roomId: string, currentUser: { id: string; name: string }) { const [onlineUsers, setOnlineUsers] = useState<OnlineUser[]>([]); useEffect(() => { const channel = supabase.channel(`presence-${roomId}`); channel .on("presence", { event: "sync" }, () => { // Gather all present users into a flat array const state = channel.presenceState<OnlineUser>(); const users = Object.values(state).flat(); setOnlineUsers(users); }) .subscribe(async (status) => { if (status === "SUBSCRIBED") { // Announce this user's presence await channel.track({ id: currentUser.id, name: currentUser.name, online_at: new Date().toISOString(), }); } }); return () => { supabase.removeChannel(channel); }; }, [roomId, currentUser.id]); return onlineUsers;}Expected result: A list of currently online users updates automatically as people join and leave. Disconnected users are removed within seconds.
Complete code example
1import { useEffect, useState } from "react";2import { supabase } from "@/integrations/supabase/client";3import { Button } from "@/components/ui/button";4import { Input } from "@/components/ui/input";56interface Message {7 id: string;8 content: string;9 user_id: string;10 created_at: string;11}1213export default function RealtimeChat({ userId }: { userId: string }) {14 const [messages, setMessages] = useState<Message[]>([]);15 const [newMessage, setNewMessage] = useState("");1617 useEffect(() => {18 // Fetch existing messages on mount19 supabase20 .from("messages")21 .select("*")22 .order("created_at", { ascending: true })23 .then(({ data }) => {24 if (data) setMessages(data);25 });2627 // Subscribe to new messages in real time28 const channel = supabase29 .channel("chat-room")30 .on(31 "postgres_changes",32 { event: "INSERT", schema: "public", table: "messages" },33 (payload) => {34 setMessages((prev) => [...prev, payload.new as Message]);35 }36 )37 .subscribe();3839 // Cleanup subscription on unmount40 return () => {41 supabase.removeChannel(channel);42 };43 }, []);4445 const sendMessage = async () => {46 if (!newMessage.trim()) return;47 await supabase.from("messages").insert({48 content: newMessage,49 user_id: userId,50 });51 setNewMessage("");52 };5354 return (55 <div className="flex flex-col h-[500px]">56 <div className="flex-1 overflow-y-auto space-y-2 p-4">57 {messages.map((msg) => (58 <div59 key={msg.id}60 className={`p-2 rounded-lg max-w-xs ${61 msg.user_id === userId62 ? "bg-blue-500 text-white ml-auto"63 : "bg-gray-100"64 }`}65 >66 {msg.content}67 </div>68 ))}69 </div>70 <div className="flex gap-2 p-4 border-t">71 <Input72 value={newMessage}73 onChange={(e) => setNewMessage(e.target.value)}74 placeholder="Type a message..."75 onKeyDown={(e) => e.key === "Enter" && sendMessage()}76 />77 <Button onClick={sendMessage}>Send</Button>78 </div>79 </div>80 );81}Best practices to prevent this
- Always use Supabase Realtime instead of raw WebSockets or Socket.io — it is already configured in every Lovable Cloud project
- Enable Realtime on each table you want to subscribe to — it is not enabled by default
- Always return a cleanup function in useEffect that calls supabase.removeChannel() to prevent memory leaks
- Use unique channel names (e.g., include a room ID) to avoid receiving events from unrelated subscriptions
- Fetch initial data before subscribing — Realtime only sends new events, not the existing dataset
- Check Row Level Security policies — Realtime subscriptions respect RLS, so users only receive changes they have SELECT access to
- Use Broadcast for ephemeral messages (typing indicators, cursors) that do not need database persistence
- Handle the SUBSCRIBED status before tracking Presence — calling track() before subscription is ready causes silent failures
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Lovable (lovable.dev) project with Supabase as the backend. I want to add real-time functionality. My project has a 'messages' table with columns: id (uuid), content (text), user_id (uuid), created_at (timestamptz). Here is my current component that fetches messages: [Paste your current component code here] Please: 1. Show me how to enable Realtime on the messages table 2. Add a Supabase Realtime subscription for new INSERT events 3. Include proper cleanup on unmount 4. Add a Broadcast channel for typing indicators 5. Add Presence tracking to show online users 6. Make sure RLS policies allow the subscription to work
Add real-time updates to my chat feature using Supabase Realtime. Subscribe to INSERT events on the messages table so new messages appear instantly without page refresh. Use supabase.channel() with postgres_changes listener. Include cleanup in useEffect to remove the channel on unmount. Also add a typing indicator using Broadcast — when a user types in the input field, broadcast a typing event to the channel. Show 'User is typing...' text above the input for other users. Make sure the messages table has Realtime enabled.
Frequently asked questions
Does Lovable support WebSockets out of the box?
Yes. Every Lovable project with Supabase gets Supabase Realtime, which uses WebSockets under the hood. You do not need to install any additional packages. Just create a channel with supabase.channel() and subscribe to events.
Do I need to install Socket.io in Lovable?
No. Supabase Realtime handles WebSocket connections natively. Using Socket.io would require a custom WebSocket server, which Lovable does not support. Supabase Realtime provides the same capabilities (live data, broadcast, presence) without extra infrastructure.
Why am I not receiving real-time updates from my Supabase table?
The most common cause is that Realtime is not enabled on the table. Supabase requires you to explicitly add each table to the supabase_realtime publication. Also check that your Row Level Security policies allow SELECT access for the authenticated user.
How do I show who is online in my Lovable app?
Use Supabase Presence. Create a channel, subscribe to presence sync events, and call channel.track() with the current user's info after the subscription is ready. The presenceState() method returns all currently connected users, updating automatically as people join or leave.
Will Realtime subscriptions use my Lovable credits?
Prompting Lovable to set up Realtime subscriptions costs credits like any Agent Mode task. However, the Realtime connections themselves are part of Lovable Cloud usage, not credit consumption. Monitor your Cloud usage in the Cloud tab under Usage.
How do I add a typing indicator in a Lovable chat app?
Use Supabase Broadcast to send a typing event when the user types. Subscribe to the same channel on other clients and display a typing indicator when the event arrives. Set a timeout to remove the indicator after a few seconds of inactivity. Broadcast messages are ephemeral and do not touch the database.
What if I can't get real-time features working myself?
Real-time functionality involves coordinating subscriptions, cleanup, authentication, and RLS policies across multiple components. If you are stuck, RapidDev's engineers have implemented Realtime features in Lovable projects extensively and can set up the complete real-time architecture for you.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation