# Supacharger Documentation > Documentation for building, configuring, deploying, and operating Supacharger applications with Next.js, Supabase, Stripe, and Vercel. This is the complete public Supacharger documentation bundle in Markdown. Use llms.txt when selective retrieval is possible. Use this file only when an agent needs a single offline document or must search across the full documentation set. ## Transactional email templates Supacharger uses Supabase Auth templates for account confirmation, OTP and magic-link sign-in, recovery, invitations, email changes, reauthentication, and security notifications. Install hosted templates under **Supabase → Authentication → Email Templates**. Files referenced by `supabase/config.toml` configure only the local CLI stack; editing one environment does not update the other. ## Before customizing Configure [production SMTP](../../Application%20Development/smtp.md) first. New Free plan projects cannot customize Auth templates while using Supabase's default SMTP service. Then confirm these settings: - the canonical Site URL and allowed redirects; - `PASSWORDLESS_EMAIL` mode and `OTP_LENGTH` in `src/supacharger.config.ts`; - `SIGN_UP_EMAIL_VERIFICATION` in `src/supacharger.config.ts`; - hosted Confirm Email policy; - Email OTP Expiration; and - whether secure email change is enabled. Template URLs and copy must match those settings. ## Template set Review every enabled authentication template: - Confirm signup - Magic link or OTP - Reset password - Invite user - Change email address - Reauthentication Also enable appropriate notifications for password, email, phone, sign-in-method, and verification-method changes. The [Supabase Auth template examples](./free-supabase-email-templates.md) provide small, auditable OTP and link examples. Apply product branding only after the destinations and token variables work. ## Which template Supabase sends The API method does not select a template by name. Supabase combines the requested operation with the account's current state and the hosted **Confirm Email** policy. In particular, `shouldCreateUser: false` prevents an unknown address from being registered; it does not force the **Magic link or OTP** template for an account that exists but is still unconfirmed. | Request | Account state | Relevant option or policy | Result | Hosted template | | --- | --- | --- | --- | --- | | Email-and-password signup | Unknown address | Confirm Email enabled | Creates an unconfirmed user and sends signup verification | **Confirm signup** | | Email-and-password signup | Unknown address | Confirm Email disabled | Creates and confirms the user immediately; no verification message is needed | None | | Email-and-password signup | Existing, unconfirmed account | Confirm Email enabled | Treats the request as another attempt to complete signup, subject to email rate limits | **Confirm signup** | | Email-and-password signup | Existing, confirmed account | Confirm Email enabled | Returns an obfuscated existing-account response to avoid disclosing registration state | None | | Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: true` and Confirm Email enabled | Creates an unconfirmed user and starts signup verification | **Confirm signup** | | Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: true` and Confirm Email disabled | Creates and confirms the user, then sends the passwordless credential | **Magic link or OTP** | | Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: false` | Rejects the request with `otp_disabled`; no user is created | None | | Passwordless `signInWithOtp()` | Existing, unconfirmed account | Either `shouldCreateUser` value | Treats the account as an incomplete signup | **Confirm signup** | | Passwordless `signInWithOtp()` | Existing, confirmed account | Either `shouldCreateUser` value | Starts passwordless sign-in | **Magic link or OTP** | | Resend signup verification | Existing, unconfirmed account | Resend type `signup` | Sends another signup credential, subject to email rate limits | **Confirm signup** | | Password recovery | Existing account | Recovery request | Sends the password-reset credential | **Reset password** | | Password recovery | Unknown address | Recovery request | Does not deliver a message; the public response may remain deliberately non-enumerating | None | | Administrator invitation | Invited address | Admin invite request | Sends the invitation credential | **Invite user** | | Email-address change | Authenticated user | Email update request | Sends the address-change credential according to secure email-change policy | **Change email address** | | Reauthentication | Authenticated user | Reauthentication request | Sends the verification code | **Reauthentication** | The **Magic link or OTP** name describes one template whose content chooses the passwordless experience. Passwordless mode is exclusive: include only `{{ .Token }}` for `otp`, or only the application-owned `{{ .TokenHash }}` link for `link`. The **Confirm signup** template is separate and follows `SIGN_UP_EMAIL_VERIFICATION`; it may include both alternatives only for `otp-and-link`. Supacharger's SSR flow supplies `/auth/confirm?next=...` as the complete `.RedirectTo` for link mode. Use this HTML link where a link is configured: ```html ... ``` Do not append another `/auth/confirm` path. Do not place the token hash on `/auth/callback` or `/account/login`. An unconfirmed Auth row therefore does not behave like a confirmed existing account. Supabase Auth explicitly treats a missing or unconfirmed user as someone who has not completed signup before it chooses the confirmation path. See the [passwordless email guide](https://supabase.com/docs/guides/auth/auth-email-passwordless), [`signInWithOtp()` reference](https://supabase.com/docs/reference/javascript/auth-signinwithotp), and [Auth template reference](https://supabase.com/docs/guides/auth/auth-email-templates). All confirmation links and email OTPs are one-time credentials. If a template exposes both forms for one request, successfully using either consumes that request and makes the other form invalid. Disable SMTP-provider click tracking and automated link rewriting so scanners or relay redirects do not consume or deform the credential before the user opens it. ## Important variables | Variable | Use | | --- | --- | | `{{ .Token }}` | Configured-length email OTP or reauthentication code | | `{{ .TokenHash }}` | Hashed token used to construct an application-owned confirmation link | | `{{ .ConfirmationURL }}` | Supabase-generated verification URL | | `{{ .RedirectTo }}` | Per-request allowed redirect supplied by the application | | `{{ .SiteURL }}` | Fixed fallback Site URL configured in Supabase | Use `{{ .RedirectTo }}` when Supacharger supplies the destination for a request. Do not replace it with `{{ .SiteURL }}` merely because both happen to share the same origin in production. ## Operating rules - Use only the configured OTP or link method in a passwordless email. - Include both signup alternatives only when `SIGN_UP_EMAIL_VERIFICATION` is `otp-and-link`. - Do not hard-code an expiry that differs from Email OTP Expiration. - Disable provider click tracking that rewrites one-time URLs. - Avoid tracking pixels in security-sensitive messages. - Never use user-editable metadata for authorization decisions. - Test each template in real desktop and mobile mail clients. The complete installation sequence is in [Install email templates](../../guides/hosted-setup/07-email-templates.md). --- ## Roles and Organisations :::warning Legacy stub This page is an old idea note and does not describe the current Supacharger Core implementation. Use [Roles and custom claims](../Application%20Development/roles-and-custom-claims.md) and [Organisation management](../Application%20Development/organisations.md) instead. ::: ## Roles Custom claims ## Organisations Pro plugin for role-based access control one subscription for seats, users can be in a team --- ## supacharger-cli ## Authentication and Supabase doctor Run the read-only alignment check from an application root: ```bash supacharger doctor ``` It checks the Next.js Proxy entry point, protected access boundaries, canonical account/organisation configuration, managed organisation routes/tests/adapters, duplicate App Router pages after route-group normalisation, canonical English namespaces, obsolete account/organisation routes, obsolete MFA visibility config, local TOTP enrol/verify capability, the managed-file manifest, required post-update scripts and Bruno assets, Supabase dependencies and configuration, migrations and aliases, and public environment-variable names. It reports whether contracts exist; it does not print values, contact a linked project, or mutate a database. The current contract corrects the historical `PATH_AUTH_GARD` spelling to `PATH_AUTH_GUARD`. Existing applications must rename that key in `src/supacharger.config.ts` when adopting this core version. After a core update adds a Supabase migration, review the changed files and the CLI's linked dry-run output before approving a push. Hosted Auth Hook selection is environment configuration: after deploying the migration, select `app.custom_access_token_hook` under **Authentication → Hooks** in each hosted Supabase project. A committed local `config.toml` does not perform that hosted setting change. ## Installation Install the supacharger-cli with npm. More on npm ```bash npm i @glowplug/supacharger-cli ``` ## Commands ### init Clones the canonical starter into the target directory and records the installed core commit in `.supacharger/core-lock.json`. If the target is the current directory or is not empty, the CLI asks before removing its contents except `.git`. The command does not configure environment values, start Supabase, apply hosted migrations, install dependencies, build, or start Next.js. Follow the setup guide after installation. Example: ```bash supacharger init my-project ``` ### enable plugin-name alias: **en** - Enables a registered module by setting enabled to true. Example: ```bash supacharger en plugin-name ``` ### disable plugin-name alias: **dis** - Disables a registered module by setting enabled to false. Example: ```bash supacharger dis plugin-name ``` ### install plugin-name alias: **in** flags: -f (force - will remove git refs and files, then clone a fresh copy) Atttempts to install a plugin and activate it. - pulls source from github - may require purcahse first and github username - modifies core files - ceates database tables - pushes edge functions - seeds data - enables the module in its config - stops the development server process - triggers a rebuild of the project - starts the development server ```bash supacharger install plugin-name ``` You will need to rebuild your project after enabling a module ```bash npm run build ``` ### uninstall plugin-name alias: **un** Completely uninstalls a Plugin - Removes the Plugin directory from (plugins) - Attempts to remove source from core files if it has not been modified - Attempts to remove database tables - Attempts to remove edge functions - stops the development server process - removes Plugin from .gitmodules - triggers a rebuild of the project, removing it from registry - starts the development server Example: ```bash supacharger un plugin-name ``` ### coreupdate Update a project to the latest version of Supacharger. This ones hardcore and can potentially screw up your project. Back up first. - Pull the latest core files from the canonical `glowplug-studio/supacharger` repository. - Read the version 2 `.supacharger/managed-files.json` ownership contract: byte-replace only `managedPaths`, merge `mergeManagedPaths`, append new files under `forwardOnlyMigrationPaths`, and preserve `developerOwnedPaths`. - Replace shared managed contract tests only with their canonical Core versions. Preserve `test/project-billing-schema-contract.test.mjs` for application catalogue, table, RPC, and policy assertions, and preserve consumer package scripts that run it beside the shared test. - Preserve developer-owned adapters in `src/supacharger.adapters/`, application routes in `src/app/(project)/`, demo presentation, configuration, localisation, and project styles. - Install `src/app/layout.tsx` for a new project, then preserve it as a developer-owned root-layout template during every core update. - When adopting the Cache Components release, set `cacheComponents: true` in the developer-owned Next.js configuration and keep `export const instant = false` in the developer-owned root layout because locale selection reads the request cookie before the document renders. Remove obsolete `dynamic = 'force-dynamic'` route exports; Cache Components rejects them. Run `npm run build` before advancing the lock. - Preserve developer-owned settings in `src/supacharger.config.ts`; insert only absent approved profile-media, `ACCOUNT_SETTINGS`, `ORGANISATIONS`, and `BILLING.ACCOUNT_SUBJECTS` keys with disabled-safe defaults. - Remove the obsolete `AUTH_SESSION.VERIFICATION_MODE` setting while retaining the rest of `AUTH_SESSION`; claims verification is now a fixed Proxy invariant. - Remove obsolete `AUTHENTICATION.MFA_TOTP.ENABLED`, preserve `REQUIRED_FOR_SIGN_IN`, and enable both local `[auth.mfa.totp]` APIs. Restart the local Supabase stack after updating; hosted MFA capability remains a separate Dashboard setting. - Update the protected `src/supacharger/supacharger-config-contract.ts` so option names and value shapes remain canonical while application values stay unchanged. Follow the release's configuration migration instructions when the contract changes. - For the social-provider contract expansion, report missing `AUTH_PROVDERS_ENABLED` keys in `--plan`; during a real update, back up the developer config, preserve existing values, and add each newly supported provider as `false`. - For the root-document contract expansion, report missing `METADATA`, `ROOT_PROVIDERS`, and `ANALYTICS` blocks in `--plan`; during a real update, back up the developer config and insert only missing blocks. Existing branding and provider values remain application-owned. - Preserve developer-owned localisation configuration in `src/i18n/` and every secondary catalogue. Recursively add missing canonical keys to `messages/en.json` and fill empty English values without replacing non-empty application wording. - Install missing auth, account, billing, and organisation adapter/style starters once, then preserve established implementations. `src/styles/supacharger-auth.css`, `src/styles/supacharger-account.css`, and `src/styles/supacharger-organisations.css` own the complete presentation of their managed semantic markup. Organisation adapter starters include `chrome.tsx`, `navigation.ts`, `pages.tsx`, `profile-fields.tsx`, and `profile-extension.ts` under `src/supacharger.adapters/organisations/`. - Preserve the developer-owned `src/assets/svgr/ui/inline-loader.svg` and optional dark variant, while updating protected authentication-provider SVGs at `src/supacharger/assets/svgr/auth-providers/` and the protected Proxy/server-access paths. - Record the installed core commit in `.supacharger/core-lock.json`. - Detect locally modified or missing baseline files before replacing them. - Back up overwritten files outside compilable source under `.supacharger/backups/` and remove files that were managed by the installed baseline but are obsolete in the new release. - Merge Core dependency requirements and missing manifest-required scripts into the application package without removing product dependencies or existing scripts. Regenerate the lock only when dependencies change. - Copy only previously absent Core migrations. A differing migration with the same filename is a conflict; installed migration history is never replaced, removed, reordered, or rewritten. A reviewed consumer-specific equivalent under another immutable filename must be declared explicitly in `.supacharger/migration-aliases.json`; the plan reports it and update validates the target before skipping the canonical file. - When migrations changed, show `supabase db push --linked --dry-run` and ask separately before any linked push. The core lock advances only after the selected post-update work completes. - Back up and remove an obsolete `postcss.config.js` only when it matches the installed baseline; preserve and stop on an independently customised legacy file. The canonical exact file is `postcss.config.mjs`. - Never treat a copied `supabase/config.toml` as a hosted Auth Hook update; select the deployed hook separately in each Supabase environment. - Install the exact managed Bruno collection and checker. Core parity covers every current `api` RPC; consumer parity requires the canonical subset and permits separate product RPCs. - Run every manifest-declared check before advancing the lock. Missing scripts, missing canonical RPCs, or parity failures leave the existing lock unchanged. Preview the exact managed writes, developer-config additions, English catalogue additions, obsolete removals, dependency changes, migrations and aliases, and checks without changing project files or a database: ```bash supacharger coreupdate --plan ``` ```bash supacharger coreupdate ``` Backups are recovery material for files, not a database rollback. Review the linked migration dry run carefully and take an environment-appropriate database backup before approving a hosted push. --- ## Getting started Supacharger is a reusable Next.js and Supabase application core. It includes authentication, account management, internationalisation, a subscription billing foundation, email integration, and a CLI-managed update boundary. ## Requirements - Node.js 22 or later - npm 11 as declared by the core package - Docker for the local Supabase stack - Supabase CLI - Stripe CLI when testing billing webhooks ## Install ```bash npm install cp .env.local.example .env.local npx supabase start npm run dev ``` Fill every required value in `.env.local`; placeholders and blank values are rejected. Keep secret/service-role Supabase keys and all Stripe secrets server-only. ## Apply database migrations Link the intended Supabase project, review pending migrations, then apply them: ```bash npm run supabase:link npx supabase db push --linked --dry-run npm run migration:up ``` The internal billing projection is stored in `app`; browser reads use narrowly scoped `api` functions and Stripe ingestion uses service-only `api_edge` functions. Grants and row-level security are explicit. Regenerate database types after a schema change. ## Configure the hosted application After the application and migrations work against local Supabase, follow the [hosted setup wizard](./guides/setup-wizard.md). It covers the linked Supabase project, Vercel environment variables, first production deployment, Auth URLs and policy, SMTP, email templates, and end-to-end production verification in dependency order. Do not assume that `supabase/config.toml`, local email templates, or an authenticated MCP connection configured the hosted project. Each hosted Supabase environment has its own Dashboard settings, secrets, hooks, providers, templates, and rate limits. ## Configure billing The current supported Stripe tuple is Node SDK `22.5.0` and API `2026-07-29.dahlia`. Follow the complete [Stripe setup](./Billing/stripe.md) before enabling subscription enforcement. Required server environment variables are: ```dotenv STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_EVENT_PROCESSOR_SECRET= STRIPE_RECONCILIATION_SECRET= ``` The hosted Checkout integration does not use Stripe.js, so it does not require a browser publishable key. It uses billing accounts, multiple Subscriptions and Items, Dynamic Payment Methods, flexible billing mode, and a local entitlement projection; see the billing guide before adding product access rules. ## Core updates The Supacharger CLI preserves developer-owned configuration, localisation messages, and project styling. When a core update changes dependencies, the CLI installs them. When it contains Supabase migrations, the CLI runs a linked-project dry run and asks separately before applying them. It does not advance the core lock when migration application is declined. Read the [CLI documentation](./cli/index.md) and [Anatomy ownership guide](./Application%20Development/folderstructure.mdx) before modifying protected core files. ## Attribution Parts of Supacharger's original billing implementation were inspired by `next-supabase-stripe-starter` and Vercel's archived `nextjs-subscription-payments` example. Retained third-party notices belong in the source repository's formal notice file; copied product branding and obsolete upstream setup instructions are not part of the Supacharger documentation. --- ## Full Setup Guide # Full setup guide Use this wizard after Supacharger works against the local Supabase stack. It takes the application from a local baseline to a tested hosted Supabase project and a production Vercel deployment. This is a production-configuration wizard, not an installation guide. Supabase and Vercel MCP connections make the work easier for a coding agent, but they are developer tooling; the deployed application does not depend on either MCP connection. ## What you will configure | Step | Outcome | | --- | --- | | [1. Before you begin](./hosted-setup/01-before-you-begin.md) | Confirm the local baseline, select the intended projects, and decide application policy. | | [2. Connect hosted Supabase](./hosted-setup/02-connect-hosted-supabase.md) | Link the project, preview and apply migrations, and enable the access-token hook. | | [3. Configure environment variables](./hosted-setup/03-environment-variables.md) | Set local and Vercel variables without exposing server credentials. | | [4. Deploy and establish URLs](./hosted-setup/04-deploy-and-urls.md) | Establish the canonical HTTPS origin and configure Supabase redirect URLs. | | [5. Configure authentication policy](./hosted-setup/05-authentication-policy.md) | Align hosted Auth settings with `src/supacharger.config.ts`. | | [6. Configure production email](./hosted-setup/06-production-email.md) | Connect custom SMTP, authenticate the sending domain, and tune rate limits. | | [7. Install email templates](./hosted-setup/07-email-templates.md) | Install templates that match OTP, link, recovery, and security flows. | | [8. Verify production](./hosted-setup/08-production-verification.md) | Exercise the complete hosted authentication journey and run final checks. | Complete the steps in order. A later page assumes the earlier page has supplied its project, keys, domain, or policy decisions. ## Keep environments separate Repeat the hosted parts of the wizard for every Supabase environment. Local `supabase/config.toml` settings and files under `supabase/templates/` configure only the local stack; they do not update a hosted project's Auth settings, templates, providers, hooks, or rate limits. Use different Supabase projects and secrets for staging and production. Never test a migration against production merely because the CLI happens to be linked to it. ## Optional branches Finish the core wizard before adding integrations that the application does not need at launch: - [Mobile login verification with deep links](../Application%20Development/mobile-auth-deep-linking.md) - [Authentication providers](../Application%20Development/authentication-coverage.mdx#supported-social-oauthoidc-providers) - [Stripe setup](../Billing/stripe.md) - [SEO, metadata, and analytics](../Marketing-and-Analytics/seo-and-analytics.md) Start with [Before you begin](./hosted-setup/01-before-you-begin.md). --- ## 1. Before you begin # Before you begin Do not configure the hosted project until the application and migrations work locally. Starting from a known-good local baseline makes hosted configuration problems much easier to identify. ## Required baseline Confirm all of the following: - Node.js 22 or later is active. - Dependencies are installed with the package manager declared by the application. - Docker and the local Supabase stack start successfully. - The local database resets cleanly and its tests pass. - Generated database types are current. - The application builds and the authentication pages load locally. - You can sign in to the Supabase CLI. - You have Owner or Administrator access to the intended hosted Supabase project. - The application repository is connected to the intended Vercel project. - You know which branch Vercel treats as Production. Run the application-provided checks rather than inventing substitute commands. A typical Supacharger project exposes commands for reset, database lint, type generation, application lint, type checking, and build. ```bash npx supabase status supacharger doctor npm run build ``` `supacharger doctor` checks names and local configuration. It does not contact a linked project, print secret values, or prove that hosted Dashboard settings match the repository. ## Tooling connections If you use an AI coding agent, connect and authenticate the Supabase and Vercel MCP servers now. Confirm that each connection points at the intended account and project before allowing it to inspect or change hosted configuration. MCP is optional. The production application communicates with Supabase and Vercel through its normal runtime configuration, not through MCP. ## Record the deployment targets Write down these values before continuing: | Value | Example | | --- | --- | | Supabase organization and project name | `Acme / Acme Production` | | Supabase project reference | `abcdefghijklmnopqrst` | | Vercel team and project name | `Acme / acme-app` | | Production branch | `main` | | Local application origin | `http://localhost:3000` | | Intended production origin | `https://app.example.com` | Treat the intended production origin as provisional until the domain is attached and responds over HTTPS in step 4. ## Choose application policy Edit the developer-owned `src/supacharger.config.ts` before configuring Supabase Dashboard. Decide: - whether users can sign in and sign up with email and password; - whether magic-link sign-in is available and may create accounts; - whether sign-up verification is `disabled`, `link`, or `otp`; - whether usernames are `disabled`, `optional`, or `required`; - whether post-sign-in onboarding is required and its real destination route; - whether organizations are enabled and have a complete onboarding journey; - which social providers are visible; and - whether mobile deep linking is actually ready for the production host and signed native application identifiers. The hosted settings must enforce the same policy as the application. Hiding a button in the UI does not disable a Supabase Auth method. ## Completion check Continue only when: - the local build and database checks pass; - the hosted project identities have been recorded; and - `src/supacharger.config.ts` expresses the intended authentication policy. Next: [Connect hosted Supabase](./02-connect-hosted-supabase.md). --- ## 2. Connect hosted Supabase # Connect hosted Supabase Link the repository to the intended hosted project, then prove the target is correct before applying any migration. ## Link and inspect From the application root, authenticate and link using the project's normal CLI workflow. Discover the installed CLI's current syntax with `--help` if a command differs from the examples. ```bash npx supabase --version npx supabase link --help npx supabase link --project-ref npx supabase migration list --linked ``` Before continuing, compare the linked project reference with the value recorded in step 1. Do not rely on a remembered project name or the last project used on the machine. ## Preview the migration Review the linked dry run: ```bash npx supabase db push --linked --dry-run ``` Read the migration list and SQL changes. Stop if the dry run contains an unexpected migration, destructive statement, project reference, or schema owner. Take an environment-appropriate database backup before the first production push or any migration that changes existing data. ## Apply and verify Apply only the reviewed migrations: ```bash npx supabase db push --linked npx supabase migration list --linked ``` Regenerate types from the project's supported workflow if the hosted schema differs from the last locally generated types. Commit schema migrations and generated types together when the repository's ownership rules require it. ## Enable the access-token hook The canonical migration installs `app.custom_access_token_hook`. After that migration exists in the hosted database: 1. Open **Authentication → Hooks** in the hosted Supabase project. 2. Select the custom access-token hook. 3. Choose `app.custom_access_token_hook`. 4. Save the setting. The `[auth.hook.custom_access_token]` entry in local `supabase/config.toml` does not enable this hosted setting. After changing roles or application metadata used by the hook, refresh the user's token or sign in again before expecting new claims. JWT claims are not retroactively rewritten in an already issued access token. ## Data API and RLS check Confirm every table exposed through the Data API has Row Level Security enabled and policies matching its ownership model. Data API exposure and RLS are separate controls: exposure determines whether a role can reach the table; RLS determines which rows it can access. Do not solve access errors by exposing private schemas, granting broad table access, or adding `SECURITY DEFINER` without reviewing the security model. ## Completion check - The linked project reference matches the intended hosted project. - The linked and local migration histories agree. - The custom access-token hook is selected in the hosted Dashboard. - No unexpected object is publicly exposed. Next: [Configure environment variables](./03-environment-variables.md). --- ## 3. Configure environment variables # Configure environment variables Configure environment variables before expecting a production deployment to work. The project may have an initial Vercel deployment already, but it must be redeployed after production values are present. ## Start from the application contract Copy the application's example file and fill every required value: ```bash cp .env.local.example .env.local ``` Do not copy a `.env.local` file from another product. Keep `.env.local` out of version control and use the exact variable names declared by the current starter. Run `supacharger doctor` to check the expected public variable names, then search the application contract for any product-specific server variables. ## Supabase values Supabase projects now provide publishable and secret API keys. Existing projects may still expose legacy `anon` and `service_role` keys for compatibility. Use the key names expected by the installed Supacharger version. | Kind | Typical purpose | Exposure | | --- | --- | --- | | Project URL | Browser and server Supabase clients | May be public | | Publishable key or legacy `anon` key | Browser client, constrained by grants and RLS | May be public | | Secret key or legacy `service_role` key | Trusted server administration only | Server only | | Database password or connection string | CLI, migrations, or a trusted server when required | Server/developer tooling only | Any variable prefixed with `NEXT_PUBLIC_` is included in the browser bundle. Never place a Supabase secret key, legacy service-role key, database password, Stripe secret, SMTP password, or webhook secret in such a variable. ## Canonical site URL Set the local value to the exact origin used by the local application: ```dotenv NEXT_PUBLIC_SITE_URL=http://localhost:3000 ``` Set Vercel Production to the canonical HTTPS origin, without a path: ```dotenv NEXT_PUBLIC_SITE_URL=https://app.example.com ``` Supacharger treats `NEXT_PUBLIC_SITE_URL` as the runtime source of truth for authentication destinations. Do not depend on an implicit localhost or Vercel fallback in production. ## Configure Vercel scopes In **Vercel → Project → Settings → Environment Variables**: 1. Add the hosted Supabase URL and the matching publishable key. 2. Add only the server credentials actually required by the application. 3. Set `NEXT_PUBLIC_SITE_URL` separately for Production, Preview, and Development where their origins differ. 4. Add Stripe, email, analytics, and application-specific variables only when those features are enabled. 5. Mark sensitive values as sensitive where the Vercel plan and workflow support it. 6. Confirm no value was copied from a different Supabase environment. Preview deployments need an explicit policy. Either connect them to a dedicated staging Supabase project or deliberately allow preview callback URLs in the production project. A staging project is safer when previews can modify data. ## Pulling variables locally If Vercel is the managed source for development values, use Vercel's environment pull workflow carefully. Review the destination before overwriting an existing `.env.local`, and do not pull production secrets onto a machine that should not hold them. ## Completion check - Local development contains no blank or placeholder required values. - Vercel Production has the production Supabase project values. - Preview and Production do not accidentally share secrets or data. - No server secret uses a `NEXT_PUBLIC_` name. - `.env.local` is ignored by Git. Next: [Deploy and establish URLs](./04-deploy-and-urls.md). --- ## 4. Deploy and establish URLs # Deploy and establish URLs Redeploy after setting the environment variables, establish the canonical HTTPS origin, and then use that exact origin in Supabase Auth. ## Produce a clean deployment Push the intended production branch or trigger a Vercel redeployment. Confirm: - the build uses the intended branch and commit; - required environment variables were available to that deployment; - the production domain resolves over HTTPS; - the domain does not redirect to a different canonical hostname unexpectedly; and - the login, signup, confirmation, callback, and reset-password routes resolve. If the domain changes, update `NEXT_PUBLIC_SITE_URL` and redeploy before configuring Supabase URLs. ## Set the Site URL In **Supabase → Authentication → URL Configuration**, set **Site URL** to the canonical production origin: ```text https://app.example.com ``` The Site URL is Supabase's default redirect when the application does not provide `redirectTo`. It does not replace `NEXT_PUBLIC_SITE_URL`; the hosted Auth setting and application environment variable must agree. ## Add allowed redirect URLs Add the exact routes used by Supacharger: ```text http://localhost:3000/auth/callback http://localhost:3000/auth/confirm https://app.example.com/auth/callback https://app.example.com/auth/confirm ``` If a flow supplies a query string such as `next` or `flow=recovery`, allow-list the stable route rather than copying a one-time token URL from an email. Password recovery uses `/auth/callback?flow=recovery`, so the callback entry above covers it. Use exact production URLs. Broad wildcards are appropriate only when a controlled local or Vercel preview workflow genuinely needs them. Supabase's Vercel preview pattern is: ```text https://*-.vercel.app/** ``` A wildcard permits every matching preview deployment to receive Auth redirects. Prefer a dedicated staging Supabase project when previews contain untrusted branches or production data must remain isolated. ## Social provider callback distinction Supabase's application redirect allow-list is not the same as an OAuth provider's callback setting. For providers such as Google or GitHub: - the application sends the user through `/auth/callback`; and - the external provider normally returns to the Supabase Auth callback URL shown in that provider's Supabase configuration panel. Copy the provider callback value from the current Supabase Dashboard rather than constructing it from memory. ## Completion check - `NEXT_PUBLIC_SITE_URL`, Vercel's production domain, and Supabase Site URL use the same canonical origin. - All enabled Auth flows have an exact local and production redirect entry. - Preview wildcards are absent unless preview authentication is intentional. - The deployed routes respond without a redirect loop. Next: [Configure authentication policy](./05-authentication-policy.md). --- ## 5. Configure authentication policy # Configure authentication policy The application UI and the hosted Auth server must enforce the same policy. Configure the hosted project from the decisions recorded in step 1. ## Align the email provider Open **Supabase → Authentication → Sign In / Providers → Email** and compare it with `AUTHENTICATION` in `src/supacharger.config.ts`. | `SIGN_UP_EMAIL_VERIFICATION` | Hosted Confirm Email | Confirm signup template | | --- | --- | --- | | `disabled` | Off | Not used for signup | | `link` | On | Token-hash link through `/auth/confirm` | | `otp` | On | Configured-length `{{ .Token }}` | | `otp-and-link` | On | Both alternatives; either one consumes the verification | Configure `PASSWORDLESS_EMAIL.SIGN_IN` and `SIGN_UP` separately as `disabled`, `otp`, or `link`. Passwordless mode is exclusive: the hosted **Magic Link or OTP** template must contain only the configured OTP or only the configured link. Set `PASSWORDLESS_EMAIL.OTP_LENGTH` to an integer from 6 through 10 and keep the hosted OTP length equal to it. Keep **Allow new users to sign up** aligned with whether the product permits public account creation. Leave anonymous sign-ins disabled unless the application deliberately accepts anonymous Supabase users and its RLS distinguishes them correctly. Anonymous users receive the `authenticated` Postgres role, so `TO authenticated` alone does not separate them from permanent users. ## Email OTP expiration Set **Email OTP Expiration** to a duration appropriate for the product. Supabase defaults to 3,600 seconds; use 3,600 seconds or less for a normal production application unless a documented user need justifies longer. This single setting governs: - email OTPs; - magic links; - signup confirmation links; - password-recovery links; - email-change links; and - invitation links. Do not write a different duration into the email copy. If the policy changes later, update the template copy at the same time. ## Resend cooldown and rate limits The OTP expiration is not the resend cooldown. Supabase normally requires 60 seconds between OTP, magic-link, signup-confirmation, and recovery requests for the same user. Keep the application's displayed resend timer equal to or longer than the hosted cooldown. Project-wide email and OTP quotas are configured separately under **Authentication → Rate Limits**. They are covered after custom SMTP is connected in step 6. ## Password policy If password authentication is enabled: 1. Set a minimum password length of at least eight characters. 2. Select the required character policy appropriate for the product. 3. Enable leaked-password protection when the project plan supports it. 4. Match the minimum and character policy in Supacharger's application configuration. 5. Treat `PASSWORD_CUSTOM_REGEX` as additional application validation only; it does not alter Supabase's server policy. Decide whether changing a password requires the current password or recent reauthentication. Do not enable a server requirement until the application supplies the corresponding user journey. ## Account changes and security notifications Keep secure email change enabled unless the product has a documented reason to confirm only the new address. When secure email change is enabled, the user confirms both the existing and new address. Enable the available security notifications for password, email, phone, sign-in-method, and verification-method changes that the product supports. Install their templates in step 7. ## Social providers For every provider enabled in `AUTH_PROVDERS_ENABLED`: 1. Enable the matching provider in Supabase. 2. Supply its client ID and secret. 3. Copy Supabase's provider callback URL into the external provider console. 4. Verify `/auth/callback` is allowed in Supabase URL Configuration. Do not enable a UI provider without its hosted credentials, and do not leave unused hosted providers enabled. ## Completion check - The email-confirmation mode matches `SIGN_UP_EMAIL_VERIFICATION`. - Passwordless mode and OTP length match `PASSWORDLESS_EMAIL`. - OTP expiration and the UI resend timer are compatible. - Password rules match on the client and Supabase server. - Anonymous sign-ins and unused providers are off. - Enabled account-change requirements have a working UI journey. Next: [Configure production email](./06-production-email.md). --- ## 6. Configure production email # Configure production email Supabase's default email service is for initial exploration, not a public production application. It sends only to authorized project-team addresses, has a low project-wide quota, and provides no delivery SLA. Configure custom SMTP before testing production signup. New Free plan projects using the default SMTP service cannot customize Auth email templates. Custom SMTP is therefore also a prerequisite for step 7 on those projects. ## Prepare the sending service Supabase works with SMTP providers such as Brevo, Postmark, Resend, SendGrid, or Amazon SES. In the chosen provider: 1. Add a dedicated transactional sending domain or subdomain, such as `auth.example.com`. 2. Create a sender such as `no-reply@auth.example.com`. 3. Publish and verify SPF and DKIM records. 4. Publish a DMARC policy and monitoring address appropriate for the domain's rollout stage. 5. Disable click tracking for Auth emails if it rewrites confirmation URLs. 6. Create SMTP credentials dedicated to the production Supabase project. Keep authentication mail separate from marketing mail where practical. It protects deliverability and makes failures easier to isolate. ## Configure Supabase SMTP Open **Supabase → Authentication → SMTP Settings**, enable custom SMTP, and enter: - sender name; - sender email; - SMTP host; - port; - username; and - password. Use the port and transport recommended by the provider. Do not put these SMTP credentials in `NEXT_PUBLIC_` variables or commit them to the repository; Supabase stores them as hosted Auth configuration. Send a test to an address outside the Supabase organization team. Delivery to only a project owner's address does not prove that custom SMTP is active. ## Configure Auth rate limits After custom SMTP is saved, open **Authentication → Rate Limits**. Supabase initially applies a low hourly email limit to a newly configured SMTP service. Increase it only to a volume the provider and sending domain can safely support. Review these separate controls: | Control | What it limits | | --- | --- | | Emails sent | Combined signup, recovery, and email-change messages project-wide | | OTP requests | Calls to the OTP endpoint project-wide | | Per-user email cooldown | How soon the same user can request another message | | Verification attempts | Attempts to verify a token, limited independently | Rate limits are abuse controls, not capacity targets. Add CAPTCHA or Turnstile before a public launch if automated signups could damage sender reputation or exhaust the quota. ## Deliverability check Inspect a delivered message's headers and confirm: - the visible From address is correct; - SPF passes; - DKIM passes; - DMARC passes or aligns as intended; - links retain the original Supabase template URL; and - the message is not unexpectedly rewritten by click tracking. ## Completion check - Custom SMTP is enabled in the production Supabase project. - An external recipient receives the test message. - SPF, DKIM, and DMARC results are understood. - Auth rate limits match expected traffic and provider limits. - Link tracking does not consume or rewrite one-time Auth links. Next: [Install email templates](./07-email-templates.md). --- ## 7. Install email templates # Install email templates Hosted and local templates are configured separately. Edit hosted templates under **Supabase → Authentication → Email Templates**. Files under `supabase/templates/` affect only the local CLI stack. Use the reusable [Supabase email-template examples](../../Marketing-and-Analytics/email-templates/free-supabase-email-templates.md) as a safe starting point, then apply the product's branding and support details. ## Match signup verification The confirm-signup template must match `SIGN_UP_EMAIL_VERIFICATION`: - `otp`: show `{{ .Token }}` and instruct the user to enter the configured-length code in the application. - `link`: construct a token-hash link using `{{ .RedirectTo }}` and send it through `/auth/confirm`. - `otp-and-link`: include both alternatives and explain that using either one consumes the verification. - `disabled`: Supabase does not send a signup-confirmation message. Passwordless login is different and always exclusive. Its hosted **Magic Link or OTP** template must contain only `{{ .Token }}` in `otp` mode or only the login link in `link` mode. Never include both for passwordless login. ## Install the authentication templates Review every flow enabled by the application: | Template | Required content | | --- | --- | | Confirm signup | OTP or token-hash link matching the chosen verification mode | | Magic link | A sign-in link using the Supabase confirmation URL or the application's documented callback construction | | Reset password | A recovery link that returns to `/auth/callback?flow=recovery`; Core verifies the recovery session before `/account/reset-password/new` | | Invite user | Invitation purpose, expiry wording, and acceptance link | | Change email address | Old/new-address context appropriate to secure email change | | Reauthentication | The `{{ .Token }}` and the action it authorizes | Use `{{ .RedirectTo }}` when the application supplies a per-request redirect. `{{ .SiteURL }}` is the project's fixed fallback and can silently return a user to the wrong route when substituted for `RedirectTo`. ## Enable security notifications Where supported by the product, enable and brand the notifications for: - password changed; - email address changed; - phone number changed; - sign-in method linked or removed; and - verification method added or removed. Security messages should state what changed, when practical, and what the user should do if they did not perform the action. Do not place secrets, raw tokens, or sensitive user metadata in a notification. ## Template safety - Keep the subject specific and recognizable. - Include a plain explanation of why the message was sent. - Use the configured product and support names consistently. - Do not claim a hard-coded expiry that differs from Email OTP Expiration. - Avoid externally hosted tracking pixels in security-sensitive messages. - Disable provider click tracking that rewrites one-time links. - Use `user_metadata` only for harmless presentation; never treat it as authorization data. - Test both mobile and desktop rendering with real mail clients. ## Completion check - The signup template matches `SIGN_UP_EMAIL_VERIFICATION`. - The passwordless template contains only its configured OTP or link method. - Every enabled Auth flow has a template with correct destinations. - Expiry copy agrees with the hosted Email OTP Expiration setting. - Security notifications are enabled and branded where appropriate. - A real external mailbox renders each important template correctly. Next: [Verify production](./08-production-verification.md). --- ## 8. Verify production # Verify production Use a new external mailbox and the production HTTPS origin. Testing only with an existing administrator account misses confirmation, onboarding, and sender restrictions. ## Deployment sanity check - Confirm Vercel Production is running the intended commit. - Confirm the custom domain is serving that deployment over HTTPS. - Confirm `NEXT_PUBLIC_SITE_URL` is the same origin as Supabase Site URL. - Confirm the browser bundle does not contain a Supabase secret/service-role key or another server credential. - Confirm production and preview deployments use the intended Supabase projects. ## Authentication journey Test every enabled path: 1. Create a new account with email and password. 2. Complete signup using the configured OTP or link. 3. Confirm a second use of the consumed token shows a safe expired/already-used message. 4. Request another message and confirm the UI cooldown is not shorter than Supabase's cooldown. 5. Sign out and sign back in. 6. Test magic-link sign-in if enabled. 7. Request a password reset, confirm it exchanges at `/auth/callback?flow=recovery`, and finish it at the protected `/account/reset-password/new` page. 8. Change the password or email if those account controls are exposed. 9. Complete the configured profile or organization onboarding journey. 10. Test every enabled social provider through `/auth/callback`. For OTP mode, also verify that an expired or mistyped code produces a useful localised error without exposing a raw Auth object. For link mode, verify the destination does not retain `token_hash` after completion. ## Claims and authorization After a fresh sign-in, inspect the user's claims through a trusted server-side diagnostic path and confirm the custom access-token hook added the expected role claim. Do not use browser-editable `user_metadata` for authorization. Exercise at least one permitted and one forbidden data operation as the new user. An authenticated session alone is not proof that RLS ownership policies work. ## Email and abuse controls - Confirm the message arrives from the production sender. - Confirm SPF, DKIM, and DMARC results. - Confirm OTP and link expiry match the configured duration. - Confirm Auth rate limits return a user-friendly response when reached. - Confirm CAPTCHA or Turnstile protects public email-triggering endpoints when required by the threat model. ## Logs and security checks Review: - **Supabase → Logs → Auth** for failed callbacks, invalid redirects, SMTP failures, and unexpected anonymous users; - database and security advisors for exposed objects, missing RLS, unsafe functions, and policy issues; - Vercel build and function logs for missing variables, callback errors, and server exceptions; and - the sending provider's delivery and suppression logs. Treat warnings individually. Do not grant broad access, disable RLS, or convert a function to `SECURITY DEFINER` merely to make an advisor or permission error disappear. ## Record the release Store a non-secret deployment record containing: - Supabase project reference; - Vercel project and production domain; - production branch and verified commit; - migration version; - enabled Auth methods; - OTP expiry and resend cooldown; - SMTP provider and sending domain; - date and person who completed the checks; and - any intentionally deferred optional branch. The hosted setup is complete when the full enabled journey works from an external mailbox, authorization checks behave correctly, and the logs contain no unexplained production errors. Return to the [wizard overview](../setup-wizard.md) or continue with an optional integration. --- ## Account settings Supacharger provides a managed account details page at `/account`. It loads the signed-in user's canonical account summary, updates names and the configured username policy, persists the selected language, and optionally manages avatar and header media. Successful updates refresh active account menus immediately. ## Configuration ```ts PROFILE_IDENTITY: { USERNAME: 'optional', AVATAR: 'optional', HEADER_IMAGE: 'optional', }, ACCOUNT_SETTINGS: { LANGUAGE: true, CANCEL_ACCOUNT: 'disabled', PRODUCT_PROFILE_PATH: null, }, ORGANISATIONS: { ENABLED: false, AUTHENTICATION_HANDLE: 'disabled', CHOOSER_PATH: '/account/organisation', ROUTE_MODE: 'root-handle', PROFILE_MEDIA: true, }, BILLING: { // Existing Stripe settings… ACCOUNT_SUBJECTS: { PERSONAL: true, ORGANISATION: false, }, }, ``` The database profile columns remain installed in every display mode. These settings control policy and presentation, not migration selection. Keep cancellation `disabled` or `hidden` until the application has an approved deletion, retention, billing, and organisation-ownership-transfer workflow. ## Validation and persistence The browser form shares the field contract, but `/api/account/details` performs the authoritative Zod parse and authenticated mutation. It writes through the canonical profile and language RPCs and returns safe field errors. Email is displayed as verified Auth identity and is changed on the Security page, not through the profile RPC. Media uploads use the authenticated `process-image-upload` Edge Function and private `user-avatars` storage paths. The new path is persisted before the previous object is deleted. A failed profile mutation cleans up the new upload and leaves the prior media intact. ## Product extensions Account extension starters live in the developer-owned `src/supacharger.adapters/account/` directory. The CLI adds a missing starter during an update and never overwrites an existing adapter. - Add ordered product links in `navigation.ts`. - Render additional fields inside the managed `FormProvider` in `profile-fields.tsx`. - Validate and persist those values at the server boundary in `profile-extension.ts`. - Use `PRODUCT_PROFILE_PATH` only for a real product-owned route. - Reserve `details-page.tsx` and the `presentation.ts` passthrough for an intentional application-owned presentation such as Specdrive's licensed interface. - Use `chrome.tsx` for the outer application frame around managed account content. It may render a top header, application sidebar, footer, or any combination. Core's starter `chrome.tsx` uses the example `AuthenticatedAppShell`, so Korunet shows its signed-in header and footer on `/account`. The managed `AccountSettingsShell` still owns the account navigation and form content inside that frame. CLI updates add a missing starter but preserve every existing consumer adapter, preventing product navigation from being overwritten or duplicated. Managed account markup uses only stable `sc-account-*` and shared `sc-control-*` presentation hooks. Put the complete layout, spacing, typography, colours, dimensions, responsive rules, and interaction styling in `src/styles/supacharger-account.css`. The CLI installs this developer-owned starter when absent and preserves it thereafter, so you can fully restyle the account journey without editing managed JSX. Keep this presentation file unlayered so it can override Tailwind-layer defaults. Scope shared `sc-control-*` rules to the account surface roots that use them. Do not define generic control selectors at the root of this unlayered stylesheet, because they would override the application's layered authentication theme. The extension cannot bypass canonical authentication, username policy, media paths, or server validation. Add non-empty English values for every new field and only empty placeholders to secondary catalogues unless translation is explicitly requested. ## Privacy and notification adapters The managed `/account/privacy` and `/account/notifications` routes render boolean options returned by developer-owned `privacy.ts` and `notifications.ts` adapters. Each adapter returns a title, plain-text description, availability, and validated option descriptors. The save route authenticates the user, reloads the descriptor, and rejects any missing or unexpected option key before invoking product persistence. Starter adapters return `disabled` and throw if mutation is attempted. This produces an explicit unavailable message instead of fake toggles or artificial save delays. To enable a surface, load the user's real values at the server, return `availability: 'enabled'`, validate the product identifiers again inside the update adapter, and enforce ownership through RPC checks, grants, and RLS. Wakekeeper is the reference product integration: vessel UUIDs become option IDs and its adapter persists public trip-log policy through the product-owned `upsert_trip_log_privacy` RPC. Those vessel tables and rules do not belong in Core. No current consumer has a notification backend, so notification controls remain disabled. ## Billing subjects `BILLING.ACCOUNT_SUBJECTS` controls which settings surfaces exist; it is separate from the `BILLING_ACCESS` entitlement gate. When `PERSONAL` is enabled, Core owns `/account/billing`, `/account/billing/subscribe`, and the POST-only Customer Portal route. The management page uses the full application guard, while acquisition uses the onboarded-user guard so a user can recover missing billing access. Personal Checkout and Portal operations derive the billing subject from the authenticated user and never accept a browser-supplied billing-account UUID. The managed service validates 1–20 recurring Stripe Prices, positive quantities, and a single currency, then checks billing-manager authority before creating Stripe resources. Organisation billing uses the same service with an organisation UUID. Enable it only with organisations, and implement `src/supacharger.adapters/billing/organisation.ts` so the current owner or administrator resolves the application organisation to its billing account. The canonical service performs a second billing-account permission check and filters overview data by subject type and ID. Product catalogues and Specdrive's authorised presentation remain developer-owned. --- ## Authentication and route protection Supacharger uses Supabase Auth with `@supabase/ssr` and the Next.js 16 `proxy.ts` convention. Proxy refreshes the session and makes optimistic page-routing decisions; it is not the authoritative authorisation boundary. Route Handlers, Server Functions, data services, grants, and Row Level Security (RLS) must validate access independently. ## Server-side identity checks The Proxy always calls `supabase.auth.getClaims()` exactly once. It verifies the JWT signature and expiry and normally avoids an Auth-server round trip when asymmetric signing keys are enabled. This is a fixed security invariant rather than an application configuration choice. `AUTH_SESSION.ALLOW_ANONYMOUS_USERS` remains application-configurable. Before it creates that request-scoped Auth client, Proxy strips known password, email, MFA and organisation-registration fields from query strings on authentication routes and redirects to the sanitised URL. Safe return parameters remain intact, and a valid `lang` handoff is captured before removal. This is defence in depth for a form rendered before client hydration; Auth forms must still submit with POST and validate again at the protected server boundary. Managed authentication contract tests remain portable across applications: they validate supported configuration shapes, exact managed route/form behaviour and the semantic presentation interface, while each application's selected passwordless mode, confirmation policy, sidecar contents, CSS values and root layout stay developer-owned. Do not trust `getSession().user` for server-side authorisation. Supacharger excludes anonymous Supabase Auth users by default even though their database role is `authenticated`; an application must deliberately opt in to accept them. ## Proxy response invariant The request-scoped Supabase client runs exactly once per matched request. When it refreshes or clears a session, Supacharger carries all of the following into every continuation and redirect: - mutated request cookies for downstream Server Components; - response `Set-Cookie` values for the browser; and - `Cache-Control`, `Expires`, and `Pragma` supplied by `@supabase/ssr`. Do not create a redirect, rewrite, error response, or replacement `NextResponse.next()` without copying this session response state. The anti-cache headers prevent an intermediary from caching a response containing another user’s auth cookies. ## Route policy Static page policy remains application-owned in `src/supacharger.config.ts`: ```ts AUTH_ONLY_APP: true, PATH_AUTH_GUARD: { UNAUTHED_USER: { ALLOWED: ['/', '/account/login', '/auth/callback'], DISALLOWED: [], }, AUTHED_USER: { DISALLOWED: ['/account/login'], }, }, ``` Keep the leading slash. `/:path*` denotes a trailing wildcard. Proxy redirects are a usability feature only: API routes return their own `401` or `403`, Server Functions validate their caller, and RLS remains the final data boundary. The protected route-policy implementation supports exact literals, one-segment parameters such as `/organisations/:handle`, and trailing wildcards such as `/docs/:path*`. Unauthenticated protected API paths receive a JSON `401`; page paths redirect to the configured login destination. Use `copySupabaseResponseState` whenever an application adds a redirect or replacement response. The Proxy performs no database RPCs. The protected authenticated layout calls the shared server-access helper after routing, where it can enforce profile completion and billing policy with the current request's claims. Routes declared in `PATH_AUTH_GUARD.UNAUTHED_USER.ALLOWED` remain public for authenticated users with incomplete profiles and bypass this protected layout. Product-specific membership, organisation, or permission checks extend that server boundary; they do not belong in the Proxy hot path. ## Authentication journey Supacharger owns the public route scaffolds and accessible form structure: | Purpose | Managed route | | ------------------------- | ----------------------------- | | Sign in | `/account/login` | | Create account | `/account/create` | | Passwordless entry/status | `/account/login-magic` | | Request password reset | `/account/reset-password` | | Set a new password (recovery session required) | `/account/reset-password/new` | | OAuth/recovery PKCE callback | `/auth/callback` | | Email/OTP confirmation | `/auth/confirm` | | Safe authentication error | `/auth/error` | These pages live under `src/app/(supacharger)/(unauthenticated)/`. Do not create another page with the same public URL under `(project)`, because two route groups cannot own the same Next.js route. The managed shell imports `AuthSidecar` and `AuthMobileBrand` from `src/supacharger.adapters/auth/auth-sidecar.tsx`. Managed forms expose stable `sc-auth-*` hooks, and `src/styles/supacharger-auth.css` supplies their product presentation. The CLI installs missing starter versions of those two developer-owned files and preserves them on later updates. The shared clean-room journey is shipped to Core, SnapScreen, and Wakekeeper, using SnapScreen as the approved visual and interaction basis. Those projects preserve its strong heading treatment, progressive provider-to-email transition, dark auth surfaces, branded primary actions, and matching password-reset presentation. Auth buttons and input fields use the same 56 px control height and 20 px by 12 px internal padding, preventing primitive component defaults from changing their relative dimensions. The password-requirements checklist is anchored below its input as a floating panel: showing it does not change form height, move the confirmation field, or cover the password input. Signed-in password and confirmation fields remain on separate rows at every viewport width. Below the `lg` breakpoint, the hidden left panel hands logo presentation to a single logo above the login or signup content. At `lg` and wider, the inline logo is hidden and the left-panel logo is shown. Project copy, colours, imagery, enabled methods, CSS, and sidecar adapters remain developer-owned. The journey supports password sign-in/sign-up, exclusive passwordless OTP or link modes, dynamically sized OTP entry, resend cooldown, correct browser autocomplete purposes, and provider buttons. Passwordless sign-in and password-signup verification keep distinct wording and verification purposes. A safe relative `next` path survives route switches, sends, resends, and verification. The bare root destination `/` is implicit, so redirects omit the redundant `?next=%2F`; a root path with a query string or fragment remains explicit. Password, link, and OTP submissions retain independent busy indicators. New shared states extend the SnapScreen-based presentation rather than replacing it with a generic form. Specdrive consumes the same routes and behaviour through its authorised developer-owned presentation; its old `/auth/login`, `/auth/sign-up`, `/auth/forgot-password`, and `/auth/update-password` pages remain for one documented release as redirects that retain only a validated internal `next` destination. ```ts AUTHENTICATION: { EMAIL_PASSWORD: { SIGN_IN: true, SIGN_UP: true }, PASSWORDLESS_EMAIL: { SIGN_IN: 'otp', // 'disabled' | 'otp' | 'link' SIGN_UP: 'disabled', OTP_LENGTH: 6, // 6 through 10 }, SIGN_UP_EMAIL_VERIFICATION: 'otp', // also supports 'otp-and-link' MFA_TOTP: { REQUIRED_FOR_SIGN_IN: false }, }, ``` UI and server actions enforce these options. Password signup crosses a Zod-validated Server Action boundary before calling Supabase; the browser form is not the trust boundary. Passwordless sign-in calls `signInWithOtp()` with `shouldCreateUser: false`; entered codes verify with `type: 'email'`. Its hosted **Magic Link or OTP** template contains either `{{ .Token }}` for OTP mode or the login link for link mode, never both. `OTP_LENGTH` drives the field count, validation, and sign-in copy. Password signup remains independent: its confirmation code verifies with `type: 'signup'`, and `SIGN_UP_EMAIL_VERIFICATION` controls the Confirm signup template. Password signup may use `otp-and-link`; consuming either credential invalidates the other. Logout posts once through the shared server route so SSR cookies are cleared before navigation. The POST response supplies a safe relative `redirectTo` path; direct GET logout uses a relative `Location` header with status `303`. The browser resolves either path against the origin it opened, so neither flow depends on the configured Site URL for its final navigation. Changing a signed-in user's password posts to `/api/account/update-password`. The protected handler strictly parses the current/new password payload, verifies the user, reauthenticates with Supabase Auth `signInWithPassword`, applies the configured password-strength policy, and calls `updateUser`. Supacharger does not expose a password-comparison database RPC. The OTP and link in one signup email are two ways to complete the same one-time verification. If someone enters the OTP and then clicks the link, Supabase rejects the consumed link as expired or invalid. Supacharger handles that expected state without exposing a generic error page: it checks the current Auth user, sends a confirmed signed-in browser to the safe configured app destination with an explanatory toast, and sends a browser without a session to the configured login page with an expired/already-used notice. The redirect never retains the token hash or accepts a protocol-relative `next` value. Keep `AuthConfirmationNotice` mounted in the application root layout so one-time confirmation notices are shown and removed from the URL. A local `supabase/config.toml` or template does not update hosted Auth settings or templates. Passwordless link mode returns through `/auth/confirm`; OTP mode verifies directly from the login form. `/auth/callback` exchanges one-use PKCE codes for OAuth and password recovery. Recovery links carry `flow=recovery`; after exchange, Supacharger verifies the JWT recovery authentication method before opening `/account/reset-password/new`. A local `supabase/config.toml` or template does not update hosted Auth settings or templates. See [Supabase Auth coverage](./authentication-coverage.mdx) for a tick-and-dash comparison of email, OAuth, passkey, SAML, Web3, anonymous, MFA, and OAuth-server support, plus every social provider exposed by Supacharger. Phone authentication is intentionally excluded from that page. Password sign-in navigates directly to the login destination, while the canonical protected server layout independently enforces configured profile and billing access. Proxy remains claims-only. APIs and Server Actions outside that layout still require their own entitlement checks. Read [Login redirects and subscription paywalls](./login-redirects-and-paywalling.md) for the exact precedence and server-side enforcement requirements. Branding, translated copy, additional authentication fields, and product onboarding stay in developer-owned paths. Organisation creation or switching occurs only after authentication through `api.organisations(input_payload)`, which derives user/session identity from the JWT; an organisation handle is not stored as arbitrary user metadata. ## Mobile verification callbacks `MOBILE_DEEP_LINKING` configures the generated iOS Universal Link and Android App Link association endpoints. Use it when magic links, link-based confirmation, or password reset should return to an installed mobile app. Email OTP verification remains an in-app code-entry flow and does not require a deep link. The callback must return to the same client that initiated PKCE. A wrapped web app reloads the verified HTTPS URL in its persistent authentication web view; a fully native app completes the exchange through its native Supabase client. See [Mobile login verification with deep links](./mobile-auth-deep-linking.md) for the complete configuration, Xcode, Android manifest, callback, and verification procedure. ## Passwords and reset Configure the same password minimum and complexity in Supacharger and the Supabase Auth provider settings. `PASSWORD_CUSTOM_REGEX` is enforced by both the browser checklist and the server-side recovery/update boundary; it does not change Supabase Auth’s own password policy. Password-reset email redirects to `/auth/callback?flow=recovery`; allow `/auth/callback` in each hosted Supabase project and set `NEXT_PUBLIC_SITE_URL` correctly. The callback exchanges the PKCE code once on the server, and `/api/account/recover-password` requires a verified recovery AMR before applying the new password. `NEXT_PUBLIC_SITE_URL` is required and is the single runtime source for the application's canonical origin. Supacharger validates it with `getSiteUrl()` and builds authentication destinations with `getURL(path)`. Do not add localhost or production fallbacks in source code: a missing value fails clearly so redirects cannot silently target the wrong application or port. A local Supabase `config.toml` can reference the same environment variable, but hosted Supabase URL Configuration remains a separate dashboard setting. Use the [hosted setup wizard](../guides/setup-wizard.md) to configure the environment variables, Site URL, exact callback allow-list, email policy, SMTP, and templates in the required order. ## Roles and custom claims The canonical migration creates private `app.user_roles` data and `app.custom_access_token_hook(jsonb)`. The hook adds a top-level `user_role` claim whenever Supabase Auth issues or refreshes a JWT. The browser cannot write the role table or execute the hook. See [Roles and custom claims](./roles-and-custom-claims.md) for setup, role assignment, RLS examples, token-refresh behaviour, and the Specdrive-style active-organisation extension. ## CLI workflow Auth hooks and role changes are schema changes. Create and verify them through the project-scoped Supabase CLI: ```bash npx supabase migration new describe_the_change npx supabase db reset npx supabase db lint --local npm run generate-types ``` Keep `[auth.hook.custom_access_token]` enabled in `supabase/config.toml`. For a hosted environment, select `app.custom_access_token_hook` in **Authentication → Hooks** after deploying the migration. Preview linked database changes before applying them; never treat a local `config.toml` edit as proof that a hosted dashboard setting changed. The first-deployment sequence and its verification checks are documented in [Connect hosted Supabase](../guides/hosted-setup/02-connect-hosted-supabase.md) and [Verify production](../guides/hosted-setup/08-production-verification.md). --- ## Connecting Specdrive Specdrive can guide work in either an existing codebase or a new Supacharger application. In both cases, keep Supacharger Core as the reusable source of truth and keep product behaviour in developer-owned seams. ## Brownfield codebase Start with an inventory rather than replacing folders wholesale: 1. record the existing authentication, roles, database objects, routes, styling and product-specific behaviour; 2. compare those contracts with the Core lock and ownership manifest; 3. classify each difference as exact managed Core, merge-managed configuration, forward-only migration history, or developer-owned product code; 4. promote reusable behaviour to Core first, then update documentation and consumers; 5. preserve the existing migration ledger and adapt only through new forward migrations; 6. run the CLI plan, review collisions, update, verify exact hashes, and advance the lock only to a reachable Core commit. Specdrive may retain licensed or product-specific presentation in developer-owned CSS, adapters and `(project)` routes. Reusable Auth, claims, route protection, organisation and API contracts belong in Core. ## New project For a new application, install the released Core with the Supacharger CLI, then configure the developer-owned files: - `src/supacharger.config.ts` for feature policy and routes; - `src/styles/` for the product's semantic auth, account and organisation presentation; - `src/supacharger.adapters/` for product composition and extensions; - `src/app/(project)/` for product routes; - `messages/` for complete English source copy and deliberate translations; - `supabase/templates/` and new migrations for product-owned database extensions. Connect the resulting implementation work to Specdrive in small, testable slices. Each slice should state the Core contract it uses, its product-owned extension, its migration or deployment consequence, and its acceptance evidence. --- ## Database, profiles, and storage Supacharger keeps internal data in the unexposed `app` schema. Browser-callable RPCs live in `api`; trusted service-only Stripe, webhook, and maintenance RPCs live in `api_edge`. New application tables should follow the same boundary and enable RLS as defence in depth. ## Canonical profile Every Supabase Auth user has one `app.profiles` row keyed by the Auth UUID. Auth insert and email-update triggers maintain it automatically. A forward repair migration fills rows missing from installations that created Auth users before those triggers existed without overwriting existing profiles. Repaired usernames remain null so the application's configured identity and onboarding policy remains authoritative. Shared fields are `first_name`, `last_name`, `username`, `email`, `avatar_path`, `header_image_path`, `language_code`, `created_at`, and `updated_at`. Applications may add product-specific profile fields or a one-to-one extension. Do not create another shared user table. Username is nullable and case-insensitively unique when populated. `PROFILE_IDENTITY.USERNAME` decides whether it is disabled, optional, or required; it never adds or removes the database column. Completion always requires non-empty first and last name and requires username only when the caller passes `input_username_required: true`. `POST_SIGN_IN_ONBOARDING` decides whether incomplete signed-in users are redirected; it does not control row creation. Identity display falls back from names, to an enabled populated username, to owner-only email, to `Account`. Email never enters public projections. The current-user profile endpoint may return the owner's email. General identity lookup uses `api.get_user_profiles_by_identifiers()` and deliberately returns a safe projection without email or unrestricted profile data. ## Private profile media The `user-avatars` bucket is private. Store object paths rather than public URLs: - `/avatar-` for avatars; - `/headers/header-` for header images. Uploads accept JPEG, PNG, and WebP files up to 5 MB. Owners control writes within their own prefix. Render profile images with short-lived signed URLs; do not turn the bucket public or persist signed URLs in `app.profiles`. ## Account summary for application chrome Use the managed account-summary boundary for headers, sidebars, and account menus. `api.get_account_summary_by_current_user()` reads the signed-in profile, active organisation context, membership role, and organisation count in one database query. The server-only `loadAccountSummary()` service verifies the user, validates that response, derives the display name and initials, and signs only the private media the current summary needs. `GET /api/account/summary` exposes the same safe result with private no-store caching. Menus keep their product-specific visual design but should read through `useAccountSummary()`, not query the profile RPC or Storage directly. After a profile or avatar mutation succeeds, call `publishAccountSummaryChanged()`. Mounted menus then refresh through the shared `supacharger:account-summary-changed` event. The shared signed-media resolver batches paths by bucket with one `createSignedUrls()` call per bucket, avoiding one signing request per image. ## Optional organisation data Every aligned schema installs private organisation, membership, invitation, access-request, and session-context tables. They remain unused when `ORGANISATIONS.ENABLED` is false. Browser roles have no direct table access. Authenticated application code calls `api.organisations(input_payload)`; the dispatcher derives the user, verified email, and Auth `session_id` from the JWT. The Auth hook can project the active organisation into refreshed claims. RLS must still verify membership at the data boundary. The private `organisation-logos` bucket stores organisation avatars and headers. It accepts JPEG, PNG, WebP, and GIF objects up to 5 MB. Store paths under the organisation UUID and persist paths rather than signed URLs. Members can read media for their organisations; owners and admins control writes. See [Organisation management](./organisations.md) for actions, role rules, invitations, access review, session refresh, and the Specdrive compatibility boundary. ## Applying and checking migrations Create changes with `supabase migration new`, apply them to a clean local database, run `supabase db lint --local`, then regenerate TypeScript types for `public,api,api_edge,app`. Exposed RPC argument names must not use the historical `p_` prefix. Prefer descriptive names; use `input_` where a parameter could collide with a table column or output field, and `result_limit` for limits. Qualify columns and variables in SQL rather than relying on a prefix. A profile schema change is incomplete until the Proxy and server-access boundary, callbacks, forms, uploads, RPC documentation, Bruno requests, and generated types all use the same field names. --- ## Deleting users This capability is under development. Supacharger does not yet expose a managed account-deletion action because deletion policy is a product and compliance decision, not a safe universal default. The planned configuration will let an application choose between: - deactivating an account while retaining records required for audit, billing or legal obligations; - permanently deleting the Auth user and application data after an explicit confirmation and recovery window. A future Supacharger skill will help developers declare which product tables and Storage paths belong to a user. It will use that ownership map to plan foreign-key cascades, deliberate archival or anonymisation, and file deletion without guessing relationships. Until that contract is available, design deletion as an application-specific, reviewed server workflow. Revoke sessions first, authorise every step, avoid a browser-callable service-role boundary, inventory external processors, and test both partial failure and retry behaviour. --- ## Deploy and production readiness Complete the [hosted setup wizard](../guides/setup-wizard.md) first. It establishes the hosted Supabase project, Vercel deployment, Auth URLs and policy, SMTP, templates, and end-to-end authentication checks. This page covers the Vercel deployment sequence and the broader application review before opening the product to real users. ## Vercel deployment sequence 1. Connect the repository to the intended Vercel team and project. 2. Select the production branch and verify the framework and root-directory settings. 3. Add environment variables for Development, Preview, and Production. 4. Produce an initial deployment and attach the canonical production domain. 5. Set `NEXT_PUBLIC_SITE_URL` to that HTTPS origin and redeploy. 6. Configure the same origin as Supabase Auth's Site URL. 7. Add exact allowed redirects for callbacks, confirmations, and password recovery. 8. Complete the wizard's production authentication tests. See [Configure environment variables](../guides/hosted-setup/03-environment-variables.md) and [Deploy and establish URLs](../guides/hosted-setup/04-deploy-and-urls.md) for the actual values and verification checklist. ## Preview deployments Choose deliberately whether previews use production or a separate staging Supabase project. A staging project is preferable when branches are untrusted or preview activity must not modify production data. If preview authentication is enabled against a project, add only the required Vercel preview pattern to that project's redirect allow-list. Do not make the production Site URL a preview URL. ## Redeployment rule Vercel environment-variable changes do not alter a deployment that has already been built. Redeploy after adding or changing build-time or runtime configuration, then confirm the new deployment is the one serving the production domain. ## Database access - Enable RLS on every table exposed through the Data API. - Confirm policies authorize the intended owner, organization, or role rather than merely checking `TO authenticated`. - Remember that an `UPDATE` operation also needs a matching `SELECT` policy and should normally use both `USING` and `WITH CHECK`. - Review grants independently from RLS. A role must be able to reach an exposed object before RLS can filter its rows. - Keep internal data in an unexposed schema and expose narrowly scoped functions or views where needed. - Use `security_invoker = true` for Postgres 15+ views that must obey the caller's RLS. - Review every `SECURITY DEFINER` function, keep genuinely privileged functions out of exposed schemas, restrict `EXECUTE`, and validate the caller inside the function. Run Supabase's database and security advisors and resolve each finding according to the application's access model. Never disable RLS or add broad grants simply to remove a warning. ## Keys and environment isolation - Use a publishable key in the browser. - Keep Supabase secret or legacy service-role keys server-only. - Confirm no Stripe secret, webhook secret, SMTP credential, or database password uses a `NEXT_PUBLIC_` variable. - Use separate hosted projects and secrets for staging and production. - Rotate any credential that has appeared in Git history, a client bundle, build output, or public logs. - Restrict who can read or change production environment variables in Vercel and Supabase. ## Authentication and abuse - Keep email confirmation enabled when the product requires verified addresses. - Set a reasonable Email OTP Expiration and compatible resend cooldown. - Use custom SMTP with SPF, DKIM, and DMARC. - Add CAPTCHA or Turnstile to public email-triggering actions when automated abuse is plausible. - Protect Supabase organization accounts with MFA and maintain more than one appropriate owner. - Enable only the Auth providers and account-change flows the application actually supports. ## Edge Functions and webhooks - Restrict CORS to the intended origins; do not ship a permissive wildcard with credentials. - Validate webhook signatures before parsing or acting on event data. - Make event ingestion idempotent and safe to retry. - Store webhook and reconciliation secrets only in the server environment. - Bound request bodies, validate inputs, and return safe errors without internal credentials or stack traces. - Ensure background work is awaited or explicitly handed to the runtime's supported continuation mechanism. ## Application behavior - Remove sensitive debug logging and confirm production logs do not include tokens, cookies, passwords, full payment payloads, or personal data that is not operationally necessary. - Verify error, not-found, maintenance, and authentication-failure pages on the production domain. - Exercise one permitted and one forbidden operation for every important user role. - Confirm onboarding, organization selection, billing access, and account deletion behave correctly where enabled. - Test backups and document the restore procedure before relying on them. - Define monitoring and ownership for Auth, database, Vercel, Stripe, and email-delivery failures. ## Release record Record the deployed commit, migration version, Supabase and Vercel project identities, enabled Auth methods, important security exceptions, test date, and reviewer. Store identifiers and decisions—not secret values. Repeat the relevant checks after changing domains, Auth policy, RLS, privileged functions, providers, environment variables, or email infrastructure. ## Final deployment checkup Run this check after the implementation is reviewed and before declaring an environment ready: - the consumer lock references a published, reachable Core commit or immutable tag; - every exact managed path matches that lock and developer-owned CSS, adapters, configuration and catalogues remain intact; - lint, type checks, Core contract tests, project tests, English catalogue validation and Bruno parity pass; - the linked migration list contains only reviewed forward migrations, the dry run is understood, and a suitable backup exists; - hosted migrations have been applied and the ledger is rechecked; - `app.custom_access_token_hook` is selected under hosted Authentication Hooks and a refreshed test session contains only the expected safe claims; - hosted Auth URLs, email templates, OTP length, MFA policy, SMTP, Storage policies and secrets match the target environment; - logout, login, OTP, password reset, account changes, billing recovery and organisation switching have been exercised in the deployed browser; and - logs and alerting expose failures without recording passwords, tokens or private customer data. Do not treat checked-in `supabase/config.toml` as proof of a hosted setting. Local and hosted Auth configuration are separate release surfaces. --- ## Deployment Checkup :::warning Merged page This checklist has been merged into [Deploy and production readiness](./deploy.md). Keep this page only as a legacy pointer for older links. ::: Run this check after the implementation is reviewed and before declaring an environment ready: - the consumer lock references a published, reachable Core commit or immutable tag; - every exact managed path matches that lock and developer-owned CSS, adapters, configuration and catalogues remain intact; - lint, type checks, Core contract tests, project tests, English catalogue validation and Bruno parity pass; - the linked migration list contains only reviewed forward migrations, the dry run is understood, and a suitable backup exists; - hosted migrations have been applied and the ledger is rechecked; - `app.custom_access_token_hook` is selected under hosted Authentication Hooks and a refreshed test session contains only the expected safe claims; - hosted Auth URLs, email templates, OTP length, MFA policy, SMTP, Storage policies and secrets match the target environment; - logout, login, OTP, password reset, account changes, billing recovery and organisation switching have been exercised in the deployed browser; - logs and alerting expose failures without recording passwords, tokens or private customer data. Do not treat checked-in `supabase/config.toml` as proof of a hosted setting. Local and hosted Auth configuration are separate release surfaces. --- ## Edge Functions Supacharger Edge Functions are TypeScript services deployed globally through Supabase. Keep their source under `supabase/functions/`, review it with the application, and deploy it separately from the Next.js/Vercel release. Treat every Edge Function as a server trust boundary. Validate structured request data before privileged work or RPC calls and retain separate authentication, authorisation, file-content, and database protections. See [Input validation](./input-validation.md) for the shared Zod and PostgreSQL contract. Edge Functions use Deno rather than the Next.js Node.js runtime. The application's root `tsconfig.json` therefore excludes `supabase/functions/**/*`; this prevents `npm run build` from trying to resolve Deno globals, explicit `.ts` imports, URL imports, and `jsr:` packages. Keep that exclusion when upgrading or creating a consumer. Validate Edge Function source with the dedicated Deno tests described below. ## Secure image resizing and compression `process-image-upload` is the reusable authenticated image processor installed by Supacharger. It accepts JPEG, PNG, or WebP source images, applies a code-owned resize/encoding policy, uploads the result to Supabase Storage, and returns the stored path plus dimensions and the configured URL form. The function is not public. `supabase/config.toml` keeps `verify_jwt = true`, and the handler authenticates with `withSupabase({ auth: 'user' })`. The upload uses the caller's RLS-scoped Supabase client. It does not use a secret/service-role key. ### Request Send multipart form data with only: - `file`: the source image; and - `target`: a configured target name such as `profile_avatar` or `profile_header`. ```ts const body = new FormData(); body.set("file", imageFile); body.set("target", "profile_avatar"); const { data, error } = await supabase.functions.invoke( "process-image-upload", { body, }, ); ``` Never send a bucket, destination folder, filename, output format, width, height, quality, compression value, visibility, or user ID. Those values are intentionally absent from the HTTP contract. ### Target policy Applications own `supabase/functions/_shared/image-targets.ts`. Each target fixes: - Storage bucket and path below the authenticated user's ID; - server-generated filename prefix; - JPEG or PNG output; - `inside` or centred `cover` resizing; - maximum width and height; - JPEG quality percentage or lossless PNG compression effort; - input byte and decoded-pixel limits; - cache control; and - private signed-URL or intentional public-URL behaviour. The default targets are: | Target | Destination | Result | | ---------------- | -------------------------------------------------- | -------------------------------------------------------------- | | `profile_avatar` | `user-avatars//avatar-.jpg` | centred 300×300 maximum JPEG, quality 80, one-hour signed URL | | `profile_header` | `user-avatars//headers/header-.jpg` | centred 1600×600 maximum JPEG, quality 80, one-hour signed URL | The private `user-avatars` bucket remains protected by Storage RLS. The function never upscales, strips embedded metadata after applying image orientation, composites JPEG transparency onto white, generates a UUID filename, and uploads with `upsert: false`. To add lossless PNG output, create an application target with `outputFormat: 'png'` and `pngCompressionPercent`. PNG compression changes encoding effort/size without discarding pixels. JPEG targets use `qualityPercent` from 1–100. ### Helper functions The resize implementation is split into reusable helper modules so product-owned Edge Functions can add image flows without copying the HTTP handler. `defineImageUploadTargets()` validates the checked-in target map at load time, including safe names, path prefixes, dimensions, byte limits, pixel limits, quality, compression, visibility and signed-URL rules. `transformAndStoreImage()` is the shared server-side helper that receives a `File`, authenticated Supabase client, selected target and current user ID. It rejects unsupported MIME types, enforces the target byte limit, decodes with the pinned ImageMagick WASM runtime, applies the target resize mode, strips metadata, writes JPEG or PNG output, stores the object below the user's Storage prefix, and returns the stored path, dimensions, byte size, content type and configured public or signed URL. `supportsImageMimeType()` is available for early request checks before invoking the heavier transformer. The resize internals support `inside` for proportional downscaling within maximum dimensions and centred `cover` for fixed-ratio crops such as avatars and headers. Both modes deliberately avoid upscaling. Assume every signed-in user can call every configured target directly. A target is safe only when its Storage policy authorises that caller and its path is scoped to the caller's user ID. If a destination belongs to a project, organisation, or another resource, create a narrow wrapper that verifies current membership/ownership before using the shared transformer; do not accept an unchecked resource ID in the generic function. ## Limits and errors Starter targets accept source files up to 5 MiB and 25 megapixels after decoding. Target policy may lower these limits and is capped at 10 MiB, 40 megapixels, and 4096 pixels per configured output dimension. Hosted Edge Function CPU and memory limits still apply; keep transforms small and move batch or heavyweight media work to a background service. Common responses are: - `401` for a missing or invalid user session; - `400` for a missing file or unknown target; - `413` when the source exceeds the target byte limit; - `415` for an unsupported declared or decoded input type; - `422` for corrupt images or excessive decoded dimensions; and - `500` when Storage or signed-URL creation fails. ## Local verification and deployment ```bash npm run test:image-transform npm run test:image-transform-runtime npx supabase functions serve process-image-upload npx supabase functions deploy process-image-upload ``` The automated runtime test loads the pinned ImageMagick WASM package and verifies real JPEG and PNG output. Before deploying a new target, also test its Storage RLS with a real authenticated user. Do not use `--no-verify-jwt` for deployment. The Edge Function pins `@imagemagick/magick-wasm`, `@supabase/server`, and the Supabase client version used for types. Native Node image libraries such as Sharp are not supported in the Supabase Edge runtime; use the checked-in WASM transformer. --- ## Extend You can extend with prebuilt functionality. See a list of extensions here https://get.supacharger.dev/source/plugins/ You will need to add your github username ## How To install Supacharger Extensions ### Using the CLI See the cli documentation install function here ### Using Git This requires a github account. You need to get a License from the store this will grant you read permissions on the github repository. You will recieve an email with instructions and the github url to use. To import the code as a git submodule: ````bash cd / git submodule add git@github.com:glowplug-studio/supacharger-ext-brevo-newsletter.git src/app/\(supacharger\)/\(plugins\)/brevo-newsletter ```` if promopted enter your local SSH key passkey, or git creds. After successfully cloned enabled the module in ````bash src/app/(project)/(plugins)/brevo-newsletter/brevo-newsletter/config.js ```` set ````js exports.SC_PLUGIN_CONFIG = { PLUGIN_ENABLED: true, ```` Then rebuild the project ````bash npm run build npm run dev ```` Pulling updates to the submodule > ⚠️ Warning! this might cause breaking changes, be prepared to roll back. ````bash cd path/to/submodule git checkout main # or the branch you track, e.g. master git pull origin main ```` ## Updating a plugin Probaly use the install with -f flag ## Manually Removing a plugin Navigate to the project root. ````bash cd / ```` Remove the plugin folder ````bash rm -rf src/app/\(supacharger\)/\(plugins\)/brevo-newsletter ```` Deinitialize the submodule: ````bash git submodule deinit -f src/app/\(supacharger\)/\(plugins\)/brevo-newsletter ```` Remove the submodule entry from the index: ````bash git rm --cached src/app/\(supacharger\)/\(plugins\)/brevo-newsletter ```` Remove the submodule folder from .git/modules: ````bash rm -rf .git/modules/src/app/\(supacharger\)/\(plugins\)/brevo-newsletter ```` You can then use Supabase studio locally to remove functions and tables and push up the changes or cloud and pull down with migrations. You will need to check files that are modified during install by looking in the Plugins install.js file. # Using an Plugin in your Project ## Code Typically you will need to mnaully integrate the plugin where the documentation suggests. ## Migrations & Seed data Use the Supabase CLI to run the sql files ## Edge & RPC functions Use the Supabase CLI to push your edge functions and RPC ## Modifying and editing plugins & RPC functions Feel free to copy the files out of the (supacharger) file structure and modify to suit your own needs. Modifications of the plugin in-situ may result in lost changes if updates are pulled from the gitsubmodule. # Creating a plugin We are exploring how to reward developers who want to sell functionality with the dev bounty. ## Anatomy of a Plugin Files are colocated using nextjs folder structure best practice App Router Considerations Any page file inside your module will appear relative to /app in the site navigation as NextJS ignores the bracketed folders Plugins will be installed to ````bash src/app/(project)/(plugins) ```` /my-plugin /my-plugin/index.tsx /my-plugin/actions.ts /my-plugin/config/install.js /my-plugin/config/config.js /my-plugin/config/supabase /my-plugin/components --- ## Git ## Suggested Branching for Vercel Out of the box, Vercel's Hobby plan deploys `main` or `master` to production. All other branches pushed to origin automatically build and deploy to preview URLs. To prevent builds from running on every branch, use `vercel.json` to specify which branches should not deploy when pushed to origin. `/vercel.json` You need a Pro or Enterprise account to associate a branch with an environment. On the free plan, you cannot prevent automatic builds by environment or assign a branch to a custom environment. There is a workaround. Create a `preview` branch and only merge to, push to, or accept pull requests against that branch when you want a preview build. https://vercel.com/docs/project-configuration/git-configuration The limits are high, but avoid burning build minutes on branches that are only storing work in progress. The Hobby plan currently allows 100 build hours per month shared across all projects on the account. You can specify directories like feat/ to exclude as well. ``` JSON { "$schema": "https://openapi.vercel.sh/vercel.json", "git": { "deploymentEnabled": { "main": true, "preview": true, "development": false, "feat/*": false } } } ``` Alternatively, turn off all deployments: https://vercel.com/docs/project-configuration/git-configuration#turning-off-all-automatic-deployments ## Local maintenance Run the local linter or formatter commands defined by the application repository rather than assuming this documentation site's scripts are identical to Core. When an application includes ESLint auto-fixes, the usual repair command is: ```bash npx eslint --fix . ``` --- ## Application configuration Application settings live in the developer-owned `src/supacharger.config.ts`. The CLI-managed `src/supacharger/supacharger-config.ts` is only a stable re-export and must not contain application values. Because the CLI preserves the developer file, review release notes when a core update introduces configuration keys and merge those keys deliberately. ## Metadata, root providers, and analytics `METADATA` configures the canonical site URL, child-page title template, indexing policy, standard favicon declarations, optional social image, colour scheme, and theme colour. `ROOT_PROVIDERS` enables the standard internationalisation, theme, and toast layers while the editable root layout retains each application's concrete provider implementations. `ANALYTICS` independently configures Google Analytics and Vercel Analytics. Use `NEXT_PUBLIC_SITE_URL` for the stable production origin and `NEXT_PUBLIC_GOOGLE_ANALYTICS_ID` for an optional GA4 measurement ID. See [SEO, document metadata, and analytics](../Marketing-and-Analytics/seo-and-analytics.md) for the complete configuration and page-level override patterns. During first deployment, configure local and Vercel scopes through the [environment-variable wizard step](../guides/hosted-setup/03-environment-variables.md). Any `NEXT_PUBLIC_` value is sent to the browser; secret Supabase, Stripe, SMTP, and webhook credentials must remain server-only. ## Authentication session configuration ```ts AUTH_SESSION: { VERIFICATION_MODE: 'claims', ALLOW_ANONYMOUS_USERS: false, }, ``` `claims` is the standard SSR verifier and validates the JWT signature and expiry. Use `user` only when every matched request needs a fresh Auth-server user/session check. Anonymous Supabase Auth users have the `authenticated` database role, so they remain excluded unless the application explicitly sets `ALLOW_ANONYMOUS_USERS` to true and its RLS supports them. ## Authenticated destinations ```ts USER_REDIRECTS: { AUTHED_USER: { HOME_PATH: '/', AUTHGUARD_REDIRECT_DESTINATION: '/', LOGIN_REDIRECT_DESTINATION: '/', }, }, ``` - `HOME_PATH` is the application's normal home destination for an authenticated user. - `LOGIN_REDIRECT_DESTINATION` is the destination after a successful passwordless or OAuth PKCE callback. It normally matches `HOME_PATH`; applications whose `/` route is public marketing content may set both values to an authenticated route such as `/account`. - `AUTHGUARD_REDIRECT_DESTINATION` is the destination used when an authenticated user requests an authentication page they should no longer see. The callback adds a one-time login notice to the configured destination. The root authentication notice displays the localised successful-login toast and immediately removes that query parameter from the URL. Subscription enforcement is separate: `BILLING_ACCESS.REQUIRED: false` prevents authentication from detouring to `BILLING_ACCESS.REDIRECT_PATH`. See [Login redirects and subscription paywalls](./login-redirects-and-paywalling.md) for the exact callback order, the difference between callback gating and a complete paywall, and configuration examples. ## Billing configuration ```ts BILLING: { AUTOMATIC_TAX: false, BILLING_ADDRESS_COLLECTION: 'auto', ALLOW_PROMOTION_CODES: true, }, BILLING_ACCESS: { REQUIRED: true, FEATURE_LOOKUP_KEY: null, REDIRECT_PATH: '/account/billing/subscribe?full=1', }, ``` - `AUTOMATIC_TAX` asks Stripe to calculate tax during Checkout. Stripe registrations and Product tax codes must also be configured. - `BILLING_ADDRESS_COLLECTION` is `auto` or `required`. - `ALLOW_PROMOTION_CODES` controls Stripe promotion-code entry. - `REQUIRED` controls whether an authenticated user without the configured entitlement is redirected before reaching the normal login destination. Set it to `false` when authentication must not require a subscription. - `FEATURE_LOOKUP_KEY` authorises through the projected entitlement with that stable key. `null` retains the compatibility rule of any active/trialling Subscription. - `REDIRECT_PATH` is used when required billing access is absent. In the current core, this billing check runs in the magic-link/social callback and in full-app layouts through `requireAppAccess()`. Password sign-in does not pass through that callback, so its destination must inherit the correct server layout. Proxy remains claims-only. Put billing acquisition beneath `requireOnboardedUser()` so a user without access can reach it, and protect paid APIs, Server Actions, and data with independent server-side entitlement checks. See [Login redirects and subscription paywalls](./login-redirects-and-paywalling.md). Payment methods are not listed in code. Configure Dynamic Payment Methods in Stripe Dashboard. `BILLING_ACCESS` is the complete current billing-access configuration contract. Existing applications must remove the obsolete `ACCOUNT_FORCE_SUBSCRIPTION` and `ACCOUNT_ENFORCE_SUBSCRIPTION_PATH` properties from their developer-owned configuration before adopting this Core version; `supacharger doctor` reports them but does not rewrite the file. ## Authentication methods ```ts AUTHENTICATION: { EMAIL_PASSWORD: { SIGN_IN: true, SIGN_UP: true }, PASSWORDLESS_EMAIL: { SIGN_IN: 'otp', SIGN_UP: 'disabled', OTP_LENGTH: 6, }, SIGN_UP_EMAIL_VERIFICATION: 'otp', MFA_TOTP: { REQUIRED_FOR_SIGN_IN: false }, }, ``` `EMAIL_PASSWORD` controls password actions independently. `PASSWORDLESS_EMAIL.SIGN_IN` and `SIGN_UP` each accept one exclusive mode: `disabled`, `otp`, or `link`. Sign-in always uses `shouldCreateUser: false`. `OTP_LENGTH` accepts 6 through 10 and controls the rendered fields; keep it equal to local `[auth.email] otp_length` and the hosted Supabase setting. `SIGN_UP_EMAIL_VERIFICATION` is separate from passwordless login and accepts `disabled`, `otp`, `link`, or `otp-and-link`. Managed TOTP enrolment is always available in Account Security. `REQUIRED_FOR_SIGN_IN` challenges enrolled AAL1 sessions before the final sign-in destination. See [Account security and TOTP MFA](./mfa-and-security.md). ## Mobile deep-link configuration ```ts MOBILE_DEEP_LINKING: { ENABLED: false, ASSOCIATED_PATHS: [ '/auth/callback', '/auth/confirm', ], IOS: { APP_IDS: [] }, ANDROID: { APPS: [] }, }, ``` Enable this only after the production HTTPS domain and signed native identifiers are ready. `IOS.APP_IDS` contains Apple App ID prefix and bundle identifier pairs. Each `ANDROID.APPS` entry contains a package name plus one or more uppercase SHA-256 signing-certificate fingerprints. Supacharger generates the platform well-known association responses; the native applications still declare and validate the matching hosts and paths. See [Mobile login verification with deep links](./mobile-auth-deep-linking.md). ## Profile identity and onboarding ```ts PROFILE_IDENTITY: { USERNAME: 'disabled', AVATAR: 'optional', HEADER_IMAGE: 'optional', }, POST_SIGN_IN_ONBOARDING: { REQUIRED: true, REDIRECT_PATH: '/account/setup-profile', }, ``` `USERNAME` accepts `disabled`, `optional`, or `required`. The database field remains installed and nullable in every mode. Disabled removes it from shared forms, username routes, and identity display. Optional accepts blank. Required adds it to completion. `AVATAR` and `HEADER_IMAGE` accept `disabled` or `optional` and control their managed account media fields. Storage remains installed so the policy can change without rewriting migration history. See [Account settings](./account-settings.md) for the full settings contract and extension seams. When onboarding is required, the callback and protected server layouts call `api.is_profile_complete_by_current_user(input_username_required)` and redirect users whose required fields are missing. Proxy performs no profile or billing RPC. `REDIRECT_PATH` is application-owned and must point to a real setup page beneath `requireVerifiedUser()`, outside the onboarding guard it recovers. Core ships `/account/setup-profile`; applications may retain richer product-specific UI at their configured path. This setting controls routing only. Supabase Auth triggers create and synchronise `app.profiles` for every user even when enforcement is false. ## Organisations ```ts ORGANISATIONS: { ENABLED: false, AUTHENTICATION_HANDLE: 'disabled', CHOOSER_PATH: '/account/organisation', ROUTE_MODE: 'root-handle', PROFILE_MEDIA: true, }, ``` Organisation tables and service adapters remain installed when disabled. `AUTHENTICATION_HANDLE` accepts `disabled`, `optional`, or `required`, and applies only when organisations are enabled. Selection occurs after authentication; creation belongs in authenticated onboarding. Applications must not treat the handle as untrusted Auth metadata. ## Other settings Branding such as `SITE_TITLE`, document metadata, root-provider switches, analytics, authentication providers, route guards, redirects, password policy, locale configuration, and toast presentation also belong to the developer-owned configuration seam. Use the existing typed values and keep application-specific policy out of CLI-managed modules. --- ## Helpful random bits :::warning Stub note This page is a legacy holding note and is not a complete Supacharger Core reference. The only useful local maintenance command has been moved to [Git](./git.md). ::: ## Sort this out later Import sorts ```bash npx eslint --fix . ``` --- ## Input validation Validate every untrusted value at the server boundary that acts on it. Browser validation gives people fast feedback, but it can be bypassed and TypeScript types are removed at runtime. ## Forms Use Zod for both client and server validation when both layers accept the same form contract. The browser may parse during editing or submission to show field errors, but the receiving Server Action or route handler must parse the submitted `FormData` or JSON again before it performs a side effect, calls Supabase, or persists data. ```ts import { z } from 'zod'; const profileSchema = z.object({ firstName: z.string().trim().min(1).max(100), lastName: z.string().trim().min(1).max(100), }); export async function updateProfile(formData: FormData) { 'use server'; const result = profileSchema.safeParse({ firstName: formData.get('firstName'), lastName: formData.get('lastName'), }); if (!result.success) { return { ok: false, fieldErrors: result.error.flatten().fieldErrors }; } // Authorise the caller, then persist result.data. } ``` Keep the schema beside the domain or operation that owns the contract so a form component, Server Action, and route handler can reuse it without making presentation code authoritative. If client and server inputs intentionally differ, derive separate schemas from shared primitives instead of weakening one schema to cover both. Return safe, serialisable validation errors. Do not expose stack traces, SQL messages, raw Supabase errors, or other internal details to the browser. Test valid input plus missing, malformed, wrong-type, and boundary values, and confirm rejected input causes no side effect. ## Supabase Edge Functions An Edge Function is another server trust boundary. Where the request contains structured body, query, path, header, or option values, validate them with Zod before choosing privileged behaviour, using a service client, or calling an RPC. Keep the Zod import compatible with the project's checked-in Deno runtime and pin it consistently with the Edge Function dependency policy. Zod validates the declared shape. Validate uploaded file size and content separately; do not trust a claimed MIME type, filename, Storage path, URL, or resource identifier. Authentication, authorisation, allow-lists, and Storage policies remain required. ## Supabase RPCs PostgreSQL RPCs cannot run Zod. Validate untrusted RPC-bound input in the calling Server Action, route handler, or Edge Function, then enforce the contract again in PostgreSQL with typed parameters, constraints, explicit checks, transaction invariants, grants, authentication, authorisation, and RLS as appropriate. RPCs called directly from the browser must remain safe when invoked without your form. Client-side Zod can improve feedback, but only PostgreSQL can authoritatively reject an invalid or unauthorised direct RPC request. Zod complements these controls; it does not replace database constraints, RLS, CSRF protection, permission checks, rate limiting, or business invariants. --- ## Localisation Supacharger uses [`next-intl`](https://next-intl.dev/docs/getting-started/app-router) with the Next.js App Router and **without locale-based routing**. Language is a user preference inside the SaaS application, so routes have one stable shape in every language: ```text /account English, French, or another supported language /en/account Not used /fr/account Not used ``` Do not add a top-level `[locale]` route segment or `next-intl` routing middleware for the standard Supacharger setup. ## Marketing-site language handoff A separate marketing application can pass its selected language into the authenticated SaaS by adding `lang` to the destination URL: ```text https://app.example.com/login?lang=fr https://app.example.com/login?plan=pro&lang=en ``` The standard query syntax is `?lang=fr`, not `lang?=fr`. Supacharger validates the value against `SUPPORTED_LOCALES`, writes an accepted value to the HTTP-only locale cookie, and redirects to the same pathname with only `lang` removed. Other query parameters, such as `plan=pro`, remain intact. The query parameter is therefore a one-time handoff and never becomes part of the application's route structure. An unsupported, malformed, or uppercase value is ignored and removed. Add a language to `SUPPORTED_LOCALES` before linking to it from the marketing site. ## Application-owned files The starter installs these localisation files: ```text messages/ en.json fr.json src/i18n/ config.ts request.ts ``` These files are developer-owned. `supacharger coreupdate` does not treat changes to them as core conflicts and does not add, overwrite, or delete catalogues when `messages/` already exists. SnapScreen and Wakekeeper use these `src/` paths directly. Specdrive follows the same route and preference contract but uses repository-root `i18n/config.ts`, `i18n/request.ts`, and `messages/`. Every consumer supports the transient `?lang=` handoff and profile restoration; translation coverage remains application-owned and does not imply that all existing UI copy is translated. `src/i18n/config.ts` defines the supported locales, fallback locale, and cookie name: ```ts export const SUPPORTED_LOCALES = ['en', 'fr'] as const; export const DEFAULT_LOCALE = 'en'; export const LOCALE_COOKIE_NAME = 'supacharger_locale'; ``` Keep a matching `messages/.json` file for every entry in `SUPPORTED_LOCALES`. The request configuration validates the cookie before loading a catalogue, so an unknown or malformed locale falls back to `DEFAULT_LOCALE` instead of attempting an arbitrary dynamic import. ## Request and client configuration `src/i18n/request.ts` reads the locale preference and returns the locale and messages to `next-intl`. The root layout uses `getLocale()` and `getMessages()` and passes both to `NextIntlClientProvider`, which makes translations available to Server and Client Components. Supacharger enables Next.js Cache Components in every application's merge-managed Next configuration. Since the validated locale cookie determines both `` and the messages provider before the document can render, the developer-owned root layout exports `instant = false`. Next validates each route segment independently, so pages and nested layouts that directly perform request-time authentication also need an explicit boundary: `instant = false` for deliberately blocking identity/access checks, or local `Suspense` for streamable work. Cache Components makes request-time data dynamic by default; do not restore `dynamic = 'force-dynamic'` or cache user-specific authentication reads with `use cache`. The shared `test:auth-instant` contract checks these route segments. The `/api/intl` endpoint changes the preference. Send a supported locale as JSON: ```ts await fetch('/api/intl', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({locale: 'fr'}) }); ``` The endpoint validates the locale and stores it in an HTTP-only, same-site cookie. For an authenticated user, it also stores the code in the profile. Reload or refresh the current route after a successful change. The pathname does not change. ## Profile persistence and precedence The canonical profile column is `app.profiles.language_code`. It accepts lowercase two-letter codes (`^[a-z]{2}$`). The database constraint is deliberately language-agnostic; `SUPPORTED_LOCALES` is the application-level allowlist that controls which catalogues can actually be selected and loaded. Supacharger resolves preferences in this order: 1. A valid `?lang=` marketing handoff. 2. A valid `supacharger_locale` cookie created by an explicit selection. 3. The signed-in user's saved `language_code` when the browser has no valid locale cookie. 4. `DEFAULT_LOCALE`. When a signed-out visitor arrives with a valid handoff, Supacharger stores the cookie and marks it for profile synchronisation. After the visitor signs in, the preference is written to the profile. On another browser or device with no locale cookie, the supported saved profile value is restored into the cookie. Supacharger, SnapScreen, Wakekeeper, and Specdrive all persist this contract in `app.profiles.language_code`. Specdrive also exposes the language choice in Account Settings. ## Using translations Use a namespace from the active application's catalogue: ```tsx import {useTranslations} from 'next-intl'; export function SaveNotice() { const t = useTranslations('GlobalUI'); return

