Skip to content

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

  1. This page · 2. EDGE_FUNCTION_GUIDELINES.md (repo root — the standard) · 3. Shared kit ·
  2. Adding an Edge Function · 5. Database migrations ·
  3. 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 deploy check:ef-drift. Decision rule: pure SQL read + no secrets + no external HTTP → RPC; everything else → Edge Function. Your plan must answer: which config.toml verify_jwt posture does the function declare, and which _shared helpers does it import (five database.ts helpers 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 under supabase/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 errors ValidationError/AuthError/ForbiddenError/NotFoundError/ConflictError), response.ts (ok/err), logging.ts (structured JSON, event field on INFO/WARN), plus error-handler.ts, database.ts, cloudflare.ts, config-cache.ts, retry.ts. Unit tests co-located in _shared/tests/ (10 files). Pin @supabase/supabase-js to one exact version (EFs: 2.39.7 everywhere; the frontend npm dep is separately pinned to 2.111.0 — ⚠ this said 2.30.0 until 2026-08-28; two different numbers on purpose, and backend/shared-kit.md gave the frontend's as the pin until 2026-08-12).
    • ⚠⚠ FIVE database.ts HELPERS TARGET DROPPED TABLES. DO NOT CALL THEM (QRS-573). The ADR-0020 baseline dropped the whole public_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: insertLogEntry ran on every log line in every EF, and insertCacheOperation on 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 from deno check/deno test, so a file-header banner is the only available control. getSupabaseClient, batchInsert and batchUpdate are table-agnostic and fine.
    • Logger and invalidateCardCache both take a _client they 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.
  • Type A (domain-action, JWT) · Type B (domain-noun-verb, cron/internal, --no-verify-jwt) · Webhook (HMAC signature + timestamp window + constant-time compare + idempotency via UNIQUE event id).
  • Every EF ships co-located Deno tests/ (success, validation, auth/authz, safe 500s, edge cases) — green before deploy. Register the name in the feature EDGE_FN map. Re-run docs:gen to 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 a config.toml entry 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-input IS 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_slug for is_slug_reserved(p_slug), and PostgREST binds by name), and its uniqueness half read setu_cards through 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 now resolve_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 last verify_jwt = false function among the Type-A set. ⚠ But config.toml does NOT declare none — it carries SEVEN verify_jwt = false entries (measured 2026-08-28), razorpay-webhook among them, which is correct: a provider webhook authenticates by HMAC, not by JWT.
    • ✅ manage-reminder's missing config.toml entry is CLOSED (2026-08-15, CR-26.0.1-39) — it is declared verify_jwt = true, and manage-order was declared in the same change. This bullet read "has a function folder and NO config.toml entry" until then, and the fix exposed the part that matters: the live function on Dev was deployed verify_jwt = false. It is a Type-A user-facing function whose every action calls requireAuth, 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 of manage-reminder tightens it. ⚠ The GATE is still broken and that is the durable half: check:fn-config requires --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 four public_page_ops_*, create-payment-link, razorpay-webhook, track-card-event, and a v1 manage-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-profile is NOT the reference implementation any longer, though this file said so until 2026-08-12 and EDGE_FUNCTION_GUIDELINES.md still 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. Read manage-item/index.ts or manage-reminder/index.ts for a live example of the same shape.
  • The _shared kit 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, plus webhook.ts, idempotency.ts, internalAuth.ts, features.ts, cardCache.ts, observability.ts, razorpay.ts. ⚠ _shared/webhook.ts EXISTS (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.