{"id":49203994,"url":"https://github.com/supabase/server","last_synced_at":"2026-04-23T17:00:44.795Z","repository":{"id":353122131,"uuid":"1165809761","full_name":"supabase/server","owner":"supabase","description":"Server-side utilities for Supabase. Handles auth, client creation, and context injection so you write business logic, not boilerplate.","archived":false,"fork":false,"pushed_at":"2026-04-22T14:12:21.000Z","size":157,"stargazers_count":14,"open_issues_count":2,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T16:15:41.838Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/supabase.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["supabase"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2026-02-24T15:12:29.000Z","updated_at":"2026-04-22T14:12:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/supabase/server","commit_stats":null,"previous_names":["supabase/server"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/supabase/server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/supabase","download_url":"https://codeload.github.com/supabase/server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/supabase%2Fserver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32189656,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T15:28:30.493Z","status":"ssl_error","status_checked_at":"2026-04-23T15:28:29.972Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-23T17:00:41.532Z","updated_at":"2026-04-23T17:00:44.775Z","avatar_url":"https://github.com/supabase.png","language":"TypeScript","funding_links":["https://github.com/sponsors/supabase"],"categories":[],"sub_categories":[],"readme":"# @supabase/server\n\n[![License](https://img.shields.io/npm/l/nx.svg?style=flat-square)](./LICENSE)\n[![Package](https://img.shields.io/npm/v/@supabase/server)](https://www.npmjs.com/package/@supabase/server)\n[![pkg.pr.new](https://pkg.pr.new/badge/supabase/server)](https://pkg.pr.new/~/supabase/server)\n\n\u003e **Beta:** This package is under active development. APIs and documentation may change. If you find a bug or have a feature request, please [open an issue](https://github.com/supabase/server/issues) or [submit a PR](https://github.com/supabase/server/blob/main/CONTRIBUTING.md).\n\n`@supabase/server` gives you batteries included access to the\n[supabase-js SDK](https://github.com/supabase/supabase-js), including client\ncreation and authentication automatically scoped to the inbound requests to your\nEdge Functions and APIs.\n\n```ts\nimport { withSupabase } from '@supabase/server'\n\nexport default {\n  fetch: withSupabase({ allow: 'user' }, async (_req, ctx) =\u003e {\n    // RLS-scoped — this user only sees their own favorites\n    const { data: myGames } = await ctx.supabase.from('favorite_games').select()\n    return Response.json(myGames)\n  }),\n}\n```\n\nOne import. One line of config. Auth is validated, clients are ready, CORS is handled. Your handler only runs on successful auth.\n\n## Installation\n\n```bash\n# Deno / Supabase Edge Functions (no install — import directly)\nimport { withSupabase } from \"npm:@supabase/server\";\n\n# npm\nnpm install @supabase/server\n\n# pnpm\npnpm add @supabase/server\n```\n\n### AI coding skills\n\nInstall the skill so your AI coding agent (Claude Code, Cursor, etc.) knows how to use this package:\n\n```bash\nnpx skills add supabase/server\n```\n\n## Quick Start\n\nImagine you're building an app where users track their favorite games. They sign in and manage their own list. An admin dashboard curates featured titles. A cron job refreshes the \"popular this week\" rankings. Here's how each piece looks:\n\n### Authenticated endpoint\n\n```ts\n// A signed-in user fetches their favorite games.\nexport default {\n  fetch: withSupabase({ allow: 'user' }, async (_req, ctx) =\u003e {\n    const { supabase, supabaseAdmin, userClaims, claims, authType } = ctx\n    // supabase       — RLS-scoped to the authenticated user\n    // supabaseAdmin  — bypasses RLS (service role)\n    // userClaims     — user identity from JWT (id, email, role)\n    // claims         — full JWT claims\n    // authType       — which auth mode matched\n\n    // RLS-scoped — this user only sees their own favorites\n    const { data: myGames } = await supabase.from('favorite_games').select()\n    return Response.json(myGames)\n  }),\n}\n```\n\n### Public endpoint (no auth)\n\n```ts\n// The frontend hits this before showing the login screen.\n// allow: 'always' means no credentials required.\nexport default {\n  fetch: withSupabase({ allow: 'always' }, async (_req, _ctx) =\u003e {\n    return Response.json({ status: 'ok' })\n  }),\n}\n```\n\n### API key protected\n\n```ts\n// An admin dashboard fetches the list of featured games to curate.\n// Secret key auth (not a user JWT) — supabaseAdmin bypasses RLS.\nexport default {\n  fetch: withSupabase({ allow: 'secret' }, async (_req, ctx) =\u003e {\n    const { data: featuredGames } = await ctx.supabaseAdmin\n      .from('featured_games')\n      .select()\n    return Response.json(featuredGames)\n  }),\n}\n```\n\n### Dual auth (user or service)\n\n```ts\n// Users view their own play stats from the app (JWT).\n// A backend service pulls stats for any user (secret key + user_id in body).\nexport default {\n  fetch: withSupabase({ allow: ['user', 'secret'] }, async (req, ctx) =\u003e {\n    const callerIsUser = ctx.authType === 'user'\n\n    if (callerIsUser) {\n      // RLS-scoped — the database enforces \"own stats only\"\n      const { data: myStats } = await ctx.supabase.from('play_stats').select()\n      return Response.json(myStats)\n    }\n\n    // Service path — bypass RLS to pull stats for any user\n    const { user_id } = await req.json()\n    const { data: playStats } = await ctx.supabaseAdmin\n      .from('play_stats')\n      .select()\n      .eq('user_id', user_id)\n    return Response.json(playStats)\n  }),\n}\n```\n\n### Server-to-server\n\n```ts\n// A cron job refreshes the \"popular this week\" list every hour.\n// Named key (\"cron\") so it can be rotated without touching other services.\nexport default {\n  fetch: withSupabase({ allow: 'secret:cron' }, async (_req, ctx) =\u003e {\n    const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)\n    const { data: popularThisWeek } = await ctx.supabaseAdmin.rpc(\n      'get_most_favorited_since',\n      { since: oneWeekAgo.toISOString(), limit_count: 10 },\n    )\n    await ctx.supabaseAdmin\n      .from('featured_games')\n      .upsert(\n        popularThisWeek.map((g) =\u003e ({ game_id: g.id, reason: 'popular' })),\n      )\n    return Response.json({ popularThisWeek })\n  }),\n}\n```\n\nThe cron job sends the named secret key in the `apikey` header:\n\n```ts\nconst refreshEndpoint =\n  'https://\u003cproject\u003e.supabase.co/functions/v1/refresh-popular'\nconst cronKey = 'sb_secret_...' // the \"cron\" named secret key\n\nawait fetch(refreshEndpoint, {\n  method: 'POST',\n  headers: { apikey: cronKey },\n})\n```\n\n## Auth Modes\n\n| Mode               | Credential            | Use case                                            |\n| ------------------ | --------------------- | --------------------------------------------------- |\n| `\"user\"` (default) | Valid JWT             | Authenticated user endpoints                        |\n| `\"public\"`         | Valid publishable key | Client-facing, key-validated endpoints              |\n| `\"secret\"`         | Valid secret key      | Server-to-server, internal calls                    |\n| `\"always\"`         | None                  | Open endpoints, wrappers that handle their own auth |\n\nArray syntax (`allow: [\"user\", \"secret\"]`) accepts multiple auth methods — first match wins. An absent credential falls through to the next mode; a present-but-invalid JWT rejects the request (no silent downgrade). See [`docs/auth-modes.md`](docs/auth-modes.md).\n\nNamed key validation: `allow: \"public:web_app\"` or `allow: \"secret:automations\"` validates against a specific named key in `SUPABASE_PUBLISHABLE_KEYS` or `SUPABASE_SECRET_KEYS`.\n\n\u003e **Supabase Edge Functions:** By default, the platform requires a valid JWT on every request. If your function uses `allow: 'public'`, `allow: 'secret'`, or `allow: 'always'`, disable the platform-level JWT check in `supabase/config.toml`:\n\u003e\n\u003e ```toml\n\u003e [functions.my-function]\n\u003e verify_jwt = false\n\u003e ```\n\n## Context\n\nEvery handler receives a `SupabaseContext`:\n\n```ts\ninterface SupabaseContext {\n  supabase: SupabaseClient // RLS-scoped (user or anon depending on auth)\n  supabaseAdmin: SupabaseClient // Bypasses RLS\n  userClaims: UserClaims | null // JWT-derived identity (for full User, call supabase.auth.getUser())\n  claims: JWTClaims | null // Present when auth is JWT\n  authType: Allow // Which auth mode matched\n  authKeyName?: string | null // Auth key name of the API key that was used for this request\n}\n```\n\n`supabase` is always the safe client — it respects RLS. When `authType` is `\"user\"`, it's scoped to that user's permissions. Otherwise, it's initialized as anonymous.\n\n`supabaseAdmin` always bypasses RLS. Use it for operations that need full database access.\n\n## Config\n\n```ts\nwithSupabase(\n  {\n    allow: 'user', // who can call this function\n    cors: false, // disable CORS (default: supabase-js CORS headers)\n    env: { url: '...' }, // env overrides (optional)\n  },\n  handler,\n)\n```\n\n`cors` defaults to the standard [supabase-js CORS headers](https://supabase.com/docs/guides/functions/cors). Pass a `Record\u003cstring, string\u003e` to set custom headers, or `false` to disable CORS handling (e.g. when using a framework that handles CORS separately).\n\n```ts\nwithSupabase(\n  {\n    allow: 'user',\n    cors: {\n      'Access-Control-Allow-Origin': 'https://myapp.com',\n      'Access-Control-Allow-Headers': 'authorization, content-type',\n    },\n  },\n  handler,\n)\n```\n\n`env` overrides environment variable resolution. Defaults to reading `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEYS`, `SUPABASE_SECRET_KEYS`, and `SUPABASE_JWKS` from the runtime environment.\n\n## Framework Adapters\n\n### Hono\n\n```ts\nimport { Hono } from 'hono'\nimport { withSupabase } from '@supabase/server/adapters/hono'\n\nconst app = new Hono()\n\n// Protected — withSupabase middleware validates the JWT before the handler runs\napp.get('/games', withSupabase({ allow: 'user' }), async (c) =\u003e {\n  const { supabase } = c.var.supabaseContext\n  const { data: myGames } = await supabase.from('favorite_games').select()\n  return c.json(myGames)\n})\n\n// Public — no middleware means no auth\napp.get('/health', (c) =\u003e c.json({ status: 'ok' }))\n\nexport default { fetch: app.fetch }\n```\n\nThe adapter does not handle CORS — use `hono/cors` for that. Per-route auth works naturally by applying the middleware to specific routes.\n\n## Primitives\n\nFor when you need more control than `withSupabase` provides — multiple routes with different auth, custom response headers, or building your own wrapper.\n\nAll primitives are available from `@supabase/server/core`.\n\n```ts\nimport {\n  verifyAuth,\n  createContextClient,\n  createAdminClient,\n} from '@supabase/server/core'\n```\n\n### verifyAuth\n\nExtracts credentials from a Request and validates against the allow config.\n\n```ts\nconst { data: auth, error } = await verifyAuth(req, { allow: 'user' })\nif (error) {\n  return Response.json({ message: error.message }, { status: error.status })\n}\n```\n\n### verifyCredentials\n\nLow-level — works with raw credentials instead of a Request. Used by SSR adapters and custom auth flows.\n\n```ts\nconst credentials = { token: myToken, apikey: null }\nconst { data: auth, error } = await verifyCredentials(credentials, {\n  allow: 'user',\n})\n```\n\n### createContextClient / createAdminClient\n\n```ts\nconst userScopedClient = createContextClient(auth.token) // RLS applies as this user\nconst anonClient = createContextClient() // RLS applies as anon role\nconst adminClient = createAdminClient() // bypasses RLS entirely\n```\n\n### createSupabaseContext\n\nFull context assembly from a Request — `verifyAuth` + client creation in one call.\n\n```ts\nconst { data: ctx, error } = await createSupabaseContext(req, { allow: 'user' })\n```\n\n### resolveEnv\n\nResolves environment variables with optional overrides.\n\n```ts\nconst { data: env, error } = resolveEnv({\n  url: process.env.NEXT_PUBLIC_SUPABASE_URL,\n})\n```\n\n### Example: custom multi-route handler\n\nThe same games API and health check from the Hono example, built from primitives instead of a framework:\n\n```ts\nimport { verifyAuth, createContextClient } from '@supabase/server/core'\n\nexport default {\n  fetch: async (req) =\u003e {\n    const url = new URL(req.url)\n\n    // Public — no auth needed\n    if (url.pathname === '/health') {\n      return Response.json({ status: 'ok' })\n    }\n\n    // Protected — verify the JWT, then create a user-scoped client\n    if (url.pathname === '/games') {\n      const { data: auth, error } = await verifyAuth(req, { allow: 'user' })\n      if (error)\n        return Response.json(\n          { message: error.message },\n          { status: error.status },\n        )\n\n      const userScopedClient = createContextClient(auth.token)\n      const { data: myGames } = await userScopedClient\n        .from('favorite_games')\n        .select()\n      return Response.json(myGames)\n    }\n\n    return new Response('Not found', { status: 404 })\n  },\n}\n```\n\n## Environment Variables\n\nAutomatically available in Supabase Edge Functions:\n\n| Variable                    | Format                                                        | Description                           |\n| --------------------------- | ------------------------------------------------------------- | ------------------------------------- |\n| `SUPABASE_URL`              | `https://\u003cref\u003e.supabase.co`                                   | Your project URL                      |\n| `SUPABASE_PUBLISHABLE_KEYS` | `{\"default\":\"sb_publishable_...\",\"web\":\"sb_publishable_...\"}` | Publishable API keys (named)          |\n| `SUPABASE_SECRET_KEYS`      | `{\"default\":\"sb_secret_...\",\"web\":\"sb_secret_...\"}`           | Secret API keys (named)               |\n| `SUPABASE_JWKS`             | `{\"keys\":[...]}` or `[...]`                                   | JSON Web Key Set for JWT verification |\n\nAlso supported (for local dev, self-hosted, or other runtimes):\n\n| Variable                   | Format               | Description            |\n| -------------------------- | -------------------- | ---------------------- |\n| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key |\n| `SUPABASE_SECRET_KEY`      | `sb_secret_...`      | Single secret key      |\n\nWhen both singular and plural forms are set, plural takes priority.\n\nFor other environments, pass overrides via the `env` config option or `resolveEnv()`. See [`docs/environment-variables.md`](docs/environment-variables.md) for details.\n\n## Runtimes\n\n- **Supabase Edge Functions** — environment variables are auto-injected. Zero config.\n- **Deno / Bun** — works out of the box with the `export default { fetch }` pattern.\n- **Node.js** — use the [Hono adapter](#hono) or [core primitives](#primitives) with your framework of choice.\n- **Cloudflare Workers** — enable `nodejs_compat` in `wrangler.toml` or pass env overrides via the `env` config option.\n- **Next.js / Nuxt / SvelteKit / Remix** — use core primitives to build a cookie-based auth adapter. See [`docs/ssr-frameworks.md`](docs/ssr-frameworks.md).\n\n## Exports\n\n| Export                           | What's in it                                                                                                      |\n| -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |\n| `@supabase/server`               | `withSupabase`, `createSupabaseContext`                                                                           |\n| `@supabase/server/core`          | `verifyAuth`, `verifyCredentials`, `extractCredentials`, `createContextClient`, `createAdminClient`, `resolveEnv` |\n| `@supabase/server/adapters/hono` | `withSupabase` (Hono middleware)                                                                                  |\n\n## Documentation\n\n| Question                                                 | Doc file                                                         |\n| -------------------------------------------------------- | ---------------------------------------------------------------- |\n| How do I create a basic endpoint?                        | [`docs/getting-started.md`](docs/getting-started.md)             |\n| What auth modes are available? Array syntax? Named keys? | [`docs/auth-modes.md`](docs/auth-modes.md)                       |\n| How do I use this with Hono?                             | [`docs/hono-adapter.md`](docs/hono-adapter.md)                   |\n| How do I use low-level primitives for custom flows?      | [`docs/core-primitives.md`](docs/core-primitives.md)             |\n| How do environment variables work across runtimes?       | [`docs/environment-variables.md`](docs/environment-variables.md) |\n| How do I handle errors? What codes exist?                | [`docs/error-handling.md`](docs/error-handling.md)               |\n| How do I get typed database queries?                     | [`docs/typescript-generics.md`](docs/typescript-generics.md)     |\n| How do I use this in Next.js, Nuxt, SvelteKit, or Remix? | [`docs/ssr-frameworks.md`](docs/ssr-frameworks.md)               |\n| What's the complete API surface?                         | [`docs/api-reference.md`](docs/api-reference.md)                 |\n\n## Development\n\n```bash\npnpm install\npnpm dev\n```\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, commit conventions, and release process.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase%2Fserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsupabase%2Fserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupabase%2Fserver/lists"}