Files
supabase/apps/docs/content/guides/functions/examples/cloudflare-turnstile.mdx
Tomás Pozo adbd3c3d22 docs: migrate Edge Functions guides to @supabase/server (COM-269) (#46656)
## What

Migrates the Edge Functions **documentation** from the legacy
`Deno.serve` + manual `createClient` pattern to the
[`@supabase/server`](https://github.com/supabase/server) `withSupabase`
wrapper. This is the part of
[COM-269](https://linear.app/supabase/issue/COM-269) that AI coding
assistants index, so it's split out to ship first; the standalone
`examples/` functions follow in a second PR.

## Canonical pattern

```ts
import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
    const { data } = await ctx.supabase.from('countries').select('*')
    return Response.json({ data })
  }),
}
```

- `export default { fetch }` object shape (not `Deno.serve`, not a bare
default export), versioned `npm:@supabase/server@1`.
- `auth` mode picks the caller: `user` → `ctx.supabase` (RLS);
`secret`/`publishable`/`none` → set `verify_jwt = false`, `secret` uses
`ctx.supabaseAdmin`.
- `Response.json(...)` over `new Response(JSON.stringify(...))`.

## Changes

- **AI prompt** (`examples/prompts/edge-functions.md`) — rewritten to
lead with `withSupabase` as the default; `auth`-mode table;
`@supabase/server@1`. Highest AI-indexing impact.
- **connect-to-postgres** — "Using supabase-js" now uses `ctx.supabase`
(+ its CodeSample deps `postgres-on-the-edge`, `drizzle`).
- **Example pages** — semantic-search, push-notifications,
amazon-bedrock, cloudflare-turnstile, og-image, send-emails,
slack-bot-mention, auth-send-email-hook.
- **Guides** — ai-models, background-tasks, routing (+ `restful-tasks`
dep), kysely-postgres, sentry-monitoring, upstash-redis, elevenlabs ×2,
websockets, cors (reframed: CORS is automatic with `withSupabase`).

## Notable fixes

- **websockets**: the JWT-auth examples had a latent bug — handler
wasn't `async` and called `getClaims()` without the extracted token. Now
`await supabase.auth.getUser(jwt)`. (`withSupabase` can't authenticate
WebSocket clients since they can't send headers — noted in the page.)
- **restful-tasks**: fixed a broken `npm:supabase-js` import →
`npm:@supabase/supabase-js`.

## Follow-ups (not in this PR)

- The ~42 standalone `examples/` edge functions → second PR.
- A dedicated `withSupabase` intro page (today it's only documented
inside the auth-framed "Securing Edge Functions" page).
- `.claude/skills/supabase-server/SKILL.md` is stale (`allow:` vs
`auth:`).

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

* **Documentation**
* Updated Edge Function examples to the modern withSupabase + exported
fetch handler pattern across guides and examples.
* Standardized JSON response/error handling (uses built-in JSON helpers)
and preserved streaming/SSE behaviors where applicable.
* Clarified auth modes, context clients (user vs admin), and automatic
CORS handling; removed manual preflight boilerplate.
* Updated local serve/deploy instructions to include --no-verify-jwt for
relevant examples.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-08 12:32:15 -05:00

80 lines
2.3 KiB
Plaintext

---
id: 'examples-cloudflare-turnstile'
title: 'CAPTCHA support with Cloudflare Turnstile'
description: 'Protecting Forms with Cloudflare Turnstile.'
tocVideo: 'OwW0znboh60'
---
[Cloudflare Turnstile](https://www.cloudflare.com/application-services/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile).
## Setup
- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/
- Add the Cloudflare Turnstile widget to your site: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/
## Code
Create a new function in your project:
```bash
supabase functions new cloudflare-turnstile
```
And add the code to the `index.ts` file:
```ts index.ts
import { withSupabase } from 'npm:@supabase/server@^1'
console.log('Hello from Cloudflare Trunstile!')
function ips(req: Request) {
return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/)
}
// `withSupabase` handles CORS and preflight requests for you.
export default {
fetch: withSupabase({ auth: 'none' }, async (req) => {
const { token } = await req.json()
const clientIps = ips(req) || ['']
const ip = clientIps[0]
// Validate the token by calling the
// "/siteverify" API endpoint.
let formData = new FormData()
formData.append('secret', Deno.env.get('CLOUDFLARE_SECRET_KEY') ?? '')
formData.append('response', token)
formData.append('remoteip', ip)
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const result = await fetch(url, {
body: formData,
method: 'POST',
})
const outcome = await result.json()
console.log(outcome)
if (outcome.success) {
return new Response('success')
}
return new Response('failure')
}),
}
```
## Deploy the server-side validation Edge Functions
- https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
```bash
supabase functions deploy cloudflare-turnstile --no-verify-jwt
supabase secrets set CLOUDFLARE_SECRET_KEY=your_secret_key
```
## Invoke the function from your site
```js
const { data, error } = await supabase.functions.invoke('cloudflare-turnstile', {
body: { token },
})
```