.claude/skills/supabase-functions/SKILL.md
Supabase Edge Functions development and deployment using Deno runtime. Use when creating serverless functions, webhooks, API endpoints, or scheduled tasks.
npx skillsauth add adaptationio/skrillz supabase-functionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Serverless functions with Deno runtime.
| Task | Command |
|------|---------|
| Create function | supabase functions new <name> |
| Serve locally | supabase functions serve |
| Deploy | supabase functions deploy <name> |
| Delete | supabase functions delete <name> |
| Set secrets | supabase secrets set KEY=value |
| List secrets | supabase secrets list |
supabase functions new hello-world
Creates: supabase/functions/hello-world/index.ts
// supabase/functions/hello-world/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
Deno.serve(async (req: Request) => {
const { name } = await req.json()
return new Response(
JSON.stringify({ message: `Hello ${name}!` }),
{ headers: { "Content-Type": "application/json" } }
)
})
supabase functions serve
supabase functions serve --env-file .env
supabase functions serve --no-verify-jwt
curl -i --request POST \
'http://localhost:54321/functions/v1/hello-world' \
--header 'Authorization: Bearer <ANON_KEY>' \
--header 'Content-Type: application/json' \
--data '{"name":"World"}'
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
Deno.serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const { name } = await req.json()
return new Response(
JSON.stringify({ message: `Hello ${name}!` }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 400 }
)
}
})
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import { createClient } from 'jsr:@supabase/supabase-js@2'
Deno.serve(async (req: Request) => {
// Create Supabase client with service role
const supabaseAdmin = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
// Or with user's JWT (respects RLS)
const authHeader = req.headers.get('Authorization')!
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{ global: { headers: { Authorization: authHeader } } }
)
// Query with user context
const { data, error } = await supabaseClient
.from('posts')
.select('*')
return new Response(
JSON.stringify({ data, error }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import { createClient } from 'jsr:@supabase/supabase-js@2'
Deno.serve(async (req: Request) => {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
)
const { data: { user }, error } = await supabaseClient.auth.getUser()
if (error || !user) {
return new Response(
JSON.stringify({ error: 'Unauthorized' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
)
}
return new Response(
JSON.stringify({ userId: user.id, email: user.email }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
# Single secret
supabase secrets set API_KEY=abc123
# Multiple secrets
supabase secrets set API_KEY=abc123 DB_PASSWORD=secret
# From file
supabase secrets set --env-file .env
const apiKey = Deno.env.get('API_KEY')
const supabaseUrl = Deno.env.get('SUPABASE_URL')
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
SUPABASE_URL - Project URLSUPABASE_ANON_KEY - Anon keySUPABASE_SERVICE_ROLE_KEY - Service role keySUPABASE_DB_URL - Direct database connectionsupabase functions deploy hello-world
supabase functions deploy webhook-handler --no-verify-jwt
supabase functions deploy
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'World' }
})
const { data, error } = await supabase.functions.invoke('api-handler', {
body: { action: 'create' },
headers: { 'x-custom-header': 'value' }
})
// supabase/functions/stripe-webhook/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import Stripe from 'npm:stripe@14'
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
apiVersion: '2023-10-16'
})
const cryptoProvider = Stripe.createSubtleCryptoProvider()
Deno.serve(async (req: Request) => {
const signature = req.headers.get('Stripe-Signature')!
const body = await req.text()
let event: Stripe.Event
try {
event = await stripe.webhooks.constructEventAsync(
body,
signature,
Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
undefined,
cryptoProvider
)
} catch (err) {
return new Response(
JSON.stringify({ error: `Webhook Error: ${err.message}` }),
{ status: 400 }
)
}
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object
// Handle successful checkout
break
default:
console.log(`Unhandled event type ${event.type}`)
}
return new Response(JSON.stringify({ received: true }))
})
Trigger function on database changes:
-- Create webhook trigger
CREATE OR REPLACE FUNCTION notify_function()
RETURNS trigger AS $$
BEGIN
PERFORM net.http_post(
url := 'https://<project>.supabase.co/functions/v1/handle-insert',
headers := '{"Authorization": "Bearer <SERVICE_KEY>"}'::jsonb,
body := row_to_json(NEW)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_insert
AFTER INSERT ON posts
FOR EACH ROW
EXECUTE FUNCTION notify_function();
-- Using pg_cron extension
SELECT cron.schedule(
'daily-cleanup',
'0 0 * * *', -- Every day at midnight
$$
SELECT net.http_post(
url := 'https://<project>.supabase.co/functions/v1/cleanup',
headers := '{"Authorization": "Bearer <SERVICE_KEY>"}'::jsonb
)
$$
);
[functions.hello-world]
verify_jwt = true
[functions.webhook-handler]
verify_jwt = false
[functions.api-handler]
verify_jwt = true
import_map = "./supabase/functions/import_map.json"
Create _shared folder for reusable code:
supabase/functions/
├── _shared/
│ ├── cors.ts
│ └── supabase.ts
├── hello-world/
│ └── index.ts
└── api-handler/
└── index.ts
// _shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
// _shared/supabase.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'
export const supabaseAdmin = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// hello-world/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import { corsHeaders } from '../_shared/cors.ts'
import { supabaseAdmin } from '../_shared/supabase.ts'
Deno.serve(async (req: Request) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
// Use shared client
const { data } = await supabaseAdmin.from('posts').select('*')
return new Response(
JSON.stringify({ data }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
})
| Limit | Value | |-------|-------| | CPU Time | 2 seconds | | Request Timeout | 150 seconds | | Wall Clock (Pro) | 400 seconds | | Bundle Size | 20 MB |
development
Setup secure web-based terminal access to WSL2 from mobile/tablet via ttyd + ngrok/Cloudflare/Tailscale. One-command install, start, stop, status. Use when you need remote terminal access, web terminal, browser-based shell, or mobile access to WSL2 environment.
development
Complete development workflows where Claude writes the code while Gemini and Codex provide research, planning, reviews, and different perspectives. Claude remains the main developer. Use for complex projects requiring expert planning and multi-perspective reviews.
development
Systematic progress tracking for skill development. Manages task states (pending/in_progress/completed), updates in real-time, reports progress, identifies blockers, and maintains momentum. Use when tracking skill development, coordinating work, or reporting progress.
testing
Comprehensive testing workflow orchestrating functional testing, example validation, integration testing, and usability assessment. Sequential workflow for complete skill testing from examples through scenarios to integration validation. Use when conducting thorough testing, pre-deployment validation, ensuring skill functionality, or comprehensive quality checks.