{t('buttonSaved')}

; } ``` Use `getTranslations` from `next-intl/server` in async Server Components, Server Actions, metadata, and route handlers. ## Authoring and translating copy Always add or update the English source value in `messages/en.json` when a localised key or its wording changes. English is the authoring language and must be complete for the affected surface. Before introducing or changing `useTranslations` or `getTranslations`, verify that its namespace and every statically referenced key exist with non-empty values in `messages/en.json`. Commit the consuming code and English catalogue together; otherwise `next-intl` raises `MISSING_MESSAGE` at runtime even when a secondary catalogue contains the namespace. Do not invent, machine-translate, copy the English wording into, or otherwise fill French or another secondary-language value unless translation into that language is explicitly requested. If catalogue structure or a validation check requires the new key in a secondary catalogue, add the key with an empty string; otherwise leave that catalogue untouched. Empty strings are untranslated placeholders, not completed translations, and should be reported as pending translation. Where a protected component supports older or incomplete developer-owned catalogues, keep an intentional English fallback so an empty or unavailable value does not expose a raw key or `MISSING_MESSAGE` error. Do not describe a locale or surface as translation-complete while placeholders remain. ## Adding a language 1. Add the locale to `SUPPORTED_LOCALES`. 2. Add `messages/.json` with every required namespace and key. 3. Add the locale to the application's language selector. 4. Test signed-out and signed-in screens, number/date formatting, and fallback behaviour. Because catalogues are not CLI-managed, a core upgrade cannot insert newly required strings. Review the release notes, add the required English values, and add empty secondary placeholders only where catalogue structure requires them. Fill those placeholders only as part of explicitly requested translation work. --- ## Login redirects and subscription paywalls Supacharger has three related settings, but they do different jobs: | Setting | What it controls | What it does not control | | --- | --- | --- | | `USER_REDIRECTS.AUTHED_USER.LOGIN_REDIRECT_DESTINATION` | The normal destination after password sign-in and after a successful magic-link or social OAuth callback | General “home” links and access control | | `USER_REDIRECTS.AUTHED_USER.HOME_PATH` | The authenticated home used by general navigation and flows such as completed password reset | The PKCE callback destination | | `BILLING_ACCESS.REQUIRED` | Whether the magic-link/social callback checks billing access and diverts a user without access | A site-wide route guard or database authorisation policy | Protected server routes use three access levels: | Boundary | Checks | Appropriate routes | | --- | --- | --- | | `requireVerifiedUser()` | Verified, non-anonymous identity according to configuration | Profile setup and other identity recovery | | `requireOnboardedUser()` | Verified identity, then configured profile completion | Subscription acquisition | | `requireAppAccess()` | Verified identity, onboarding first, then configured billing access | Full product routes | A recovery destination must not inherit the guard for the condition it recovers. Route groups can give `/account/setup-profile` a verified-only layout and `/account/billing/subscribe` an onboarded layout without changing either public URL. ## Redirect order in the current core For a magic link or social provider, Supabase first returns the browser to the allow-listed application callback, normally `/auth/callback`. Supacharger then processes the result in this order: 1. Exchange the PKCE code for a session and verify that a user exists. 2. If `POST_SIGN_IN_ONBOARDING.REQUIRED` is enabled and the profile is incomplete, redirect to `POST_SIGN_IN_ONBOARDING.REDIRECT_PATH`. 3. If `BILLING_ACCESS.REQUIRED` is enabled, call the server-side billing-access function. A user without access is redirected to `BILLING_ACCESS.REDIRECT_PATH`. 4. Otherwise, redirect to `LOGIN_REDIRECT_DESTINATION` and add the one-time successful-login notice. In compact form: ```text Supabase callback -> valid session? -> required profile complete? -> required billing access present? -> LOGIN_REDIRECT_DESTINATION ``` Password sign-in navigates directly to `LOGIN_REDIRECT_DESTINATION`; it does not run callback policy in `/auth/callback`. The matched protected server layout then enforces the appropriate verified, onboarded, or full-app boundary before rendering. Proxy remains claims-only and performs neither database check. `BILLING_ACCESS.REQUIRED: true` protects the canonical authenticated layout and callback journey, but it is not a substitute for handler-specific authorisation. Enforce entitlement checks in protected APIs and Server Actions that do not render through that layout, and keep Supabase Row Level Security as the final database boundary where appropriate. ## Recommended configurations ### One authenticated landing page, no forced subscription Use this when users may enter the product without paying: ```ts USER_REDIRECTS: { AUTHED_USER: { HOME_PATH: '/app', AUTHGUARD_REDIRECT_DESTINATION: '/app', LOGIN_REDIRECT_DESTINATION: '/app', }, }, BILLING_ACCESS: { REQUIRED: false, FEATURE_LOOKUP_KEY: null, REDIRECT_PATH: '/account/billing/subscribe?full=1', }, ``` This is the simplest default. All normal authenticated navigation and successful sign-ins converge on `/app`. Paid features should check their own entitlement when used. ### Public marketing home and authenticated product home If `/` is a public marketing page, do not use `/` as the authenticated destination unless returning signed-in users to marketing is deliberate: ```ts USER_REDIRECTS: { UNAUTHED_USER: { HOME_PATH: '/', AUTHGUARD_REDIRECT_DESTINATION: '/account/login', LOGOUT_REDIRECT_DESTINATON: '/', }, AUTHED_USER: { HOME_PATH: '/dashboard', AUTHGUARD_REDIRECT_DESTINATION: '/dashboard', LOGIN_REDIRECT_DESTINATION: '/dashboard', }, }, ``` Keeping the three authenticated destinations aligned avoids surprising differences between login, “home”, and attempts to revisit the login page. `LOGOUT_REDIRECT_DESTINATON` must be an application-relative path beginning with one `/`, never an absolute or protocol-relative URL. The managed logout button receives this path as JSON from `POST /account/logout` after the SSR session is cleared. A developer-owned direct link may use `GET /account/logout`, which clears the same session and responds with `303` plus that relative path in `Location`. The browser resolves both forms against the origin it opened rather than `NEXT_PUBLIC_SITE_URL`. ### Callback subscription detour Use this only when passwordless/social callbacks should send users without the required access to the subscription page: ```ts BILLING_ACCESS: { REQUIRED: true, FEATURE_LOOKUP_KEY: 'product_access', REDIRECT_PATH: '/account/billing/subscribe?full=1', }, ``` `FEATURE_LOOKUP_KEY` should be a stable entitlement lookup key. A value of `null` uses the compatibility rule of any `trialing` or `active` Subscription. The redirect path must exist beneath an onboarded-only boundary and must remain reachable by a profile-complete authenticated user who does not yet have access. Placing it beneath `requireAppAccess()` creates a self-redirect loop. If the whole product must be paywalled, also add server-side entitlement enforcement to protected page layouts, route handlers, Server Actions, and APIs. Apply the same policy to password sign-in so every authentication method behaves consistently. A successful Stripe Checkout return is not itself proof of access; use the local entitlement projection populated by webhooks and reconciliation. ### Different post-login and home destinations The two paths may deliberately differ: ```ts AUTHED_USER: { HOME_PATH: '/app', AUTHGUARD_REDIRECT_DESTINATION: '/app', LOGIN_REDIRECT_DESTINATION: '/welcome', }, ``` Use this only for an unconditional post-login landing page. For conditional profile setup, use `POST_SIGN_IN_ONBOARDING` instead; it runs before the billing check and normal callback destination. Ensure `/welcome` does not redirect an authenticated user back to login or create a loop. ## Supabase URL Configuration is a separate layer These Supacharger values are application paths after a session has been established. They do not replace the hosted Supabase settings under **Authentication → URL Configuration**. - Set the Supabase **Site URL** to the production origin. - Add the exact production callback URL, such as `https://example.com/auth/callback`, to **Redirect URLs**. - Add the password-reset and confirmation destinations used by the project. - Add localhost and preview URLs only for the environments that need them; prefer exact production paths over broad wildcards. - Keep `NEXT_PUBLIC_SITE_URL` aligned with the deployed origin so Supacharger builds the same callback origin that Supabase allows. Supabase validates the full `redirectTo` URL before returning a passwordless or social authentication flow. After the browser reaches `/auth/callback`, Supacharger applies the onboarding, billing, and login-destination decisions described above. See the official [Supabase Redirect URLs guide](https://supabase.com/docs/guides/auth/redirect-urls). ## Release checklist - Every configured path starts with `/` and exists in the application. - The onboarding destination inherits only `requireVerifiedUser()` and the subscription destination inherits only `requireOnboardedUser()`. - No recovery response repeats the same effective URL after one recovery hop. - All enabled authentication methods reach the intended product destination. - Password, magic-link, OTP, and social login are tested separately because they do not all use `/auth/callback`. - Paid pages and APIs reject missing entitlements on the server, independently of browser redirects. - Supabase RLS protects paid or private database data where client access is possible. - The hosted Supabase callback allow-list and `NEXT_PUBLIC_SITE_URL` match production. --- ## Account security and TOTP MFA The managed `/account/security` page shows the user's Auth providers and supplies real email, password, and authenticator-app controls. Email and password mutations reauthenticate password users at the server boundary. OAuth-only users see those controls disabled because they do not have a password identity to reauthenticate. ## Configure authenticator MFA ```ts AUTHENTICATION: { MFA_TOTP: { REQUIRED_FOR_SIGN_IN: true, }, }, ``` Enrolment and factor management are always displayed. `REQUIRED_FOR_SIGN_IN` sends an enrolled user whose session is at AAL1 through `/account/mfa` before completing a password, OTP, magic-link, or OAuth sign-in destination. If no verified factor exists, the session has no AAL2 step to perform; the user may enrol from Security. For local development, set both `enroll_enabled = true` and `verify_enabled = true` under `[auth.mfa.totp]` in `supabase/config.toml`, then restart the local Supabase stack. Hosted projects use their separate Auth MFA setting in the Supabase Dashboard; changing the local TOML file does not change a hosted project. Supacharger uses Supabase Auth's `listFactors`, `enroll`, `challengeAndVerify`, and `unenroll` operations. It never stores a TOTP secret in application tables. An unverified enrolment can be cancelled. A verified factor requires a current six-digit code when the session needs to step up before removal. ## Email and password changes `POST /api/account/email` parses the request with Zod, verifies the current user and password identity, reauthenticates the current password, and asks Supabase Auth to send the secure email-change confirmation. The redirect returns to `/account/security`. `POST /api/account/update-password` applies the configured password policy, verifies the current password, and calls `auth.updateUser`. OAuth-only accounts keep both forms visibly unavailable instead of exposing a control that cannot succeed. ## Product presentation The security route and non-visual behaviour are managed. A developer-owned `src/supacharger.adapters/account/security-page.tsx` starter may preserve an authorised product presentation. The CLI installs this adapter only when missing and never overwrites it. Specdrive uses this seam; its restricted presentation is not copied into the open-source Core. --- ## Mobile login verification with deep links Supacharger supports verified HTTPS links for mobile authentication. iOS calls these Universal Links; Android calls them App Links. The same email URL opens the installed app when the platform association succeeds and falls back to the web route when the app is unavailable. This guide covers magic-link sign-in, link-based email confirmation, and password-reset callbacks. An email OTP is different: the user copies or autofills the configured-length code into the Supacharger form, so that flow does not need to leave and reopen the app. ## How the callback works ```text app → Supabase Auth → email client ↓ installed app ← verified HTTPS callback → browser fallback ↓ same initiating client exchanges the one-use PKCE code ``` Supacharger's `/auth/callback` Route Handler exchanges the `code` for a session. Supabase PKCE codes expire quickly, can be exchanged only once, and require the verifier stored by the client that started the flow. Therefore: - a wrapped Supacharger web app must load the incoming URL in the same persistent web view and cookie store that requested the email; - a fully native app must start and complete the flow with the same native Supabase client and secure storage adapter; and - neither implementation should copy access tokens, refresh tokens, or PKCE verifiers into logs, analytics, application metadata, or another URL. See Supabase's [PKCE flow](https://supabase.com/docs/guides/auth/sessions/pkce-flow), [native mobile deep linking](https://supabase.com/docs/guides/auth/native-mobile-deep-linking), and [redirect URL](https://supabase.com/docs/guides/auth/redirect-urls) guidance. ## Configure Supacharger Edit the developer-owned `src/supacharger.config.ts` (`supacharger.config.ts` in Specdrive). Replace every example value with identifiers belonging to the application being signed: ```ts MOBILE_DEEP_LINKING: { ENABLED: true, ASSOCIATED_PATHS: [ '/auth/callback', '/auth/confirm', ], IOS: { APP_IDS: ['ABCDE12345.com.example.myapp'], }, ANDROID: { APPS: [ { PACKAGE_NAME: 'com.example.myapp', SHA256_CERT_FINGERPRINTS: [ '', ], }, ], }, }, ``` `IOS.APP_IDS` uses `.`, not the numeric App Store ID. `ANDROID.APPS` can contain separate debug, staging, or production packages. A package can contain multiple fingerprints during an intentional signing-key transition. The example Apple prefix, bundle identifier, Android package, domain, and fingerprint are placeholders. Public documentation must never reproduce an application's real values merely to provide an example. When a platform has no configured identifiers, its well-known endpoint returns `404`. Once enabled and configured, Supacharger generates current platform documents at: ```text https://app.example.com/.well-known/apple-app-site-association https://app.example.com/.well-known/assetlinks.json ``` Each URL must respond directly over HTTPS with status `200`, `Content-Type: application/json`, and no redirect. Configure every production subdomain independently. The generated iOS document uses Apple's current `appIDs` and `components` format; the Android document uses `delegate_permission/common.handle_all_urls`. ## Configure Supabase Auth In each hosted Supabase environment: 1. Set **Authentication → URL Configuration → Site URL** to the canonical production web origin, such as `https://app.example.com`. 2. Add exact production redirect URLs for every enabled flow: ```text https://app.example.com/auth/callback https://app.example.com/auth/confirm ``` 3. Use broad `/**` patterns only for local development or deployment previews. Prefer exact production paths. 4. If an email template constructs its own confirmation URL while the application supplies `emailRedirectTo`, use Supabase's `{{ .RedirectTo }}` variable as documented. Keep `{{ .Token }}` for the OTP template. 5. Configure hosted settings separately from `supabase/config.toml`; the repository file affects only the local stack. Supacharger's magic-link operation sends users to `/auth/callback`. Password recovery uses `/auth/callback?flow=recovery`, so it is covered by the same callback path and does not associate the internal `/account/reset-password/new` page. Link-based sign-up confirmation uses `/auth/confirm` with `token_hash` and `type`. OTP sign-up calls `verifyOtp` with the entered email and token and does not use the well-known endpoints. ## iOS Universal Links Follow Apple's [associated domains](https://developer.apple.com/documentation/xcode/supporting-associated-domains) and [Universal Link](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) guidance: 1. In Xcode, select the native target and add **Signing & Capabilities → Associated Domains**. 2. Add the exact host without a scheme, path, query, or trailing slash: ```text applinks:app.example.com ``` 3. Confirm that the signed target's application identifier exactly matches an `IOS.APP_IDS` entry. 4. Accept only the expected HTTPS host and paths when continuing the user activity. A web-wrapper bridge can use this shape: ```swift .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in guard let url = activity.webpageURL, url.scheme == "https", url.host == "app.example.com", ["/auth/callback", "/auth/confirm"] .contains(url.path) else { return } authWebView.load(URLRequest(url: url)) } ``` Use the web view and data store that initiated authentication. A native Swift client should instead give the verified URL to its native auth coordinator and complete the code exchange there. Apple fetches the association through its CDN and may cache it. The file must be named `apple-app-site-association` without a `.json` extension. Apple's [Universal Link diagnostics](https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links) recommend: ```bash sudo swcutil dl -d app.example.com sudo swcutil verify -d app.example.com -j ./apple-app-site-association \ -u 'https://app.example.com/auth/callback' ``` On a device, paste the link into Notes and long-press it. Typing the URL directly into Safari's address bar intentionally remains browser navigation and is not a valid Universal Link test. ## Android App Links Follow Android's [App Link intent-filter](https://developer.android.com/training/app-links/add-applinks), [website association](https://developer.android.com/training/app-links/configure-assetlinks), and [verification](https://developer.android.com/training/app-links/verify-applinks) guidance. Use the application ID from the native module's Gradle configuration. When Google Play App Signing is enabled, use the app-signing certificate fingerprint shown by Play Console—not the local upload-key fingerprint. Fingerprints are uppercase, colon-separated SHA-256 values. Declare verified HTTPS paths in `AndroidManifest.xml`. Separate filters avoid accidental combinations when hosts or path rules later diverge: ```xml ``` The generated `assetlinks.json` proves the package/domain relationship. On Android versions before dynamic App Links, the native manifest remains responsible for path restrictions, so keep it aligned with `ASSOCIATED_PATHS`. Handle both a cold start and a new intent, then validate the URL again before loading or exchanging anything: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleAuthLink(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) handleAuthLink(intent) } private fun handleAuthLink(intent: Intent) { val uri = intent.data ?: return val allowedPaths = setOf( "/auth/callback", "/auth/confirm", ) if (uri.scheme != "https" || uri.host != "app.example.com" || uri.path !in allowedPaths ) return authWebView.loadUrl(uri.toString()) } ``` A fully native Kotlin client should pass the validated URI to its native auth coordinator instead of a web view. After installing the signed build, wait for verification and run: ```bash adb shell pm set-app-links --package com.example.myapp 0 all adb shell pm verify-app-links --re-verify com.example.myapp adb shell pm get-app-links com.example.myapp adb shell am start -W -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d 'https://app.example.com/auth/callback?code=test' ``` The host should report `verified`. A `legacy_failure`, browser chooser, or browser-only result usually means the deployed file redirected, the package did not match, the wrong signing certificate was used, or the manifest host/path differed. ## Deployment and security checklist - Deploy the well-known routes before shipping a native build that declares the domain. - Use exact production hosts and callback paths; do not accept arbitrary `next`, host, scheme, or path values. - Preserve the full callback query string, but never log it. Auth codes and token hashes are short-lived credentials. - Keep the callback in the client that initiated PKCE. Do not attempt a second exchange after the code has been consumed. - Test installed and uninstalled behaviour. Without the app, the same HTTPS URL must complete safely in the browser. - Test cold start, warm start, expired links, cancelled sign-in, staging and production signing, and password reset separately. - Do not enable Android with a guessed package or fingerprint. Do not enable iOS with another application's App ID. - Re-test after changing domains, native application identifiers, signing certificates, callback paths, email templates, or Supabase redirect settings. --- ## Organisation management Supacharger installs an optional, private organisation backend in every aligned application. Set `ORGANISATIONS.ENABLED` to expose product routes; disabling the feature leaves the schema installed but dormant. ## Data and roles The reusable contract contains organisations, `owner`/`admin`/`member` memberships, hashed invitations, reviewable access requests, versioned per-session context, and a private media bucket. Owners and admins manage the organisation. The last owner cannot be removed or demoted. Organisation creation also creates or synchronises the organisation billing subject. Membership removal and role changes invalidate session contexts that are no longer valid. ## Authenticated RPC Call `api.organisations(input_payload)` with a verified Supabase access token. The RPC derives the current user, Auth email, and `session_id`; never send those values in the payload. Supported actions are: - discovery and context: `list`, `handle.available`, `create`, `switch`, and `read`; - organisation settings: `update`; - team management: `members.list`, `members.add`, `members.updateRole`, and `members.remove`; - invitations: `invites.list`, `invites.create`, `invites.accept`, and `invites.revoke`; and - access review: `access.request`, `access.list`, `access.approve`, and `access.reject`. The Bruno request in `docs/bruno/supacharger-rpc/organisations.bru` documents the payload fields. Context-changing results include `requiresSessionRefresh`; refresh the browser session before relying on active-organisation claims. ## Handles, invitations, and access requests Handles are normalised to lowercase route-safe values. `handle.available` returns false for invalid values, existing organisations, and reserved application routes such as `account`, `api`, `auth`, `pricing`, and `settings`. Invitation creation returns the raw token once and stores only its SHA-256 hash. Tokens expire, are revocable and single use, and acceptance requires the signed-in Auth email to match the normalised recipient email. `access.request` is available only when the organisation's access policy is `request`. It creates a pending request; it never grants membership. An owner or admin must approve or reject it. ## Organisation media The private `organisation-logos` bucket accepts JPEG, PNG, WebP, and GIF files up to 5 MB. Store objects under the organisation UUID. Members can read their organisation's objects, while owners and admins control writes. Persist object paths, not signed URLs. ## Specdrive compatibility Specdrive remains a semantic consumer because its product schema and licensed presentation predate the Core layout. Its forward migration preserves product-only organisation actions and maps canonical `member` to the existing `contributor` value. Core does not adopt that product-specific role name, usage reporting, agent licensing, or licensed UI. ## Managed routes and interface When `ORGANISATIONS.ENABLED` is true, the account navigation exposes `/account/organisation`. The managed chooser lists memberships and active context, creates organisations after a handle-availability check, switches context, accepts one-time invitation links, and submits reviewed access requests. Context selection and invitation acceptance refresh the Supabase session before navigation so the next request receives current organisation claims. Root-handle mode exposes: - `/{handle}/settings` for name, handle, bio, colour, access policy, logo, and header image; - `/{handle}/settings/team` for roster, roles, invitations, and access review; and - `/{handle}/settings/billing` when organisation billing is enabled. The interface includes keyboard-visible controls, mobile layouts, disabled/busy feedback, empty states, safe errors, and semantic `sc-organisation-*` and shared `sc-control-*` class hooks. If organisations are disabled, the routes return a controlled not-found response and the navigation item is absent. ```text /account/organisation └─ switch/create/accept/request → refresh session → /{handle}/settings ├─ /team └─ /billing → /billing/portal ``` Use the canonical option shape while keeping values product-owned: ```ts ORGANISATIONS: { ENABLED: false, AUTHENTICATION_HANDLE: 'disabled', CHOOSER_PATH: '/account/organisation', ROUTE_MODE: 'root-handle', PROFILE_MEDIA: true, }, BILLING: { ACCOUNT_SUBJECTS: { PERSONAL: true, ORGANISATION: false, }, }, ``` ## Product profile extensions The managed profile form owns `FormProvider`, canonical validation, dirty state, and save feedback. Add product fields in `src/supacharger.adapters/organisations/profile-fields.tsx` with `useFormContext()`. Define the matching JSON-serialisable schema, initial-value loader, and mutation in `profile-extension.ts`. Core owns the `/account/organisation` and `/{handle}/settings` public routes. Do not leave product `page.tsx` files for those same URLs in another route group: Next.js treats route groups as URL-transparent and rejects the duplicate pages during a production build. Use the developer-owned organisation adapters instead: - `pages.tsx` can preserve product chooser, team, or billing behaviour and register product-only settings sections; - `navigation.ts` registers links for those extra sections using unique kebab-case IDs that do not replace `profile`, `team`, or `billing`; - `chrome.tsx` wraps managed settings in the product application chrome and may enforce the product's stronger access boundary; and - `src/styles/supacharger-organisations.css` styles the managed semantic classes without editing CLI-managed markup. The CLI installs missing starter adapters once and preserves established product implementations on later updates. The server parses both canonical and extension values with Zod before calling the database. It invokes extension persistence only after the canonical owner/admin update succeeds. Product code cannot weaken role checks, handle validation, or organisation Storage paths. Style the complete shared surface through project tokens and its stable semantic hooks in `src/styles/supacharger-organisations.css`. The CLI installs this developer-owned starter when absent and preserves it thereafter. Keep it unlayered so it can override Tailwind-layer defaults; do not edit managed route/components or copy Specdrive's licensed Untitled UI implementation into Core. ## Upgrade and troubleshooting Run `supacharger coreupdate --plan` before updating. The plan lists the managed routes/tests, missing developer adapter starters, disabled-safe config additions, English catalogue additions, and the forward organisation migration. An application with a reviewed adapted migration under a different immutable name must declare it in `.supacharger/migration-aliases.json`. After updating, run `supacharger doctor`. A failure normally names an actionable state: two route-group pages resolve to the same public URL; an obsolete `/organisation` or `/auth/*` page still exists; account/organisation config is incomplete; the managed routes/tests or adapter starters are missing; or a migration alias points to a file that does not exist. A disabled organisation feature returning not found is expected and is not a failed installation. ## Specdrive route transition Specdrive uses the Core-owned route tree and shell. Its authorised chooser, team and billing behaviour, application chrome, usage page, and agent-licence page remain developer-owned adapter implementations. Its licensed presentation stays in Specdrive's developer CSS and components and is not redistributed through the open-source Core. --- ## Roles and custom claims Supacharger separates three concepts: - `role` is Supabase’s database role, normally `authenticated`; do not replace it with an application role. - `user_role` is the shared, global application role emitted by the custom access-token hook. - resource roles, such as an organisation owner or member, remain authoritative in membership tables and RLS. ## Enable the canonical hook The committed configuration is: ```toml [auth.hook.custom_access_token] enabled = true uri = "pg-functions://postgres/app/custom_access_token_hook" ``` The migration grants only `supabase_auth_admin` permission to read `app.user_roles` and execute the hook. `anon`, `authenticated`, and `public` cannot read the role source or call the hook. For a hosted project, deploy the migration first, then enable the hook separately in that project's Supabase dashboard: 1. Sign in as an organisation or project **Owner** or **Administrator**. A Developer or Read-Only account cannot update Auth configuration; the dashboard or Management API may return `403`. 2. Open the intended hosted project and go to **Authentication → Hooks**. 3. Find **Custom Access Token**, choose **Postgres Function** (also labelled **SQL** in some dashboard versions), and select `app.custom_access_token_hook`. 4. Enable and save the hook. Do not select a similarly named function in `public` and do not create another function when the canonical migration is already deployed. 5. Sign out of the application completely and sign in again, or explicitly refresh the session, so Supabase issues a new access token. 6. Call `supabase.auth.getClaims()` and confirm that `claims.user_role` is present. An existing token can remain stale even after the hook is enabled. A local `config.toml` controls the local stack; it does not update the hosted dashboard selection automatically. If the migration ledger is current but a fresh hosted token still lacks `user_role`, re-check the selected project, hook type, schema, function, and the permissions of the dashboard account used to save the setting. ## Extend claims without forking Core Core owns the hook, Supabase-required claims, `user_role`, and the active-organisation claims. Applications add compact product claims through this developer extension function: ```sql app.custom_access_token_claims_extension(event jsonb, canonical_claims jsonb) ``` The default function returns `{}`. To extend it, create a **new forward migration** and replace only that function body. A developer-owned example is installed at `supabase/templates/custom-access-token-claims-extension.sql`. ```sql create or replace function app.custom_access_token_claims_extension( event jsonb, canonical_claims jsonb ) returns jsonb language sql stable security invoker set search_path = '' as $$ select jsonb_build_object('product_plan', coalesce(plan.lookup_key, 'free')) from app.product_user_plans plan where plan.user_id = (event ->> 'user_id')::uuid; $$; ``` Return only an object of additional product-owned claims. The Core hook rejects attempts to replace Supabase claims, the global role, or active-organisation context. This keeps the contract DRY: Core maintains the secure merge and reserved names while the application owns its product query. The function runs as `supabase_auth_admin` with `security invoker`. Grant that role only the table access and RLS policy the extension genuinely needs. Preserve the narrow execute grant and revoke browser roles. Keep claims small, avoid personal data, never use user-editable `user_metadata` for authorisation, and remember that values remain stale until the token refreshes. ## Assign a global application role Roles are administrative data, so change them through a reviewed migration or another separately designed trusted boundary. For example: ```sql update app.user_roles set role = 'admin'::app.application_role, updated_at = timezone('utc', now()) where user_id = '00000000-0000-0000-0000-000000000000'; ``` Do not add `admin` to `user_metadata`; signed-in users can edit that metadata. Do not expose a generic browser RPC that lets a caller choose their own role. The new claim appears only in newly issued access tokens. After a role change, sign in again or refresh the session: ```ts const { data, error } = await supabase.auth.refreshSession(); if (error) throw error; const { data: claimData } = await supabase.auth.getClaims(); console.log(claimData?.claims.user_role); ``` ## Use the claim in RLS Claims can provide a fast coarse-grained check: ```sql create policy admin_can_read_audit_log on app.audit_log for select to authenticated using ((select auth.jwt() ->> 'user_role') = 'admin'); ``` JWT claims are cached until the access token is refreshed. For access that must be revoked immediately, query authoritative database state in RLS or a server-side domain function instead of relying only on the claim. ## Organisation roles An organisation role is not a single global user role. One person can own one organisation and be a member of another. Core's canonical roles are `owner`, `admin`, and `member`; keep that source of truth in the membership table: ```sql create type app.organisation_role as enum ('owner', 'admin', 'member'); create table app.organisation_members ( organisation_id uuid not null references app.organisations(id) on delete cascade, user_id uuid not null references auth.users(id) on delete cascade, role app.organisation_role not null default 'member', primary key (organisation_id, user_id) ); ``` Authoritative RLS reads the membership row for the resource being accessed: ```sql create policy organisation_admins_manage_settings on app.organisation_settings for all to authenticated using ( exists ( select 1 from app.organisation_members member where member.organisation_id = organisation_settings.organisation_id and member.user_id = (select auth.uid()) and member.role in ('owner', 'admin') ) ); ``` The canonical session context stores a selected organisation against the current Auth `session_id`. The hook emits only that compact context: ```json { "active_organisation_id": "3ae1…", "active_organisation_handle": "acme", "active_organisation_role": "admin", "organisation_context_version": 4 } ``` When the user switches organisation, update the server-owned session context, refresh the JWT, and verify the returned claims. Keep the membership lookup in RLS even when the active context claim is used for navigation or an early rejection. Do not put every organisation membership into the JWT: the claim becomes stale and SSR cookies have practical size limits. Consumer-specific role names must be mapped at a product boundary or migrated to the canonical enum. They must not silently change the reusable Core claim contract. ## TypeScript claim helper Narrow custom values before using them: ```ts type ApplicationRole = 'user' | 'admin'; export async function getApplicationRole(supabase: SupabaseClient) { const { data, error } = await supabase.auth.getClaims(); if (error) throw error; const role = data?.claims.user_role; const normalisedRole: ApplicationRole = role === 'admin' ? 'admin' : 'user'; return normalisedRole; } ``` Use this for display or coarse server routing. Keep final data authorisation in RLS and trusted server code. --- ## Auth email and SMTP Supabase's default mail service is intended for exploration. It sends only to authorized project-team addresses, has a low quota, and provides no delivery SLA. A public application using email signup, OTP, magic links, invitations, email changes, or password recovery needs custom SMTP. Follow [Configure production email](../guides/hosted-setup/06-production-email.md) during first deployment. This page records the longer-term operating policy. ## Provider setup Supabase accepts standard SMTP credentials. Brevo, Postmark, Resend, SendGrid, and Amazon SES are common choices; use the provider that matches the project's delivery, regional, and support requirements. For Brevo, follow its current [Supabase SMTP configuration guide](https://help.brevo.com/hc/en-us/articles/7924908994450-Send-transactional-emails-using-Brevo-SMTP), then enter the generated credentials under **Supabase → Authentication → SMTP Settings**. Use: - a dedicated transactional subdomain such as `auth.example.com`; - a recognizable sender such as `no-reply@auth.example.com`; - production-specific SMTP credentials; - SPF and DKIM records supplied by the provider; and - a monitored DMARC policy. Keep Auth mail separate from marketing broadcasts where practical. Do not store Supabase-hosted SMTP credentials in browser environment variables or commit them to the repository. ## Rate limits After custom SMTP is enabled, Supabase initially applies a conservative hourly email limit. Set a sustainable project-wide limit under **Authentication → Rate Limits**. Do not confuse: - **Email OTP expiration**, which controls how long OTPs, magic links, confirmations, recovery links, email changes, and invitations remain valid; - **per-user resend cooldown**, normally 60 seconds; - **project-wide email quota**, shared by several email-producing operations; and - **OTP endpoint quota**, which is configured separately. The application resend timer must never be shorter than the hosted cooldown. ## Abuse and deliverability Public email endpoints can be abused to exhaust quotas or damage sender reputation. Apply CAPTCHA or Turnstile where the threat model requires it, monitor the sending provider's suppression and complaint lists, and plan capacity increases before a launch spike. Disable click tracking in Auth emails when it rewrites one-time links. Some mail security scanners also prefetch links; use OTP entry where link prefetching is common among the intended users. Inspect real delivered headers periodically and confirm SPF, DKIM, and DMARC alignment. Test with recipients outside the Supabase organization—a successful message to a project owner can otherwise hide that the default mailer is still active. ## Template availability New Free plan projects using Supabase's default SMTP cannot customize Auth email templates. Configure custom SMTP before installing the project's [transactional email templates](../Marketing-and-Analytics/email-templates/index.md). --- ## Styling Supacharger uses Tailwind CSS as its styling foundation. The root layout imports one CSS entrypoint: ```ts import '@/supacharger/styles/globals.css'; ``` That entrypoint loads Tailwind, the shared Supacharger styles, the developer-owned authentication presentation, and the application's general developer-owned stylesheet in that order. Do not import them again from the layout. ## Style ownership | File | Owner | Purpose | | --- | --- | --- | | `src/supacharger/styles/globals.css` | Supacharger CLI | Tailwind entrypoint and import order only | | `src/supacharger/styles/supacharger.css` | Supacharger CLI | Reusable Supacharger element and component rules | | `src/supacharger/styles/project.example.css` | Supacharger CLI | Unimported reference for the developer stylesheet | | `src/styles/supacharger-auth.css` | Application developer | Presentation for managed authentication `sc-auth-*` hooks | | `src/styles/project.css` | Application developer | Project theme tokens, global defaults, overrides, and product-specific classes | The CLI may replace files under `src/supacharger/styles/` during a core update. It installs `src/styles/supacharger-auth.css` when absent, then preserves it alongside `src/styles/project.css` and `src/supacharger.config.ts`. Keep the `Project: ...` header in `project.css` updated with the application name. This makes the ownership of copied or compared styles explicit. ## Tailwind conventions Use Tailwind utilities in markup for most styling. Add CSS only when a reusable semantic rule, an element default, a theme token, or a project-wide override is genuinely clearer than repeated utilities. Place custom CSS in Tailwind's layers: - `@layer base` for project theme variables and element defaults; - `@layer components` for reusable semantic component classes; and - `@layer utilities` for small, single-purpose project utilities. Tailwind's Preflight already supplies the normal reset through `@import 'tailwindcss'`. Do not reproduce Preflight rules in project CSS, and do not add another Tailwind import to `project.css`. The merge-managed `tailwind.config.ts` retains its TypeScript filename, application font choices, and current CommonJS export. The exact-managed `postcss.config.mjs` uses an explicit ESM export so Next.js and Turbopack can evaluate the Tailwind PostCSS plugin reliably. Keep the `@config` reference unchanged and do not add a package-wide `"type": "module"` solely for either file. ## Shared Supacharger rules Rules that every Supacharger application should receive belong in the CLI-managed core stylesheet. Supacharger centrally gives enabled native and ARIA interactive controls a pointer cursor on hover: ```css title="src/supacharger/styles/supacharger.css" @layer base { html { scroll-behavior: smooth; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } :where( a[href], button:not(:disabled), input[type='button']:not(:disabled), input[type='submit']:not(:disabled), input[type='reset']:not(:disabled), input[type='checkbox']:not(:disabled), input[type='radio']:not(:disabled), label[for], select:not(:disabled), summary, [role='button']:not([aria-disabled='true']), [role='link']:not([aria-disabled='true']), [role='menuitem']:not([aria-disabled='true']), [role='menuitemcheckbox']:not([aria-disabled='true']), [role='menuitemradio']:not([aria-disabled='true']), [role='option']:not([aria-disabled='true']), [role='tab']:not([aria-disabled='true']) ) { cursor: pointer !important; } } ``` The root rule is the native fallback for same-page fragment links. The installed root layout also mounts `SmoothAnchorNavigation` from `src/supacharger/components/layout/smooth-anchor-navigation.tsx`, because Next.js `` can otherwise perform an immediate fragment jump before CSS animates it. Together they make `` and `` work automatically. Modified clicks, downloads, non-self targets, and missing fragments retain normal browser behaviour; add `data-smooth-scroll="false"` to opt out for one link. Visitors who request reduced motion receive immediate navigation. The `!important` cursor declaration intentionally keeps that interaction affordance authoritative when a component library supplies `cursor-default`. Disabled controls are excluded so they do not misleadingly advertise an available action. Ordinary components therefore should not repeat `cursor-pointer` or page-level smooth-scroll utilities. Change this file in the canonical Supacharger core first, then distribute the same file through the CLI. Do not add product branding or application-specific selectors to it. ## Project styles Put the application's colors, typography defaults, visual effects, and overrides in `src/styles/project.css`: ```css title="src/styles/project.css" /** * Project: Example Application * * Developer-owned. The Supacharger CLI must preserve this file. */ @layer base { :root { --primary: 174 49% 50%; --primary-foreground: 222 47% 11%; } } ``` Tailwind color mappings in `tailwind.config.ts` consume these space-separated HSL channels. For example, `#42bfb1` becomes `174 49% 50%`. `--primary` is the application's main brand action colour and `--primary-foreground` is the content colour placed on top of it. Prefer semantic utilities such as `bg-primary`, `text-primary-foreground`, `text-foreground`, `text-muted-foreground`, `bg-accent`, and `ring-ring`. They automatically follow the named project's light and dark token values; a shared component should not copy a product hex value. Application-specific classes may override a shared Supacharger class because `project.css` is imported after `supacharger.css`. Keep overrides intentional and document why the consumer differs from the core. ## Theme selector The shared `ModeToggle` opens a three-position selector ordered System, Light, and Dark. Use `appearance='marketing'` beside the application-owned locale switcher when both controls appear in marketing navigation or a footer. The marketing appearance gives both triggers the same control height, padding, text size, and small radius. The application-owned root layout must mount a compatible theme provider with system mode enabled so every choice can resolve correctly. ## Responsive SVG components Keep UI SVGs under the developer-owned `src/` path belonging to their feature or surface, with demo-only artwork under `src/components/sc_demo/`. Import them as React components through SVGR and use `public/` only when an asset genuinely needs a URL. Every responsive SVG must have a valid `viewBox` so it preserves its aspect ratio. When CSS or Tailwind controls the rendered size, remove `width` and `height` from the root ``: ```svg ``` Size the component at its call site with classes such as `size-*`, `w-*`, `h-*`, or `size-[1em]`, and usually add `shrink-0` beside text. Do not force mismatched dimensions or fix clipping with arbitrary component props. Use `fill='currentColor'` or `stroke='currentColor'` only when the artwork should inherit text colour. Confirm that SVGR/SVGO preserved the rendered `` element's `viewBox`, test classes such as `h-8 w-auto` at the intended viewport sizes, and verify that the complete artwork stays within its bounds. Add a regression test that checks responsive SVG assets retain their `viewBox` and omit root-level intrinsic dimensions, then visually verify every affected page in the browser. ## Inline loader branding The primary application-owned inline loading animation lives at `src/assets/svgr/ui/inline-loader.svg`, with a matching black variant at `src/assets/svgr/ui/inline-loader-dark.svg`. Replace either file with a new SVG of the same name to customise it; Supacharger CLI updates preserve both. The shared loading component uses the primary asset. This is an inline-loader convention for pending buttons, form actions, uploads, and compact content states. It does not replace an application's route-level or full-page loading design. Render the asset as an SVGR component rather than with an image tag or public URL: ```tsx import InlineLoader from '@/assets/svgr/ui/inline-loader.svg'; ``` The primary loader defaults to white and the dark clone defaults to black. Both use their root `fill`, so an SVGR caller can pass a different colour. A replacement may instead hard-code `fill` or `stroke`; in that case the same prop is harmless and the custom artwork keeps its own colours. Keep all UI SVGs under an appropriate developer-owned `src/` path and import them through SVGR. Reserve `public` SVGs for favicon metadata, manifests, external URL contracts, and other cases that genuinely require a URL. Colocate feature-owned SVGs with the developer-owned feature when that makes its lifecycle clearer. For example, demo-only logos belong under `src/components/sc_demo/assets/` and are imported through SVGR, so deleting the replaceable demo also deletes its artwork. [SVG Backgrounds animated SVG preloaders](https://www.svgbackgrounds.com/elements/animated-svg-preloaders/) is one source of replacement animations. Check and satisfy the selected asset's current licence and attribution requirements before publishing it. ## Error and not-found pages `src/app/error/page.tsx`, `src/app/not-found.tsx`, and their presentation under `src/components/error-page/` are application-owned, CLI-preserved surfaces. This lets a product own its support destination, wording, signed-in chrome, and brand treatment without editing a protected Supacharger route. The starter pattern verifies the current user on the server. A verified user sees the error state inside the normal authenticated header and footer; every other visitor sees only a vertically centred, full-viewport error state. The actions use the semantic project tokens described above. Keep the explicit `/error` route in `PATH_AUTH_GUARD.UNAUTHED_USER.ALLOWED` when `AUTH_ONLY_APP` is enabled, otherwise an authentication failure can be redirected away from the page intended to explain it. The canonical Proxy also lets `/error` bypass profile-onboarding redirects so an authenticated but incomplete account can still see the failure explanation. Add translated wording under the developer-owned `ErrorPage` namespace. The error-copy resolver reads any available values directly and supplies protected English defaults for an absent namespace or missing field. This compatibility fallback prevents an older preserved catalogue—or a stale development module—from raising `MISSING_MESSAGE` inside the error boundary itself. ## Fonts Define application fonts with `next/font` in `src/app/layout.tsx` and expose them through a CSS variable: ```ts import { Space_Grotesk } from 'next/font/google'; const spaceGrotesk = Space_Grotesk({ variable: '--font-space-grotesk', subsets: ['latin'], }); ``` Apply the generated variable to the body class and map it in Tailwind configuration when it should back a font utility. Next.js downloads Google font assets at build time, so the browser does not request them from Google at runtime. ## Favicon customisation Use [RealFaviconGenerator](https://realfavicongenerator.net/) to create a complete favicon pack from the application's icon. A 300 × 300 px PNG works well as the source image. Configure the browser, mobile, and manifest options on the site, then download and extract the generated pack to a temporary folder. Give your coding agent the location of the downloaded archive or extracted folder with a prompt like this: ```text Install the favicon pack from in this project. Inspect the existing favicon and metadata setup, replace the current favicon files and references with the new pack, and keep the implementation consistent with the framework and project structure. Remove obsolete favicon assets that have been replaced. Verify that the relevant metadata, manifest, and icon paths resolve correctly, then delete the temporary downloaded archive or extracted folder. Preserve unrelated files and summarise what changed. ``` Replace the placeholder with the pack's actual location. The pack does not need to be copied into the project first; it can remain in Downloads or another temporary location while the agent installs it. --- ## UI Helpers and components :::warning Stub note This page is a thin implementation note and does not fully reflect the current Supacharger Core UI helper surface. Use [Styling](./styling.md) for the maintained styling contract until this page is expanded. ::: ## Loader Loading ring update InlineLoader.svg for the component to be automatically updated. ## Messages Semantic error message colours defiend in globals.scss src/styles/globals.scss .sc-message .sc-message-error --- ## Database features Within your Supabase database, we have provided two PostgreSQL functions that can help you defining RLS policies and alter data on triggers. ## Migrations Migrations are managed by Supabase using Supabase CLI. See these [docs](https://supabase.com/docs/guides/cli/local-development) for more details. ## `private` schema The `private` schema contains tables and functions that are needed in the business logic but should not be exposed to the [public API](https://supabase.com/docs/guides/api). See [private_schema.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000001_private_schema.sql) migration for more details. ## Profiles table The `profiles` table is used to track who are the users. This table is populated automatically when a user signs up, using the `auth.users`'s `id` column as primary key. See [profiles.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000002_profiles.sql) migration for more details. ### Suspended users The `profiles` table has a `profile_is_suspended` column that the user can set to true for itself. Is a user has the `profile_is_suspended` column set to true, they cannot update their profile. The `private.is_profile_suspended(profile_id uuid)` function can be used to check if the user with the given `profile_id` is suspended. The `public.is_current_profile_suspended()` function can be used to check if the current user (the user sending the request to the Supabase API) is suspended. See [profiles.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000002_profiles.sql) migration for more details. ## Admins The `admins` table is used to track who are the admins. The `private.is_profile_admin(profile_id uuid)` function can be used to check if the user with the given `profile_id` is an admin. The `public.is_current_profile_admin()` function can be used to check if the current user (the user sending the request to the Supabase API) is an admin. For both `profiles` and `admins` tables RLS policies are applied so that admins can do all the SQL operations. See [admins.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000003_admins.sql) migration for more details. ## Log activity These functions serve the purpose of automatically updating key columns, including `created_by`, `created_at`, `updated_by`, and `updated_at` within a given table: - `log_activity_on_insert()`: This function automatically populates the `created_by` column with the result of [`auth.uid()`](https://supabase.com/docs/guides/auth/row-level-security#authuid) and the `created_at` column with the current timestamp upon an insertion. - `log_activity_on_update()`: Similarly, this function serves to populate the `updated_by` column with [`auth.uid()`](https://supabase.com/docs/guides/auth/row-level-security#authuid) and the `updated_at` column with the current timestamp when an update operation occurs. These functions come pre-enabled for the `public.profiles` table by default, using triggers. Below is the SQL code that shows how to enable these functions for other tables. Note that these functions are enabled by default on `public.profiles` table. ```sql CREATE TRIGGER log_activity_profiles_insert BEFORE INSERT ON public.profiles FOR EACH ROW EXECUTE FUNCTION log_activity_on_insert(); CREATE TRIGGER log_activity_profiles_update BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE FUNCTION log_activity_on_update(); ``` You have the flexibility to enable these functions on any other table as well. However, it's crucial to ensure that the table in question possesses the following columns: `created_by` (of type `uuid`), `created_at` (of type `timestamptz`), `updated_by` (of type `uuid`) and `updated_at` (of type `timestamptz`) columns. . If needed, you may consider referencing `profiles.user_id` within the `*_by` columns for integrity. Please note that any operation executed on the db using the service_role will be logged as `null` in the `created_by` and `updated_by` columns, as well as the insert in the profiles table done by the automatic trigger on the `auth.users` table. See [log_activity_helpers.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000004_log_activity_helpers.sql) migration for more details. ## Automatically delete files from storage At some point in your implementation, you'll need to use [Supabase Storage](https://supabase.com/docs/guides/storage). You usually set `*_url` columns in your tables to keep track of the current location of the file, but deleting the file from storage is a manual operation. Two functions are provided to help you with that: - `private.delete_storage_object(object_path text)`: This function deletes the given object at the given path. For example, if a file is located at `/storage/v1/object/public/avatars/picture.jpg` (Supabase Storage API details [here](https://github.com/supabase/storage-api)), call the function with `private.delete_storage_object('avatars/picture.jpg')`. - `private.delete_storage_object_from_url(object_url text)`: This function deletes the file at the given URL. The object URL can be a full `https://...` URL or a relative path, such as `/storage/v1/object/...` or `/{bucket_name}/{object_full_path}`. See [delete_storage_object_functions.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000005_delete_storage_object_function.sql) migration for more details. These functions are enabled by default on `public.profiles` table on the `avatar_url` column with a trigger. See [delete_old_files_trigger.sql](https://github.com/glowplugstudio/supacharger/tree/main/supabase/migrations/00000000000006_delete_old_files_triggers.sql) migration for more details. --- ## Formatting :::warning Legacy note This page is a thin note and does not currently describe the maintained Supacharger Core formatting contract. Check the application's actual `package.json` scripts before running formatter commands. ::: We use [dprint](https://dprint.dev/) to format our code. To format your code, run: ```bash pnpm format ``` https://prettier.io/ --- ## Security Tuneup :::warning Idea note This page is an unfinished checklist and does not fully reflect the current Supacharger Core security model. Use [Authentication and route protection](../Application%20Development/authentication.md), [Roles and custom claims](../Application%20Development/roles-and-custom-claims.md), [Input validation](../Application%20Development/input-validation.md), and [Deploy and production readiness](../Application%20Development/deploy.md) as the maintained references. ::: RLS policies How to set up cloudflare Rate limiting test with postman Setting timeouts and enforcing only one session https://supabase.com/dashboard/project/XXX/auth/sessions Email password settings https://supabase.com/dashboard/project/XXX/auth/providers?provider=Email --- ## Tests ## Database tests Supacharger comes with some database unit tests, which can be found in the [tests/database](https://github.com/glowplugstudio/supacharger/tree/main/supabase/tests/database) directory. To run them, use the following command: ```bash pnpm test ``` ## Bruno RPC collection The Supacharger repository includes a CLI-managed Bruno collection and checker at `docs/bruno/supacharger-rpc/` and `scripts/check-bruno-rpc-parity.mjs`. Managed applications receive byte-identical copies. The collection uses local environment placeholders for the Supabase URL, publishable key, and user access token. Run the parity check whenever an RPC migration changes: ```bash npm run check:bruno-rpcs ``` In Core, the check fails when a current `api` RPC lacks a request or a request refers to a removed RPC. In a consumer, it requires every canonical Supacharger RPC while allowing additional product-owned RPCs. Product functions stay in the application's own collection. Privileged `api_edge` functions remain excluded so the shared collection never encourages storing service-role credentials. --- ## Typing :::warning Thin note This page is a short operational note, not a full Supacharger Core typing guide. Confirm the current code-generation script in the application repository before running it. ::: Supabase offers automatic code generation based on the tables your database. To generate types, run: ```bash pnpm codegen:supabase ``` See Supabase CLI gen [docs](https://supabase.com/docs/reference/cli/supabase-gen-types). --- ## Stripe setup and operations Supacharger uses Stripe-hosted Checkout and Customer Portal with an account-based billing model. Stripe is authoritative; Supabase stores a secured, recoverable projection for application reads and access decisions. ## Supported contract - Stripe Node SDK `22.5.0`, API and event destinations `2026-07-29.dahlia`. - New subscriptions use flexible billing mode. - A billing account represents a user, organisation, team, tenant, or custom billable party. - Users receive owner, billing-administrator, or member access through billing-account membership. - One Stripe Customer is mapped per billing account, Stripe account, and live/test mode. - A Customer can own multiple Subscriptions; each Subscription can contain multiple Items. - Stripe and application entitlements are projected separately from Prices. - One-time fulfilment and Stripe Connect are extension contracts, not implicit starter behaviour. Do not key billing records directly to a login user, assume the first Subscription Item is the plan, or use a Price ID as application authorisation. ## Environment ```dotenv STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_EVENT_PROCESSOR_SECRET= STRIPE_RECONCILIATION_SECRET= ``` Secrets are server-only and distinct per environment and purpose. Hosted Checkout does not require Stripe.js, so the canonical application has no browser publishable-key requirement. `SC_CONFIG.BILLING` controls Automatic Tax, billing-address collection, and promotion codes. Automatic Tax still requires correct Stripe registrations and Product tax codes. Payment methods are configured in Stripe Dashboard. ## Dynamic Payment Methods and Checkout Supacharger deliberately omits `payment_method_types`. Stripe therefore presents eligible cards, wallets, bank debits, and local methods according to Dashboard settings, currency, customer location, and payment-flow compatibility. `createCheckoutAction` accepts 1–20 line items. The server retrieves every Price from Stripe and requires: - an active recurring Price; - a positive integer quantity; and - one currency across all Items. Checkout creates or reuses the Customer belonging to the selected billing account, verifies that the current user can manage it, and creates a flexible-mode Subscription. The success redirect is informational; only Stripe events and reconciliation update the billing projection. ## Event destination Create a Sandbox destination for `https:///api/webhooks`, pin it to `2026-07-29.dahlia`, and select only: - `product.created`, `product.updated`, `product.deleted`; - `price.created`, `price.updated`, `price.deleted`; - `checkout.session.completed`, `checkout.session.async_payment_succeeded`, `checkout.session.async_payment_failed`; - `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`; - `customer.subscription.paused`, `customer.subscription.resumed`; - `entitlements.active_entitlement_summary.updated`. For local development: ```bash stripe listen --forward-to localhost:3000/api/webhooks ``` Use the signing secret printed for that process. Never select all events. ## Processing and reconciliation The webhook verifies the raw request, atomically records the Event and snapshot payload, acknowledges duplicates, and returns after durable acceptance. A protected worker claims records with leases and retry state. Subscription handlers retrieve current Stripe state and atomically replace the complete Item set because Stripe delivery can be duplicated or out of order. Schedule authenticated POST requests to: ```http POST /api/stripe/process-events Authorization: Bearer POST /api/stripe/reconcile Authorization: Bearer ``` The reconciliation job enumerates mapped Customers, refreshes all Subscriptions and Items, then replaces Stripe-origin entitlement grants. It reports failures per Customer and leaves manual/promotional grants untouched. Monitor non-empty failures and failed event rows. ## Supabase model and security Internal `app` tables include: - `billing_accounts` and `billing_account_members`; - `stripe_customer_mappings`, whose local `stripe_customer_mapping_id` is distinct from Stripe's external `stripe_customer_id`; - `products`, `prices`, `subscriptions`, and `subscription_items`; - `billing_features`, `product_features`, and `entitlement_grants`; - `stripe_events` and `stripe_event_payloads`. Browser reads go through deliberate `api` functions. Stripe ingestion uses service-only `api_edge` functions. Tables have explicit grants and RLS; browser roles cannot mutate projections, Customer mappings, entitlements, payment summaries, or event payloads. After migration: ```bash npx supabase migration list --linked npm run generate-types npx supabase db advisors --linked ``` Test grants and RLS separately as anonymous, authenticated, and service roles. ## Customer Portal and advanced changes Portal sessions are created only after verifying billing-account management authority. Configure supported payment-method changes, invoice history, upgrades, downgrades, and cancellation in Stripe. Use separately authorised application services for seat allocation, organisation membership, quotes, subscription schedules, complex prorations, or coordinated changes across multiple Subscriptions. Portal configuration must not be treated as the application's access-control policy. ## Release verification Before production, test: - one user with multiple billing accounts and an organisation with multiple members; - multiple Subscriptions and multiple Items, including quantities and mixed intervals; - Dynamic Payment Methods, including delayed methods enabled for the account; - duplicate, reversed, failing, leased, and replayed events; - Stripe Entitlement replacement and persistence of manual grants; - Portal authorisation and return paths; - flexible-mode upgrade/downgrade and proration behaviour; - trials, test clocks, renewal, failure, pause, cancellation, and reconciliation; - fresh and upgrade migrations, generated types, advisors, grants, RLS, lint, tests, and build. Create independent live destinations and secrets. Stripe Sync Engine is not the default; adopting it requires a separate architecture, access-control, deployment, and operational review. --- ## Billing accounts, subscriptions, and entitlements Supacharger separates identity, billing, and application access. ## Relationship model ```text billing account ├── members (owner, billing administrator, member) ├── Stripe Customer mapping (per account and live/test mode) ├── Subscriptions │ └── Subscription Items → Prices → Products └── entitlement grants → stable feature lookup keys ``` A personal account can use the authenticated user's UUID as its subject, but this is a subject reference—not proof that every billable party is a user. Organisation/team applications create a billing account for that domain object and manage user authority through membership. ## Billing acquisition boundary When `BILLING_ACCESS.REQUIRED` is enabled, full product routes use `requireAppAccess()`, which evaluates onboarding before billing. The configured acquisition page must instead use `requireOnboardedUser()`: unauthenticated users are sent to login, incomplete profiles are sent to setup, and an onboarded user without billing access may render Checkout. Never place the acquisition page beneath `requireAppAccess()`, and never treat Checkout query parameters or return state as proof of entitlement. ## Multiple subscriptions and items A Stripe Customer can hold multiple independent Subscriptions with different periods and lifecycle states. A Subscription can combine multiple fixed, quantity/seat, and metered Prices. Flexible billing mode also supports mixed intervals where Stripe account capabilities permit them. The canonical read model is `subscriptions[] → items[]`. The old `subscriptions.price_id` and `quantity` columns are temporary compatibility aliases to the first Item. New features must never read them as the complete plan. ## Entitlement-based access Prices answer “what is billed”; entitlements answer “what can this account use”. Stable feature lookup keys let pricing change without changing application authorisation. Sources include: - Stripe Entitlements attached to Stripe Products; - application-maintained subscription/Product mappings; - manual administrator grants; and - promotional grants with optional start/end dates. Stripe summary events trigger a complete paginated refresh. Runtime access reads the local projection, avoiding a live Stripe dependency on every request. Configure `SC_CONFIG.BILLING_ACCESS.FEATURE_LOOKUP_KEY` for feature access. Leave it `null` only when the product intentionally grants broad access for any `trialing` or `active` Subscription. `SC_CONFIG.BILLING_ACCESS.REQUIRED` controls a post-authentication callback detour; it is not, by itself, a universal paywall. See [Login redirects and subscription paywalls](../Application%20Development/login-redirects-and-paywalling.md) before enabling it, and enforce entitlements at every protected server boundary. ## Lifecycle states The projection recognises `trialing`, `active`, `canceled`, `incomplete`, `incomplete_expired`, `past_due`, `unpaid`, and `paused`. Applications must explicitly decide which entitlements remain active during payment failure, grace periods, pauses, scheduled cancellation, and manual support overrides. Do not infer access solely from a successful Checkout redirect, a client timer, or a database error. Webhooks and reconciliation populate the projection; the server-side access function evaluates it. ## Upgrades and scheduled changes Simple changes can be enabled in Customer Portal. For application-driven changes: - preview prorations before confirmation; - use pending updates when access must not change until payment succeeds; - use Subscription Schedules for future-dated phases; - retain Item IDs when replacing Prices; and - reconcile after complex or operator-created Dashboard changes. ## One-time payments One-time fulfilment remains outside the canonical recurring-billing contract. Add it only with an Order/Payment projection, fulfilment idempotency, async payment handling, refunds/disputes, customer communication, and reconciliation. Do not overload Subscriptions or entitlement grants to represent unimplemented order fulfilment. ## Shared and application billing tests The CLI-managed `test/billing-schema-contract.test.mjs` protects the reusable account-based billing model, Stripe customer mapping identifiers, shared RPC argument and grant conventions, generated type alignment, and entitlement-based access. It remains byte-identical across CLI-managed applications. Your product catalogue, application-only billing tables, RPCs, policies, and entitlement definitions are not Core behaviour. Put their assertions in `test/project-billing-schema-contract.test.mjs` and extend the merge-managed `test:billing-schema` package script to run both files. Core updates then refresh the shared contract test while preserving the application's product coverage. See [Stripe setup and operations](./stripe.md) for configuration, security, processing, and release checks. --- ## Subscription trials Stripe Price data may include a default trial period, and trial subscriptions are projected with exact Stripe timestamps. ## Required product decisions Before enabling a trial, decide and document: - whether a payment method is required at signup; - which plans and customers are eligible; - whether repeated trials are prevented; - when reminder messages are sent; - what happens when payment fails at conversion; - whether access ends immediately or receives a grace period; and - how cancellation during a trial behaves. Supacharger does not make these product decisions automatically. ## Access state `trialing` is a distinct subscription state on each Subscription. Applications may grant trial access, but they must use the same billing-account entitlement decision as paid access. With multiple Subscriptions, one trial ending must not revoke features still granted by another Subscription or by a manual/promotion grant. Never trust a client-side timer or Checkout redirect. ## Testing Use a Stripe Sandbox and test clocks to verify: - trial creation timestamps; - reminder timing if `customer.subscription.trial_will_end` is added to the implemented event contract; - conversion to `active`; - payment failure and recovery; - cancellation before the trial ends; and - expiry without a payment method. The current canonical event list does not handle `customer.subscription.trial_will_end`. Add that event only with a notification handler, duplicate protection, retry behaviour, tests, and matching documentation. --- ## Agent-readable documentation Supacharger Docs publishes complementary discovery formats for traditional search crawlers and AI agents. These artifacts are generated from the same Markdown sources as the rendered documentation so they remain aligned during every production build. ## Public endpoints | Endpoint | Purpose | | --- | --- | | [`/robots.txt`](https://supacharger.dev/robots.txt) | Declares crawler access and the absolute XML sitemap location. | | [`/sitemap.xml`](https://supacharger.dev/sitemap.xml) | Exhaustive standard list of public rendered routes. | | [`/llms.txt`](https://supacharger.dev/llms.txt) | Concise Markdown index with descriptions and links to raw documentation pages. | | [`/llms-full.txt`](https://supacharger.dev/llms-full.txt) | Complete public documentation combined into one Markdown document. | | `/docs/.md` | Raw Markdown alternative for an individual rendered documentation route. | For example, the hosted setup wizard is available as rendered HTML at `/docs/guides/setup-wizard` and as Markdown at `/docs/guides/setup-wizard.md`. ## Discovery links Every rendered page advertises `/llms.txt` with: ```html ``` Every rendered documentation page also advertises its matching Markdown resource: ```html ``` The conventional root filename and these link relations let an agent discover the machine-oriented representation without guessing or scraping the rendered navigation. ## Build behavior The Docusaurus sitemap plugin generates `sitemap.xml`. `docusaurus-plugin-llms` generates `llms.txt`, `llms-full.txt`, and individual Markdown pages. A small local Docusaurus plugin generates `robots.txt` after the build so its policy cannot drift away from `noIndex`. Do not edit files under `build/`; they are disposable output. Edit the source document, its front matter, or the generator configuration and rebuild. Descriptions in `llms.txt` come from each page's `description` front matter when present. Public pages should therefore have a concise, factual description that distinguishes the page from nearby documentation. ## Public and private builds Production builds are indexable by default. Development uses Docusaurus `noIndex`, and a private preview or staging deployment must set: ```bash SITE_NO_INDEX=true ``` That setting prevents sitemap generation, emits robots rules that disallow crawling, and adds Docusaurus no-index metadata. During the temporary private-review period, `HIDE_DOCS=true` builds the content behind a browser-side development gate instead of omitting it. A valid `token` query parameter unlocks the site for the current tab and is propagated to internal links. The gated build remains `noIndex` and omits search and machine-readable documentation bundles. This is a review convenience rather than secure authentication because the token is present in the static client bundle. Restore build-time omission when the review period ends. ## Maintenance checks After adding, moving, or deleting documentation, run a production build and verify: ```bash pnpm run build ``` Then confirm: - `build/robots.txt` allows production crawling and points to the canonical sitemap; - `build/sitemap.xml` contains the rendered public route; - `build/llms.txt` links to the generated Markdown route; - the individual `.md` file exists under `build/`; - `build/llms-full.txt` contains the new content; and - the rendered HTML includes both `describedby` and Markdown `alternate` links. `llms.txt` complements, rather than replaces, robots policy, XML sitemaps, canonical URLs, structured data, accurate content, and ordinary search-engine indexing. --- ## Brevo A Supacharger project can use Brevo for two separate purposes: - **transactional email**, where Supabase Auth sends confirmations, OTPs, magic links, invitations, and password recovery through Brevo SMTP; and - **marketing automation**, where the optional Supacharger Brevo plugin synchronizes contacts and subscribes users to newsletters or automation flows. Keep transactional and marketing email configuration separate. Authentication messages must continue working even when a user has not consented to marketing email or has unsubscribed from a marketing list. ## Transactional email Follow Brevo's current [Supabase SMTP configuration guide](https://help.brevo.com/hc/en-us/articles/7924908994450-Send-transactional-emails-using-Brevo-SMTP), then enter the credentials under **Supabase → Authentication → SMTP Settings**. Complete the domain authentication, sender, deliverability, and Supabase rate-limit checks in [Auth email and SMTP](../Application%20Development/smtp.md). ### Disable authentication-link tracking Do not allow Brevo to rewrite one-time Supabase Auth links. For transactional authentication email: 1. Open [**Settings → Automations → Transactional emails → Tracking**](https://app-smtp.brevo.com/advanced). 2. Disable click/link tracking for transactional messages. 3. Disable any automation or tracking option that rewrites links through a Brevo `/tr/cl/` URL. 4. Click **Save**. Anonymous tracking is not sufficient: it can still rewrite the URL even though recipient details are anonymised. A Supabase confirmation, magic-link, recovery, invitation, or email-change credential must link directly to the configured application or Supabase endpoint. After saving the setting, send fresh confirmation and recovery emails. Their links must not use a `sendibt3.com/tr/cl/` or other tracking redirect. Confirm that each direct one-time URL works exactly once and reaches the expected application handler. ## Marketing automation plugin Marketing automation is an optional paid Supacharger module. Purchase and download it from [Brevo Marketing Email Automation](https://get.supacharger.dev/shop/source/plugins/brevo-marketing-email-automation/), then follow the versioned installation instructions included with the plugin. ### Automation flows Create the intended automation in Brevo before connecting the application. Define its entry conditions, exit conditions, suppression behavior, and what happens after a contact unsubscribes. Only enroll a user when the application has the required marketing consent. Authentication or account creation by itself is not universal permission to send marketing email. ### Newsletter signup Create the newsletter list and campaign in Brevo, configure the plugin's required server environment variables, and enable the installed extension through the application's plugin registry: ```text src/supacharger/plugins/registry.ts ``` Keep Brevo API keys server-only. Do not expose them through a `NEXT_PUBLIC_` environment variable. ### CRM contact synchronization Decide which application fields are allowed to enter Brevo, how updates are reconciled, and how deletion or withdrawal of consent propagates. Avoid copying authorization roles, secrets, or unrelated profile data into marketing contact attributes. --- ## Supabase Auth template examples These minimal templates make the security-relevant variables and destinations easy to audit. Replace the product name, support address, and visual presentation, but preserve the selected flow. Hosted templates are edited under **Supabase → Authentication → Email Templates**. New Free plan projects must configure custom SMTP before Supabase permits template customization. ## Confirm signup with OTP Use this when `SIGN_UP_EMAIL_VERIFICATION` is `otp` and hosted Confirm Email is enabled. **Subject** ```text {{ .Token }} is your Supacharger confirmation code ``` **Body** ```html

