Appearance
Backend — start here
One Supabase backend: Postgres + RLS + Auth + Edge Functions + Storage, code-split by tier. This index is the entry point for anything under supabase/ — the Edge Function kit, the standards document, config.toml, migrations and the archive.
Read before changing an Edge Function or a migration
- This page · 2.
EDGE_FUNCTION_GUIDELINES.md(repo root — the standard) · 3. Shared kit · - Adding an Edge Function · 5. Database migrations ·
- Architecture change protocol when a core entity is touched. Gates:
npm run test:ef·check:sql·check:rpc·check:fn-config(⚠ needs--project, QRS-643) · after deploycheck:ef-drift. Decision rule: pure SQL read + no secrets + no external HTTP → RPC; everything else → Edge Function. Your plan must answer: whichconfig.tomlverify_jwtposture does the function declare, and which_sharedhelpers does it import (fivedatabase.tshelpers target dropped tables — QRS-573)?
Pages
- Shared kit — the
_shared/utilities and their pins - Edge Functions index — generated by
npm run docs:gen; verify the Type column against source EDGE_FUNCTION_GUIDELINES.md— at the repo root, not undersupabase/functions/supabase/docs/PROMOTION_RUNBOOK.md— the promotion mechanism, for when the owner calls for it
Edge Functions — the operating-manual statement
Provenance — moved from CLAUDE.md on 2026-09-23 (QRS-1288)
This is the verbatim text of CLAUDE.md § "Edge Functions [ENFORCED]" as of commit 00c1eca, relocated here under the context-architecture programme. Sentences of the form "this said X until [date]" are corrections recorded at the time they were made; the live rule is the corrected one. Retired vocabulary inside those corrections names what was retired and is not a live claim.
Edge Functions [ENFORCED]
Shared entrypoint (utilities in supabase/functions/_shared/):
ts
const preflight = handleCors(req); if (preflight) return preflight;
try {
const { user, serviceClient } = await requireAuth(req); // requireAdmin for admin; optionalAuth for public
const body = await req.json();
if (!body.required) throw new ValidationError('required is required.');
// business logic — complex parts in helpers.ts
return ok({ result });
} catch (e) { return err('<name>', e); }- Kit (
supabase/functions/_shared/):cors.ts(handleCors),auth.ts(requireAuth/requireAdmin/optionalAuth),errors.ts(typed errorsValidationError/AuthError/ForbiddenError/NotFoundError/ConflictError),response.ts(ok/err),logging.ts(structured JSON,eventfield on INFO/WARN), pluserror-handler.ts,database.ts,cloudflare.ts,config-cache.ts,retry.ts. Unit tests co-located in_shared/tests/(10 files). Pin@supabase/supabase-jsto one exact version (EFs:2.39.7everywhere; the frontend npm dep is separately pinned to2.111.0— ⚠ this said2.30.0until 2026-08-28; two different numbers on purpose, andbackend/shared-kit.mdgave the frontend's as the pin until 2026-08-12).- ⚠⚠ FIVE
database.tsHELPERS TARGET DROPPED TABLES. DO NOT CALL THEM (QRS-573). The ADR-0020 baseline dropped the wholepublic_page_ops_*family on 2026-08-08 and these were never updated with it:insertLogEntry,insertCacheOperation,insertCronExecution,getActiveCronJobs,getJobByName. All five are still exported and still compile, so the next person wanting "a log table helper" finds one that runs, silently fails, and swallows the error — which is exactly how the two real callers went unnoticed for four days:insertLogEntryran on every log line in every EF, andinsertCacheOperationon every card-affecting write. Both call sites are now removed; the helpers are kept only because_archive_pre_v2/imports them and that tree is excluded fromdeno check/deno test, so a file-header banner is the only available control.getSupabaseClient,batchInsertandbatchUpdateare table-agnostic and fine. LoggerandinvalidateCardCacheboth take a_clientthey deliberately ignore — the handle the dead inserts used. Do not "clean up" the parameter: 13+ call sites would churn, and it is exactly what an outbox enqueue needs back.
- ⚠⚠ FIVE
- Type A (
domain-action, JWT) · Type B (domain-noun-verb, cron/internal,--no-verify-jwt) · Webhook (HMAC signature + timestamp window + constant-time compare + idempotency viaUNIQUEevent id). - Every EF ships co-located Deno
tests/(success, validation, auth/authz, safe 500s, edge cases) — green before deploy. Register the name in the featureEDGE_FNmap. Re-rundocs:gento update the EF index. - Standards doc:
EDGE_FUNCTION_GUIDELINES.md. All EFs are unified onto the kit with a single version pin. The live set, its names, and how many carry aconfig.tomlentry are in the measured inventory at the top of this file — do not restate the count here. It has now been wrong twice: it read "eleven" (of which seven were archived and five never existed), then "SIX" until 2026-08-13.- ⚠
validate-user-inputIS DELETED (QRS-640, 2026-08-13) — from the repo and undeployed from Dev, so a call now returns 404 rather than the 500 it had been returning. Its slug branch was its only caller anywhere, and it was broken two ways at once: it bound an RPC argument by the wrong NAME (check_slugforis_slug_reserved(p_slug), and PostgREST binds by name), and its uniqueness half readsetu_cardsthrough an anon client that has no grant on it. Deleted rather than repaired, because it was the wrong layer by this file's own decision rule — a pure SQL read belongs in an RPC. Availability is nowresolve_setu_card_slug_status(text). It was also the last endpoint that took a table name from a REQUEST BODY and passed it to.from(), and the lastverify_jwt = falsefunction among the Type-A set. ⚠ Butconfig.tomldoes NOT declare none — it carries SEVENverify_jwt = falseentries (measured 2026-08-28),razorpay-webhookamong them, which is correct: a provider webhook authenticates by HMAC, not by JWT. - ✅
manage-reminder's missingconfig.tomlentry is CLOSED (2026-08-15, CR-26.0.1-39) — it is declaredverify_jwt = true, andmanage-orderwas declared in the same change. This bullet read "has a function folder and NOconfig.tomlentry" until then, and the fix exposed the part that matters: the live function on Dev was deployedverify_jwt = false. It is a Type-A user-facing function whose every action callsrequireAuth, so the body still rejected unauthenticated callers — defence in depth lost, not an open hole — but the posture was an accident rather than a decision, which is exactly what "an absent entry is a silent default" means in practice. The next deploy ofmanage-remindertightens it. ⚠ The GATE is still broken and that is the durable half:check:fn-configrequires--project <ref>and exits with USAGE without it, so it runs nowhere (QRS-643, still open). The measured block now reports 0 functions without an entry — read that as the symptom fixed, not the gate.
- ⚠
- ⚠ The v1 EF set is ARCHIVED, not deployed —
supabase/functions/_archive_pre_v2/holds twelve of them (manage-profile,manage-settings,get-public-menu,get-public-feedback, all fourpublic_page_ops_*,create-payment-link,razorpay-webhook,track-card-event, and a v1manage-reminder). They were written against the pre-ADR-0020 schema, so do not read one as prior art for a live table — most of what they touch (profiles,bio_pages,digital_menu_*) no longer exists.manage-profileis NOT the reference implementation any longer, though this file said so until 2026-08-12 andEDGE_FUNCTION_GUIDELINES.mdstill uses it as its running example throughout (its Type-A examples, its folder diagram, its logging and error samples, and its deploy command all name it). The guidelines' prose is still correct; only the file it points at is gone. Readmanage-item/index.tsormanage-reminder/index.tsfor a live example of the same shape.
- The
_sharedkit has grown well past the list above. Also present — and the first three were named nowhere in this file until 2026-08-28:commission.ts,r2.ts,workspace.ts, pluswebhook.ts,idempotency.ts,internalAuth.ts,features.ts,cardCache.ts,observability.ts,razorpay.ts. ⚠_shared/webhook.tsEXISTS (added 2026-08-03). This file cited its absence as a worked example of a false written claim right up until that claim itself went stale — which is the lesson landing twice rather than an argument against it.