WebSockets in Edge Functions
Can a SupaHost Edge Function serve WebSockets? The runtime supports Deno.upgradeWebSocket, but worker limits apply, so test before relying on it. Realtime is usually the better fit.
Possibly, but test it before you rely on it. The Edge Runtime that SupaHost uses supports Deno.upgradeWebSocket, so a function can accept a WebSocket connection. Each connection lives inside a function worker, though, and SupaHost's worker limits apply: 150 MB of memory, a 150-second request idle timeout, and an overall worker lifetime cap (SupaHost sets a 400-second worker timeout). Expect connections to end within minutes, and design your client to reconnect.
For most real-time features, Realtime is the better tool. Broadcast and Presence already give you long-lived WebSocket connections with reconnection, authorization and fan-out to many clients, with no per-message fees on SupaHost.
When a function WebSocket makes sense
- A short-lived session that needs custom server logic per message, such as relaying a stream from an AI model or a speech-to-text API.
- A protocol bridge: your client speaks WebSocket and the function forwards to another service.
If you need to push the same message to many users, keep presence lists, or hold connections open for hours, use Broadcast and Presence instead.
A minimal echo server
// functions/live-quote/index.ts
Deno.serve((req) => {
if (req.headers.get('upgrade')?.toLowerCase() !== 'websocket') {
return new Response('Expected a WebSocket upgrade', { status: 426 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
socket.onopen = () => console.log('client connected')
socket.onmessage = (event) => {
socket.send(JSON.stringify({ received: event.data, at: new Date().toISOString() }))
}
socket.onclose = () => console.log('client disconnected')
socket.onerror = (event) => console.error('socket error', event)
return response
})
Upstream Supabase also recommends keeping the worker alive while the socket is open by passing a promise that resolves in socket.onclose to EdgeRuntime.waitUntil(). Whether that extends a connection on SupaHost within the limits above is something to verify in your own test.
Authentication
Browsers can't set an Authorization header on a WebSocket. With Require JWT on, the upgrade request fails with 401 UNAUTHORIZED_NO_AUTH_HEADER. To use a function WebSocket from a browser:
- Turn off Require JWT for that function in Dashboard → your project → Functions tab.
- Send the user's access token another way, for example as the first message after connecting, and verify it in the function with
supabase.auth.getUser(token)before doing any work. Close the socket if it fails.
Avoid putting tokens in the URL query string, where they can end up in logs.
Connecting from the client
const ws = new WebSocket('wss://app-one.acme.supahost.space/functions/v1/live-quote')
ws.onopen = () => ws.send(JSON.stringify({ type: 'auth', token: session.access_token }))
ws.onmessage = (e) => console.log(JSON.parse(e.data))
ws.onclose = () => setTimeout(connect, 1000) // plan for reconnects
Test checklist before you ship
- How long does an idle connection stay open? How long does a busy one?
- What does the client see when the worker ends: a clean close or an error?
- How much memory does one connection use? All functions in a project share one container (384 MB on Builder up to 3 GB on Enterprise), so many open sockets add up.
- Check the Functions tab's Logs card for shutdown events, which include
memory_mbandcpu_ms.