Confirm your email address

Enter this code in Supacharger:

{{ .Token }}

This code expires according to the account security policy.

If you did not create this account, you can ignore this email.

``` Add a confirmation link only when `SIGN_UP_EMAIL_VERIFICATION` is explicitly `otp-and-link`; using either alternative consumes the same one-time verification. ## Confirm signup with a link Use this when `SIGN_UP_EMAIL_VERIFICATION` is `link`, hosted Confirm Email is enabled, and the application supplies the canonical origin as its allowed redirect. **Subject** ```text Confirm your Supacharger account ``` **Body** ```html

Confirm your email address

Use the button below to finish creating your Supacharger account.

Confirm email address

This link expires according to the account security policy and can be used once.

If you did not create this account, you can ignore this email.

``` Verify that the resulting URL contains exactly one `/auth/confirm` path. If the installed application passes a route rather than an origin as `emailRedirectTo`, preserve that contract instead of appending the route twice. ## Password recovery The Supabase-generated confirmation URL contains the recovery token and the allowed `redirectTo` destination supplied by the application. **Subject** ```text Reset your Supacharger password ``` **Body** ```html

Reset your password

We received a request to reset the password for this email address.

Choose a new password

This link can be used once. If you did not request it, you can ignore this email.

``` The request supplies `/auth/callback?flow=recovery`. Allow-list the stable `/auth/callback` route in hosted Auth; Core exchanges the one-use PKCE code and verifies recovery AMR before redirecting internally to `/account/reset-password/new`. ## Reauthentication code **Subject** ```text {{ .Token }} is your Supacharger security code ``` **Body** ```html

