Files
supabase/apps/studio/components/interfaces/Functions/Functions.templates.ts
Kalleby Santos f584fdc975 feat: refactor edge-functions templates (#45234)
## What kind of change does this PR introduce?

feature

## What is the new behavior?

Update dashboard templates to use new `@supbase/server` SDK

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Updates**
* Standardized edge function templates to use a unified request handler
with built-in Supabase context, improved secret-based flows, and
consistent handling of OPTIONS, streaming, binary, and websocket
responses.
* Unified error handling to return consistent JSON error and simplified
success/unauthorized payloads across AI, database, storage, webhook,
email, image, and websocket templates.

* **Documentation**
* Guide examples and text updated to use the revised auth mode naming
(ctx.authMode).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-07 01:27:15 +01:00

437 lines
12 KiB
TypeScript

export const EDGE_FUNCTION_TEMPLATES = [
{
value: 'hello-world',
name: 'Simple Hello World',
description: 'Basic function that returns a JSON response',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
interface ReqPayload {
name: string;
}
console.info("server started");
export default {
fetch: withSupabase({ auth: ["publishable", "secret"] }, async (req, ctx) => {
const { name }: ReqPayload = await req.json();
// Using 'sb_secret_xyz' bypasses RLS — use for privileged operations
if (ctx.authType === "secret") {
return Response.json({
message: \`Hello \${name} admin!\`,
});
}
return Response.json({
message: \`Hello \${name}!\`,
});
}),
};`,
},
{
value: 'database-access',
name: 'Supabase Database Access',
description: 'Example using Supabase client to query your database',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
// This endpoint uses 'user' access, credentials is required.
export default {
fetch: withSupabase({ auth: "user" }, async (_req, { supabase }) => {
// TODO: Change the table_name to your table
const { data, error } = await supabase.from("table_name").select("*");
if (error) {
return Response.json(
{ error: error.message },
{ status: 500 },
);
}
return Response.json({ data });
}),
};`,
},
{
value: 'storage-upload',
name: 'Supabase Storage Upload',
description: 'Upload files to Supabase Storage',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
import { randomUUID } from "node:crypto"
export default {
fetch: withSupabase({ auth: "publishable" }, async (req, { supabase }) => {
const formData = await req.formData()
const file = formData.get('file')
// TODO: update your-bucket to the bucket you want to write files
const { data, error } = await supabase
.storage
.from('your-bucket')
.upload(
\`\${file.name}-\${randomUUID()}\`,
file,
{ contentType: file.type }
)
if (error) {
return Response.json(
{ error: error.message },
{ status: 500 },
);
}
return Response.json({ data });
}),
};`,
},
{
value: 'node-api',
name: 'Node Built-in API Example',
description: 'Example using Node.js built-in crypto and http modules',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { randomBytes } from "node:crypto";
import { createServer } from "node:http";
import process from "node:process";
const generateRandomString = (length) => {
const buffer = randomBytes(length);
return buffer.toString('hex');
};
const randomString = generateRandomString(10);
console.log(randomString);
const server = createServer((req, res) => {
const message = \`Hello\`;
res.end(message);
});
server.listen(9999);`,
},
{
value: 'express',
name: 'Express Server',
description: 'Example using Express.js for routing',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import express from "npm:express@4.18.2";
const app = express();
// TODO: replace slug with Function's slug
// https://supabase.com/docs/guides/functions/routing?queryGroups=framework&framework=expressjs
app.get(/slug/(.*)/, (req, res) => {
res.send("Welcome to Supabase");
});
app.listen(8000);`,
},
{
value: 'stream-text-with-ai-sdk',
name: 'Stream text with AI SDK',
description: 'Generate and stream text with Vercel AI SDK',
content: `/*
* Setup OPENAI_API_KEY secret to get started.
* For usage with useChat, point transport.api to this endpoint
* and include your publishable key as ApiKey: <key> in transport.headers.
*/
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
import { createOpenAI } from "npm:@ai-sdk/openai";
import { convertToModelMessages, streamText } from "npm:ai";
const cors = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "authorization, content-type",
"Access-Control-Max-Age": "3600",
Vary: "Access-Control-Request-Headers",
};
class ClientError extends Error {}
const openai = createOpenAI({
apiKey: Deno.env.get("OPENAI_API_KEY"),
});
const SYSTEM_PROMPT = "You are a helpful AI assistant.";
export default {
fetch: withSupabase({ auth: "publishable", cors }, async (req, _ctx) => {
try {
const body = await req.json().catch(() => {
throw new ClientError("Invalid JSON payload");
}) as { messages?: unknown; model?: unknown };
const { messages, model: modelName } = body;
if (!Array.isArray(messages)) {
throw new ClientError("Request must include a messages array");
}
const normalizedMessages = await convertToModelMessages(messages);
const model = openai(
typeof modelName === "string" ? modelName : "gpt-5.1-chat-latest",
);
const result = streamText({
model,
messages: normalizedMessages,
system: SYSTEM_PROMPT,
});
return result.toUIMessageStreamResponse({
sendReasoning: true,
sendSources: true,
});
} catch (err) {
if (err instanceof ClientError) {
return Response.json({ error: err.message }, { status: 400 });
}
console.error("Assistant chat error:", err);
return Response.json({
error: "Failed to process chat request",
details: err instanceof Error ? err.message : String(err),
}, { status: 500 });
}
}),
};`,
},
{
value: 'generate-recipes-with-ai-sdk',
name: 'Generate recipes with AI SDK',
description: 'Generate structured cooking recipes with Vercel AI SDK',
content: `/*
* 1) Setup OPENAI_API_KEY secret to get started.
* 2) Call this endpoint with { prompt, model? } to generate a recipe object matching the schema below.
*/
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
import { createOpenAI } from "npm:@ai-sdk/openai";
import { generateText, Output } from "npm:ai";
import { z } from "npm:zod";
const cors = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "authorization, content-type",
"Access-Control-Max-Age": "3600",
Vary: "Access-Control-Request-Headers",
};
class ClientError extends Error {}
const openai = createOpenAI({
apiKey: Deno.env.get("OPENAI_API_KEY"),
});
const RecipeSchema = z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(z.string()),
steps: z.array(z.string()),
}),
});
const SYSTEM_PROMPT =
"You are a recipe generator. Always return a structured recipe matching the given schema.";
export default {
fetch: withSupabase({ auth: "publishable", cors }, async (req, _ctx) => {
try {
const body = await req.json().catch(() => {
throw new ClientError("Invalid JSON payload");
}) as {
model?: unknown;
prompt?: unknown;
};
const { model: modelName, prompt } = body;
if (typeof prompt !== "string" || !prompt.trim()) {
throw new ClientError("Request must include a non-empty prompt string");
}
const model = openai(
typeof modelName === "string" ? modelName : "gpt-5.1-chat-latest",
);
const result = await generateText({
model,
system: SYSTEM_PROMPT,
prompt,
output: Output.object({
schema: RecipeSchema,
}),
});
return Response.json(result.output, { status: 200 });
} catch (err) {
if (err instanceof ClientError) {
return Response.json({ error: err.message }, { status: 400 });
}
console.error("generateText error:", err);
console.error("Assistant chat error:", err);
return Response.json({
error: "Failed to process generateText request",
details: err instanceof Error ? err.message : String(err),
}, { status: 500 });
}
}),
};`,
},
{
value: 'stripe-webhook',
name: 'Stripe Webhook Example',
description: 'Handle Stripe webhook events securely',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
import Stripe from "npm:stripe";
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!);
export default {
fetch: withSupabase({ auth: "none" }, async (req, { supabaseAdmin }) => {
const body = await req.text();
const sig = req.headers.get("stripe-signature")!;
let event: Stripe.Event;
try {
event = await stripe.webhooks.constructEventAsync(
body,
sig,
Deno.env.get("STRIPE_WEBHOOK_SECRET")!,
);
} catch {
return Response.json({ error: "Invalid signature" }, { status: 401 });
}
/*
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
await supabaseAdmin
.from("orders")
.update({ status: "paid" })
.eq("stripe_session_id", session.id);
break;
}
}
*/
console.log(\`🔔 Event received: \${event.id}\`)
return Response.json({ received: true });
}),
};
`,
},
{
value: 'resend-email',
name: 'Send Emails',
description: 'Send emails using the Resend API',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
const RESEND_API_KEY = Deno.env.get("RESEND_API_KEY")!;
export default {
fetch: withSupabase({ auth: "user" }, async (req, _ctx) => {
const { to, subject, html } = await req.json();
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: \`Bearer \${RESEND_API_KEY}\`,
},
body: JSON.stringify({
from: "you@example.com",
to,
subject,
html,
}),
});
const data = await res.json();
return Response.json(data);
}),
};`,
},
{
value: 'image-transform',
name: 'Image Transformation',
description: 'Transform images using ImageMagick WASM',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
import {
ImageMagick,
initializeImageMagick,
} from "npm:@imagemagick/magick-wasm@0.0.30";
await initializeImageMagick();
export default {
fetch: withSupabase({ auth: "publishable" }, async (req, _ctx) => {
const formData = await req.formData();
const file = formData.get("file");
const content = await file.arrayBuffer();
const result = await ImageMagick.read(new Uint8Array(content), (img) => {
img.resize(500, 300);
img.blur(60, 5);
return img.write((data) => data);
});
return new Response(
result,
{ headers: { "Content-Type": "image/png" } },
);
}),
};`,
},
{
value: 'websocket-server',
name: 'WebSocket Server Example',
description: 'Create a real-time WebSocket server',
content: `// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { withSupabase } from "jsr:@supabase/server@^1";
export default {
fetch: withSupabase({ auth: "publishable" }, async (req, _ctx) => {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() != "websocket") {
return new Response("request isn't trying to upgrade to websocket.");
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => {
console.log("client connected!");
socket.send("Welcome to Supabase Edge Functions!");
};
socket.onmessage = (e) => {
console.log("client sent message:", e.data);
socket.send(new Date().toString());
};
return response;
}),
};`,
},
]