Skip to main content
The useRealtime hook connects your React components to realtime events with full type safety.

Basic Usage

Subscribe to events in any client component:
page.tsx
"use client"

import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Page() {
  useRealtime<RealtimeEvents>({
    event: "notification.alert",
    onData(data, channel) {
      console.log("Received:", data)
    },
  })

  return <p>Listening for events...</p>
}

Hook Options

event
string
required
The event to subscribe to (e.g. "notification.alert")
onData
function
required
Callback when an event is received. Receives data and channel as arguments.
channels
string[]
default:"[\"default\"]"
Array of channel names to subscribe to
history
boolean | object
default:"false"
  • true: Fetch all available history
  • { length: number }: Fetch the last N messages
  • { since: number }: Fetch messages after a Unix timestamp (in milliseconds)
enabled
boolean
default:"true"
Whether the connection is active. Set to false to disconnect.
maxReconnectAttempts
number
default:"3"
Maximum number of reconnection attempts before giving up
api
object
API configuration: - url: The realtime endpoint URL, defaults to /api/realtime - withCredentials: Whether to send cookies with requests (useful for external backends)

Return Value

The hook returns an object with:
status
ConnectionStatus
Current connection state: "connecting", "connected", "reconnecting", "disconnected", or "error"
page.tsx
const { status } = useRealtime<RealtimeEvents>({
  event: "notification.alert",
  onData: (data, channel) => {},
})

console.log(status)

Connection Control

Enable or disable connections dynamically:
page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Page() {
  const [enabled, setEnabled] = useState(true)

  const { status } = useRealtime<RealtimeEvents>({
    enabled,
    event: "notification.alert",
    onData: (data, channel) => {
      console.log(data, channel)
    },
  })

  return (
    <div>
      <button onClick={() => setEnabled((prev) => !prev)}>
        {enabled ? "Disconnect" : "Connect"}
      </button>

      <p>Status: {status}</p>
    </div>
  )
}

Conditional Connections

Connect only when certain conditions are met:
page.tsx
"use client"

import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import { useUser } from "@/hooks/auth"

export default function Page() {
  const { user } = useUser()

  useRealtime<RealtimeEvents>({
    enabled: Boolean(user),
    channels: [`user-${user.id}`],
    event: "notification.alert",
    onData: (data, channel) => {
      console.log(data)
    },
  })

  return <p>Notifications {user ? "enabled" : "disabled"}</p>
}

Multiple Channels

Subscribe to multiple channels at once:
page.tsx
"use client"

import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Page() {
  useRealtime<RealtimeEvents>({
    channels: ["global", "announcements", "user-123"],
    event: "notification.alert",
    onData(data, channel) {
      console.log(`Message from ${channel}:`, data)
    },
  })

  return <p>Listening to multiple channels</p>
}

Dynamic Channel Management

Add and remove channels dynamically:
page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Page() {
  const [channels, setChannels] = useState<string[]>(["lobby"])

  useRealtime<RealtimeEvents>({
    channels,
    event: "chat.message",
    onData(data, channel) {
      console.log(`Message from ${channel}:`, data)
    },
  })

  const joinRoom = (roomId: string) => {
    setChannels((prev) => [...prev, roomId])
  }

  const leaveRoom = (roomId: string) => {
    setChannels((prev) => prev.filter((c) => c !== roomId))
  }

  return (
    <div>
      <p>Active channels: {channels.join(", ")}</p>
      <button onClick={() => joinRoom("room-1")}>Join Room 1</button>
      <button onClick={() => joinRoom("room-2")}>Join Room 2</button>
      <button onClick={() => leaveRoom("lobby")}>Leave Lobby</button>
    </div>
  )
}

Fetch History on Connection

Replay past messages when connecting:
page.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function ChatRoom() {
  const [messages, setMessages] = useState<string[]>([])

  useRealtime<RealtimeEvents>({
    event: "chat.message",
    history: { length: 50 },
    onData(data, channel) {
      // each history item is automatically passed to this handler
      // so you can replay with any logic you like
      setMessages((prev) => [...prev, data])
    },
  })

  return (
    <div>
      {messages.map((msg, i) => (
        <p key={i}>{msg}</p>
      ))}
    </div>
  )
}

Custom API Endpoint

Configure a custom realtime endpoint:
page.tsx
"use client"

import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Page() {
  useRealtime<RealtimeEvents>({
    event: "notification.alert",
    api: {
      url: "/api/custom-realtime",
      withCredentials: true,
    },
    onData: (data, channel) => {
      console.log(data)
    },
  })

  return <p>Connected to custom endpoint</p>
}

Use Cases

Show real-time notifications to users:
notifications.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import { toast } from "react-hot-toast"
import { useUser } from "@/hooks/auth"

export default function Notifications() {
  const { user } = useUser()

  useRealtime<RealtimeEvents>({
    channels: [`user-${user.id}`],
    event: "notification.alert",
    onData(content, channel) {
      toast(content)
    },
  })

  return <p>Listening for notifications...</p>
}
Build a real-time chat:
chat.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
import z from "zod/v4"

type Message = z.infer<RealtimeEvents["chat"]["message"]>

export default function Chat() {
  const [messages, setMessages] = useState<Message[]>([])

  useRealtime<RealtimeEvents>({
    channels: ["room-123"],
    event: "chat.message",
    history: true,
    onData(message, channel) {
      setMessages((prev) => [...prev, message])
    },
  })

  return (
    <div>
      {messages.map((msg, i) => (
        <p key={i}>
          <span className="font-bold">{msg.sender}:</span> {msg.text}
        </p>
      ))}
    </div>
  )
}
Update metrics in real-time:
dashboard.tsx
"use client"

import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Dashboard() {
  const queryClient = useQueryClient()
  
  const { data: metrics } = useQuery({
    queryKey: ["metrics"],
    queryFn: async () => {
      const res = await fetch("/api/metrics?user=user-123")
      return res.json()
    },
  })

  useRealtime<RealtimeEvents>({
    channels: ["user-123"],
    event: "metrics.update",
    onData() {
      // 👇 invalidate, so react-query refetches
      queryClient.invalidateQueries({ queryKey: ["metrics"] })
    },
  })

  return (
    <div>
      <p>Active Users: {metrics.users}</p>
      <p>Revenue: ${metrics.revenue}</p>
    </div>
  )
}
Sync changes across users:
editor.tsx
"use client"

import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"

export default function Editor({ documentId }: { documentId: string }) {
  const [content, setContent] = useState("")

  useRealtime<RealtimeEvents>({
    channels: [`doc-${documentId}`],
    event: "document.update",
    history: { length: 1 },
    onData(data, channel) {
      setContent(data.content)
    },
  })

  return <textarea value={content} onChange={(e) => setContent(e.target.value)} />
}

Next Steps

I