Verify this security-sensitive change

Enter this code in Supacharger:

{{ .Token }}

If you did not request this change, secure your account and contact support.

``` ## Security notification pattern For password, email, phone, sign-in-method, or verification-method changes: 1. State exactly what changed. 2. State the affected account address where the template safely provides it. 3. Tell the user what to do if the change was unauthorized. 4. Link to a stable support or account-security page, never a raw token. Test the final versions through the [production verification checklist](../../guides/hosted-setup/08-production-verification.md). --- ## SEO, document metadata, and analytics Supacharger installs `src/app/layout.tsx` as a developer-editable root-layout template. It is explicitly excluded from CLI-managed updates because applications need different fonts, body classes, theme providers, route providers, and occasional extra head content. The reusable behaviour lives in the protected `src/supacharger/root-document.tsx` module. Core updates keep that helper and the `SupachargerConfig` contract aligned across applications. The helper provides: - a validated `metadataBase` from the canonical application URL; - a default title and configurable child-page title template; - description, application name, Open Graph, and Twitter defaults; - production-aware robots metadata; - the standard favicon and manifest declarations when enabled; - colour-scheme and browser theme-colour viewport metadata; and - optional Google Analytics and Vercel Analytics providers. Next.js generates the charset, standard viewport, and other resulting head elements from its Metadata API. Do not duplicate them with hand-written `` tags. ## Global metadata configuration Configure global defaults in the developer-owned `src/supacharger.config.ts`: ```ts METADATA: { SITE_URL: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000', TITLE_TEMPLATE: '%s | My Product', INDEXING_ENABLED: process.env.VERCEL_ENV ? process.env.VERCEL_ENV === 'production' : process.env.NODE_ENV === 'production', FAVICON_SET_ENABLED: true, SOCIAL_IMAGE: { URL: '/opengraph-image.png', ALT: 'My Product', WIDTH: 1200, HEIGHT: 630, }, COLOR_SCHEME: 'light dark', THEME_COLOR: { LIGHT: '#ffffff', DARK: '#111111', }, }, ``` Use the stable public production origin for `SITE_URL`. Preview deployment URLs must not become canonical production URLs. `INDEXING_ENABLED` defaults to production-only behaviour in the supplied template, including Vercel preview protection. Keep `public/favicon.ico` at the public root. Store `favicon.svg`, `favicon-96x96.png`, `apple-touch-icon.png`, both web-app manifest PNGs, and `site.webmanifest` under `public/favicons/`. Set `FAVICON_SET_ENABLED` only when that complete standard set exists. The protected metadata helper and web manifest use `/favicons/...` URLs for every asset except `/favicon.ico`. A relative social-image URL is resolved against `SITE_URL`. Keep the image descriptive, product-owned, and suitable for social sharing. The root helper deliberately does not declare one global canonical pathname: doing so would incorrectly make every route canonical to the home page. ## Page titles and canonical URLs A child layout or page can export ordinary Next.js metadata: ```ts import type { Metadata } from 'next'; export const metadata: Metadata = { title: 'Pricing', }; ``` This becomes `Pricing | My Product` through the root template. Use `title.absolute` only when a page deliberately needs a title outside that template. For a page that also needs consistent canonical and social metadata, use the protected helper: ```ts import { SC_CONFIG } from '@/supacharger/supacharger-config'; import { createSupachargerPageMetadata } from '@/supacharger/root-document'; export const metadata = createSupachargerPageMetadata(SC_CONFIG, { title: 'Pricing', description: 'Compare plans for My Product.', canonicalPath: '/pricing', }); ``` Dynamic pages may call the same helper from `generateMetadata`. Supply a canonical path only when it is the preferred public URL for that content. When overriding nested `openGraph` or `twitter` objects manually, remember that Next.js merges nested metadata shallowly. Public content sites should also add accurate Next.js `robots.ts` and `sitemap.ts` files for their real route policy. Do not publish authenticated or private application routes in a sitemap. Exclude the complete `/_next/` framework namespace, `/robots.txt`, `/sitemap.xml`, `/.well-known/`, and `.webmanifest` files from the application's Proxy matcher. Framework assets, HMR, crawler metadata, association documents, and install manifests must return their direct responses without session refresh, localisation, authentication, onboarding, rewrite, or redirect handling. When development requests originate from the loopback alias rather than the hostname used to start Next.js, add only `127.0.0.1` to `allowedDevOrigins`. For the Supacharger documentation site's own XML sitemap, agent-facing Markdown index, and raw page endpoints, see [Agent-readable documentation](./agent-discovery.md). ## Root providers The editable layout reads the common provider switches: ```ts ROOT_PROVIDERS: { INTERNATIONALISATION: true, THEME: true, TOASTS: true, }, ``` These switches determine whether the template mounts the standard layer. The concrete theme implementation, provider props, product route/context providers, fonts, and body classes remain application-owned. Specdrive can therefore retain its product-specific provider tree while using the same protected document and analytics helper. ## Analytics Google Analytics is opt-in and uses the integration recommended for Next.js App Router: ```ts ANALYTICS: { GOOGLE_ANALYTICS_ID: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID || null, VERCEL_ANALYTICS_ENABLED: true, }, ``` Set a GA4 measurement ID in the deployment environment: ```bash NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX ``` The measurement ID is public configuration rather than a secret. When it is empty, Supacharger emits no Google Analytics component. When configured, the protected helper loads `GoogleAnalytics` from `@next/third-parties/google`; do not add duplicate inline `gtag.js` scripts. Vercel Analytics remains independently configurable. Analytics configuration does not replace consent, privacy, retention, or regional compliance decisions. Add the consent mechanism required by the application's audience before enabling analytics where applicable. Security controls such as Content Security Policy, HSTS, Permissions Policy, and Referrer Policy belong in HTTP response headers, not in the root metadata object. --- ## Deploy to Vercel :::warning Moved page This page has been merged into [Deploy and production readiness](../Application%20Development/deploy.md). Use the Application Development page as the canonical Supacharger Core deployment reference. ::: Deployment remains one phase of the [hosted setup wizard](./setup-wizard.md). Follow the wizard for the complete sequence; environment variables, the canonical domain, Supabase Auth URLs, SMTP, and email templates must agree before authentication is production-ready. --- ## Local development with a coding agent A coding-agent harness can inspect the repository, run the Supacharger and Supabase CLIs, edit files, and verify the result. Use a coding mode with access to the local project and terminal. A normal chat-only conversation cannot prepare files or run local commands. This guide uses Codex and Claude Code as examples. The Supacharger workflow is the same in any harness that can read the repository, run commands, show diffs, and request approval before sensitive operations. ## 1. Install the local prerequisites Install these tools before asking the agent to set up the application: - Git - Node.js 22 or later - npm 11, as declared by the Supacharger core package - Docker Desktop or Docker Engine for the local Supabase stack - a coding-agent harness The project runs the Supabase CLI with `npx`, so a separate global Supabase CLI installation is not required. Install the Supacharger CLI globally and verify that it is available: ```bash npm install -g @glowplug/supacharger-cli supacharger --help ``` The Stripe CLI is optional until you need to test billing webhooks locally. ## 2. Choose a coding-agent harness ### ChatGPT with Codex If you are starting from ChatGPT, use its Codex coding environment rather than a standard chat. Codex can work against a local repository from the desktop app or terminal. Follow the current [Codex CLI installation guide](https://learn.chatgpt.com/docs/codex/cli), open the application directory as the workspace, and start the agent from that directory. Codex reads repository guidance from `AGENTS.md`. Supacharger already includes this file, so ask Codex to read it before making changes. Do not run `/init` over established project instructions. ### Claude Code Follow the current [Claude Code setup guide](https://code.claude.com/docs/en/overview), then start Claude from the application directory: ```bash cd /path/to/your-project claude ``` Claude Code reads project guidance from `CLAUDE.md`. If the project has only `AGENTS.md`, add a small `CLAUDE.md` that imports the shared instructions instead of maintaining two divergent copies: ```md # Project instructions See @AGENTS.md. ``` Claude Code supports file imports in `CLAUDE.md`; see [How Claude remembers your project](https://code.claude.com/docs/en/memory). Review the imported instructions before approving work. ## 3. Create or open the Supacharger project To create a new application, run the CLI from the parent directory. The target should be a new or empty directory because `supacharger init` installs the complete canonical starter there. Never point it at files you need to preserve; for a non-empty target, the CLI can offer to remove the existing contents except `.git`. ```bash supacharger init my-project cd my-project ``` For an existing application, clone it normally and open its repository root in the agent harness. Do not run `supacharger init` inside an established project. ## 4. Give the agent the right context Start with an orientation request that does not authorize changes: ```text Orient yourself to this Supacharger repository before changing anything. 1. Read AGENTS.md and any more specific instruction files. 2. Inspect README.md, package.json, the lockfile, .supacharger metadata, and the local environment example. 3. Verify the available versions of Node, npm, Git, Docker, the Supacharger CLI, and the project-scoped Supabase CLI. 4. Report missing prerequisites and the exact local setup commands you propose. Do not edit files, print secret values, link a hosted project, push migrations, or change any hosted service yet. ``` This preflight gives the agent the repository's actual package manager, scripts, ownership rules, and validation commands instead of relying on generic framework assumptions. You can also direct the agent to Supacharger's maintained setup instructions: ```text Fetch and follow the appropriate instructions to set me up for Supacharger from https://app.specdrive.dev/agents/setup-supacharger.md Before executing commands, compare the instructions with AGENTS.md and the current repository. Show me any conflict or destructive step first. ``` Treat fetched instructions like code: confirm the source, review the proposed commands, and do not approve operations outside the current project without understanding why they are needed. ## 5. Let the agent prepare the local environment After reviewing the preflight, give the agent a local-only implementation request: ```text Set up this Supacharger application for local development. - Follow the repository instructions and use its declared package manager. - Install dependencies. - Create .env.local from the supplied example if it does not exist. - Never invent, reveal, or commit secrets; stop and tell me which values I must add. - Start the local Supabase stack and apply the local migrations. - Generate current database types. - Run supacharger doctor and the repository's relevant checks. - Start the development server and report the local URL. Keep all database work local. Do not link, reset, migrate, or reconfigure a hosted Supabase project. Preserve unrelated working-tree changes. ``` A typical Supacharger project uses commands like these, but the agent should prefer the scripts declared by the current repository: ```bash npm install cp .env.local.example .env.local npx supabase start npm run db:reset npm run generate-types supacharger doctor npm run build npm run dev ``` Some local environment values come from `npx supabase status`. Add them to `.env.local` without pasting service-role keys, database passwords, Stripe secrets, or other credentials into the chat transcript. ## 6. Verify the first run Before building features, confirm that: - `node --version` reports Node 22 or later; - Docker is running and `npx supabase status` succeeds; - the local database resets without migration errors; - generated Supabase types are current; - `supacharger doctor` passes or reports only understood follow-up work; - the application build succeeds; - the local application loads; and - `git diff` contains only expected setup changes and no secrets. Ask the agent to summarize every changed file, every check it ran, and anything it could not verify. Review the diff before committing. ## 7. Keep local and hosted work separate Local setup does not configure a hosted Supabase project, Vercel, authentication providers, SMTP, email templates, or production secrets. When the local application is healthy, continue with the [Full setup guide](./setup-wizard.md). For a hosted step, name the intended account, project, and environment explicitly. Always preview linked database migrations before applying them, and never let an agent infer that production is the intended target. --- ## Updates Update applications from an immutable, reviewed Core tag or commit. Run the CLI plan first, inspect exact replacements, merge targets, new forward migrations and preserved developer paths, then apply the update. ```bash supacharger coreupdate --plan --ref supacharger coreupdate --ref ``` Do not edit exact managed files in a consumer. Put product routes under `src/app/(project)/`, composition in `src/supacharger.adapters/`, styling in `src/styles/`, and configuration in `src/supacharger.config.ts`. After updating, review the Git diff and run every post-update check. Review the linked Supabase migration dry run separately; the CLI must not silently deploy hosted database changes. Advance the consumer Core lock only after hashes, tests and release checks pass. --- ## Skills # Supacharger skills Supacharger includes agent skills under `.agents/skills/`. A skill is a focused set of engineering instructions and supporting references that helps a compatible coding agent work safely in a particular area of the project. The starter currently includes: - `supabase` for Auth, Database, Storage, Edge Functions, migrations, RLS and Supabase CLI work; - `supabase-postgres-best-practices` for schema design, query performance, indexes, locking, connections and PostgreSQL security. ## Use a skill Open your coding agent in the project root. You can name a skill directly in a request, for example: ```text Use $supabase to add a forward migration for this feature, test it locally, and document the hosted deployment step. ``` Compatible agents may also select a skill automatically when your task matches its description. Naming it explicitly is useful when you want to make the working method unambiguous. The agent reads the complete `SKILL.md`, follows the project `AGENTS.md`, and loads only the references needed for the task. Project rules remain authoritative when a general skill and the repository differ. ## Keep skills aligned Core owns the supplied skill packages. The CLI copies the managed skill directories to consumers so improvements reach every application. Do not edit a managed skill independently in one consumer. Promote a reusable improvement to Core, release it, and update consumers through the normal Core lock flow. Product-specific skills may live beside the supplied skills in a developer-owned path. Give each one a narrow trigger, a complete `SKILL.md`, and only the references or scripts it actually needs. Never put secrets or environment credentials in a skill. ## A useful request pattern Tell the agent the outcome, environment and verification boundary: ```text Use $supabase. Add organisation invitations to the local schema, preserve RLS, run the database tests, generate a forward migration, and list the separate hosted release steps. Do not deploy until I approve the dry run. ``` This keeps the task concrete while the skill supplies current Supabase-specific safety and verification guidance. --- ## Supabase Auth coverage export const Supported = () => ( ); export const NotSupported = () => ( — ); # Supabase Auth coverage Supacharger builds its shared authentication journey on [Supabase Auth](https://supabase.com/docs/guides/auth). This page distinguishes between features available from Supabase and features wired end-to-end in the canonical Supacharger application. A green tick means supported; a dash means the shared Supacharger UI, server actions, callback handling, and configuration contract do not yet provide that complete journey. Phone authentication is outside the scope of this coverage. ## Authentication methods | Authentication method | Supabase Auth | Supacharger | Supacharger coverage | | --- | :---: | :---: | --- | | Email and password | | | Configurable sign-in and sign-up, password-strength validation, email confirmation, and protected password changes. | | Passwordless email sign-in: OTP | | | Sends an email code to an existing user, renders a configurable 6–10 digit input, and verifies with `type: 'email'`. | | Passwordless email sign-in: magic link | | | Sends a sign-in link to an existing user and completes the PKCE flow through `/auth/confirm`. | | Passwordless email sign-up | | | A guarded server action and configuration value exist, but the shared signup form does not currently invoke the signup action end-to-end. | | Password sign-up confirmation: OTP | | | The confirmation email contains a code and the form verifies it with `type: 'signup'`. | | Password sign-up confirmation: link | | | The confirmation email links to `/auth/confirm`, which verifies the token hash and preserves a safe return path. | | Password sign-up confirmation: OTP and link | | | One email offers both credentials. They represent the same one-time verification, so using either invalidates the other. | | Password reset by email link | | | Exchanges the recovery PKCE code at `/auth/callback`, verifies recovery AMR, then opens `/account/reset-password/new` for the protected password update. | | Built-in social OAuth/OIDC | | | The 21 supported Supacharger provider identifiers use `signInWithOAuth()` and return through `/auth/callback`. | | Custom OAuth/OIDC providers | | | Supabase accepts `custom:` provider identifiers, but Supacharger's typed provider map and buttons currently accept only the built-in identifiers listed below. | | Enterprise SSO with SAML 2.0 | | | Supabase provides SAML SSO; Supacharger has no SSO discovery, organisation-domain, or SAML callback journey. | | Passkeys (WebAuthn) | | | Supabase passkeys are experimental. Supacharger has no client opt-in, enrollment, sign-in, or credential-management UI. | | Web3 wallet sign-in | | | Supabase supports Ethereum and Solana wallets; Supacharger has no wallet discovery or signing journey. | | Anonymous sign-in | | | Supacharger can be configured to accept an existing anonymous session, but it does not create anonymous users or provide an upgrade/linking journey. | | TOTP multi-factor authentication | | | Account Security provides factor enrolment, verification, listing, and removal; the sign-in journey can require AAL2 for enrolled accounts. | | OAuth 2.1/OIDC server | | | Supabase can make a project an identity provider for other applications. Supacharger does not ship the required authorization and consent experience. | The email code and link modes share Supabase's **Magic Link or OTP** template. Passwordless sign-in selects one mode at a time. Password sign-up confirmation can select OTP, link, or both. See [Authentication and route protection](./authentication.md#authentication-journey) for configuration, callback behaviour, and the consumed-code/link edge case. The unsupported rows are not enabled by adding an icon or changing a Supabase Dashboard toggle. Each needs a complete application journey and a corresponding addition to Supacharger's configuration contract, server boundary, tests, and documentation. Refer to Supabase's current guides for [custom OAuth/OIDC providers](https://supabase.com/docs/guides/auth/custom-oauth-providers), [SAML SSO](https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml), [passkeys](https://supabase.com/docs/guides/auth/passkeys), [Web3](https://supabase.com/docs/guides/auth/auth-web3), [anonymous sign-in](https://supabase.com/docs/guides/auth/auth-anonymous), [MFA](https://supabase.com/docs/guides/auth/auth-mfa), and the [OAuth 2.1 server](https://supabase.com/docs/guides/auth/oauth-server) before extending coverage. ## Supported social OAuth/OIDC providers Supabase documents 19 built-in [social provider families](https://supabase.com/docs/guides/auth/social-login). Supacharger exposes 21 configuration identifiers because Slack and Twitter/X each have a preferred current integration and a legacy compatibility integration. Enable a button with `AUTH_PROVDERS_ENABLED`, then enable and configure the same provider in every relevant Supabase environment. A button alone does not configure the hosted provider, its secret, or its redirect allow-list. | Icon | Provider | Configuration identifier | Supacharger | | --- | --- | --- | :---: | | | Apple | `apple` | | | | Microsoft Azure | `azure` | | | | Bitbucket | `bitbucket` | | | | Discord | `discord` | | | | Facebook | `facebook` | | | | Figma | `figma` | | | | GitHub | `github` | | | | GitLab | `gitlab` | | | | Google | `google` | | | | Kakao | `kakao` | | | | Keycloak | `keycloak` | | | | LinkedIn (OIDC) | `linkedin_oidc` | | | | Notion | `notion` | | | | Twitch | `twitch` | | | | X / Twitter (OAuth 2.0) | `x` | | | | Twitter (OAuth 1.0a, legacy) | `twitter` | | | | Slack (OIDC) | `slack_oidc` | | | | Slack (legacy OAuth) | `slack` | | | | Spotify | `spotify` | | | | WorkOS | `workos` | | | | Zoom | `zoom` | | Prefer LinkedIn OIDC, Slack OIDC, and X/Twitter OAuth 2.0 for new integrations. The Slack legacy and Twitter OAuth 1.0a identifiers remain available only for compatibility. Every supported provider uses the OAuth PKCE callback at `/auth/callback`. Add the exact URL to the Supabase redirect allow-list. Browser code uses the project URL and a publishable key; never expose a secret or service-role key through a `NEXT_PUBLIC_` variable. Provider artwork lives in the CLI-managed `src/supacharger/assets/svgr/auth-providers/` directory and is rendered by the shared provider-button component. Project UI can enable any subset of the supported provider map. --- ## Anatomy & Folder Structure Supacharger uses four ownership classes. The colours below are labels as well as visual cues, so the tree remains understandable without colour. Core exact Merge managed Forward only Developer owned ## Reading the tree **Core exact** paths are copied from the immutable Core lock and must be byte-identical. **Merge managed** files have a shared contract but retain application values and dependencies. **Forward only** paths are permanent histories: add new migrations, never replace an installed one. **Developer owned** files survive every Core update. `src/supacharger.config.ts` supplies application values and is checked against the exact `SupachargerConfig` type. Managed forms render stable semantic `sc-*` classes; their dimensions, colours, radii, typography, spacing and layout live in the developer-owned stylesheets under `src/styles/`. The CLI reads `.supacharger/managed-files.json`, previews each ownership action, preserves developer paths, runs the declared checks and advances `.supacharger/core-lock.json` only after the exact contract passes. ## Naming Use lowercase kebab-case for files and folders, PascalCase for React component names, and route groups such as `(supacharger)` and `(project)` to separate ownership without changing public URLs.