diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 3f03268edb5ba..d731b4e78ecd5 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -1276,6 +1276,10 @@ export const functions: NavMenuConstant = {
name: 'Ephemeral Storage',
url: '/guides/functions/ephemeral-storage',
},
+ {
+ name: 'WebSockets',
+ url: '/guides/functions/websockets',
+ },
{
name: 'Running AI Models',
url: '/guides/functions/ai-models',
diff --git a/apps/docs/content/guides/functions/background-tasks.mdx b/apps/docs/content/guides/functions/background-tasks.mdx
index 6947f1a57608e..9480c2342cd54 100644
--- a/apps/docs/content/guides/functions/background-tasks.mdx
+++ b/apps/docs/content/guides/functions/background-tasks.mdx
@@ -48,7 +48,7 @@ You can call `EdgeRuntime.waitUntil` in the request handler too. This will not b
```ts
async function fetchAndLog(url: string) {
- const response = await fetch('https://httpbin.org/json')
+ const response = await fetch(url)
console.log(response)
}
diff --git a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx
index 3eeaded1bed81..908d9808ee77b 100644
--- a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx
+++ b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx
@@ -253,7 +253,7 @@ const code = {
-You can find a selection of React Email templates in the [React Email Eamples](https://react.email/examples).
+You can find a selection of React Email templates in the [React Email Examples](https://react.email/examples).
diff --git a/apps/docs/content/guides/functions/limits.mdx b/apps/docs/content/guides/functions/limits.mdx
index c21f46009fd47..9a41065188b8d 100644
--- a/apps/docs/content/guides/functions/limits.mdx
+++ b/apps/docs/content/guides/functions/limits.mdx
@@ -8,10 +8,13 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
## Runtime limits
- Maximum Memory: 256MB
-- Maximum Duration (Wall clock limit): 400s (this is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests)
-- Maximum CPU Time: 2s
-- Request idle timeout: 150s (if an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned)
-- Maximum Function Size (after bundling via CLI): 10MB
+- Maximum Duration (Wall clock limit):
+ This is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests or process background tasks.
+ - Free plan: 150s
+ - Paid plans: 400s
+- Maximum CPU Time: 2s (Amount of actual time spent on the CPU per request - does not include async I/O.)
+- Request idle timeout: 150s (If an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned)
+- Maximum Function Size: 20MB (After bundling using CLI)
- Maximum log message length: 10,000 characters
- Log event threshold: 100 events per 10 seconds
@@ -19,6 +22,5 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
- Outgoing connections to ports `25` and `587` are not allowed.
- Serving of HTML content is only supported with [custom domains](/docs/reference/cli/supabase-domains) (Otherwise `GET` requests that return `text/html` will be rewritten to `text/plain`).
-- Deno and Node file system APIs are not available.
- Web Worker API (or Node `vm` API) are not available.
- Node Libraries that require multithreading are not supported. Examples: [libvips](https://github.com/libvips/libvips), [sharp](https://github.com/lovell/sharp).
diff --git a/apps/docs/content/guides/functions/websockets.mdx b/apps/docs/content/guides/functions/websockets.mdx
new file mode 100644
index 0000000000000..d606fec4a8f7a
--- /dev/null
+++ b/apps/docs/content/guides/functions/websockets.mdx
@@ -0,0 +1,313 @@
+---
+id: 'function-websockets'
+title: 'Handling WebSockets'
+description: 'How to handle WebSocket connections in Edge Functions'
+subtitle: 'How to handle WebSocket connections in Edge Functions'
+---
+
+Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
+
+You can also establish outgoing WebSocket client connections to another server from Edge Functions (e.g., [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview)).
+
+### Writing a WebSocket server
+
+Here are some basic examples of setting up WebSocket servers using Deno and Node.js APIs.
+
+
+
+```ts
+ Deno.serve(req => {
+ const upgrade = req.headers.get("upgrade") || "";
+
+ if (upgrade.toLowerCase() != "websocket") {
+ return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
+ }
+
+ const { socket, response } = Deno.upgradeWebSocket(req);
+
+ socket.onopen = () => console.log("socket opened");
+ socket.onmessage = (e) => {
+ console.log("socket message:", e.data);
+ socket.send(new Date().toString());
+ };
+
+ socket.onerror = e => console.log("socket errored:", e.message);
+ socket.onclose = () => console.log("socket closed");
+
+ return response;
+
+});
+
+````
+
+
+
+```ts
+import { createServer } from "node:http";
+import { WebSocketServer } from "npm:ws";
+
+const server = createServer();
+// Since we manually created the HTTP server,
+// turn on the noServer mode.
+const wss = new WebSocketServer({ noServer: true });
+
+wss.on("connection", ws => {
+ console.log("socket opened");
+ ws.on("message", (data /** Buffer */, isBinary /** bool */) => {
+ if (isBinary) {
+ console.log("socket message:", data);
+ } else {
+ console.log("socket message:", data.toString());
+ }
+
+ ws.send(new Date().toString());
+ });
+
+ ws.on("error", err => {
+ console.log("socket errored:", err.message);
+ });
+
+ ws.on("close", () => console.log("socket closed"));
+});
+
+server.on("upgrade", (req, socket, head) => {
+ wss.handleUpgrade(req, socket, head, ws => {
+ wss.emit("connection", ws, req);
+ });
+});
+
+server.listen(8080);
+````
+
+
+
+
+### Outbound Websockets
+
+You can also establish an outbound WebSocket connection to another server from an Edge Function.
+
+Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a WebSocket proxy.
+
+Here is an example of proxying messages to OpenAI Realtime API.
+
+We use [Supabase Auth](/docs/guides/functions/auth#fetching-the-user) to authenticate the user who is sending the messages.
+
+```ts
+import { createClient } from 'jsr:@supabase/supabase-js@2'
+
+const supabase = createClient(
+ Deno.env.get('SUPABASE_URL'),
+ Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
+)
+const OPENAI_API_KEY = Deno.env.get('OPENAI_API_KEY')
+
+Deno.serve(async (req) => {
+ const upgrade = req.headers.get('upgrade') || ''
+
+ if (upgrade.toLowerCase() != 'websocket') {
+ return new Response("request isn't trying to upgrade to websocket.")
+ }
+
+ // WebSocket browser clients does not support sending custom headers.
+ // We have to use the URL query params to provide user's JWT.
+ // Please be aware query params may be logged in some logging systems.
+ const url = new URL(req.url)
+ const jwt = url.searchParams.get('jwt')
+ if (!jwt) {
+ console.error('Auth token not provided')
+ return new Response('Auth token not provided', { status: 403 })
+ }
+ const { error, data } = await supabase.auth.getUser(jwt)
+ if (error) {
+ console.error(error)
+ return new Response('Invalid token provided', { status: 403 })
+ }
+ if (!data.user) {
+ console.error('user is not authenticated')
+ return new Response('User is not authenticated', { status: 403 })
+ }
+
+ const { socket, response } = Deno.upgradeWebSocket(req)
+
+ socket.onopen = () => {
+ // initiate an outbound WS connection with OpenAI
+ const url = 'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01'
+
+ // openai-insecure-api-key isn't a problem since this code runs in an Edge Function (not client browser)
+ const openaiWS = new WebSocket(url, [
+ 'realtime',
+ `openai-insecure-api-key.${OPENAI_API_KEY}`,
+ 'openai-beta.realtime-v1',
+ ])
+
+ openaiWS.onopen = () => {
+ console.log('Connected to OpenAI server.')
+
+ socket.onmessage = (e) => {
+ console.log('socket message:', e.data)
+ // only send the message if openAI ws is open
+ if (openaiWS.readyState === 1) {
+ openaiWS.send(e.data)
+ } else {
+ socket.send(
+ JSON.stringify({
+ type: 'error',
+ msg: 'openAI connection not ready',
+ })
+ )
+ }
+ }
+ }
+
+ openaiWS.onmessage = (e) => {
+ console.log(e.data)
+ socket.send(e.data)
+ }
+
+ openaiWS.onerror = (e) => console.log('OpenAI error: ', e.message)
+ openaiWS.onclose = (e) => console.log('OpenAI session closed')
+ }
+
+ socket.onerror = (e) => console.log('socket errored:', e.message)
+ socket.onclose = () => console.log('socket closed')
+
+ return response // 101 (Switching Protocols)
+})
+```
+
+### Authentication
+
+WebSocket browser clients don't have the option to send custom headers. Because of this, Edge Functions won't be able to perform the usual authorization header check to verify the JWT.
+
+You can skip the default authorization header checks by explicitly providing `--no-verify-jwt` when serving and deploying functions.
+
+To authenticate the user making WebSocket requests, you can pass the JWT in URL query params or via a custom protocol.
+
+
+
+```ts
+ import { createClient } from "jsr:@supabase/supabase-js@2";
+
+const supabase = createClient(
+Deno.env.get("SUPABASE_URL"),
+Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
+);
+Deno.serve(req => {
+const upgrade = req.headers.get("upgrade") || "";
+
+ if (upgrade.toLowerCase() != "websocket") {
+ return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
+ }
+
+// Please be aware query params may be logged in some logging systems.
+const url = new URL(req.url);
+const jwt = url.searchParams.get("jwt");
+if (!jwt) {
+console.error("Auth token not provided");
+return new Response("Auth token not provided", { status: 403 });
+}
+const { error, data } = await supabase.auth.getUser(jwt);
+if (error) {
+console.error(error);
+return new Response("Invalid token provided", { status: 403 });
+}
+if (!data.user) {
+console.error("user is not authenticated");
+return new Response("User is not authenticated", { status: 403 });
+}
+
+ const { socket, response } = Deno.upgradeWebSocket(req);
+
+ socket.onopen = () => console.log("socket opened");
+ socket.onmessage = (e) => {
+ console.log("socket message:", e.data);
+ socket.send(new Date().toString());
+ };
+
+ socket.onerror = e => console.log("socket errored:", e.message);
+ socket.onclose = () => console.log("socket closed");
+
+ return response;
+
+});
+
+````
+
+
+```ts
+ import { createClient } from "jsr:@supabase/supabase-js@2";
+
+const supabase = createClient(
+ Deno.env.get("SUPABASE_URL"),
+ Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
+);
+ Deno.serve(req => {
+ const upgrade = req.headers.get("upgrade") || "";
+
+ if (upgrade.toLowerCase() != "websocket") {
+ return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
+ }
+
+ // Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2`
+ const customProtocols = (req.headers.get("Sec-WebSocket-Protocol") ?? '').split(',').map(p => p.trim())
+ const jwt = customProtocols.find(p => p.startsWith('jwt')).replace('jwt-', '')
+ if (!jwt) {
+ console.error("Auth token not provided");
+ return new Response("Auth token not provided", { status: 403 });
+ }
+ const { error, data } = await supabase.auth.getUser(jwt);
+ if (error) {
+ console.error(error);
+ return new Response("Invalid token provided", { status: 403 });
+ }
+ if (!data.user) {
+ console.error("user is not authenticated");
+ return new Response("User is not authenticated", { status: 403 });
+ }
+
+ const { socket, response } = Deno.upgradeWebSocket(req);
+
+ socket.onopen = () => console.log("socket opened");
+ socket.onmessage = (e) => {
+ console.log("socket message:", e.data);
+ socket.send(new Date().toString());
+ };
+
+ socket.onerror = e => console.log("socket errored:", e.message);
+ socket.onclose = () => console.log("socket closed");
+
+ return response;
+ });
+````
+
+
+
+
+### Limits
+
+The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
+
+### Testing WebSockets locally
+
+When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent keeping WebSocket connections open.
+
+To prevent that, you can update the `supabase/config.toml` with the following settings:
+
+```toml
+[edge_runtime]
+policy = "per_worker"
+```
+
+When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.