> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Supabase Boilerplate

> Clone the Next.js, Supabase, and Dodo Payments subscription starter, with Google sign-in, a Drizzle schema, a webhook Edge Function, and a billing dashboard.

<CardGroup cols={2}>
  <Card title="GitHub Repository" icon="github" href="https://github.com/dodopayments/dodo-supabase-subscription-starter">
    Minimal Next.js, Supabase, and Dodo Payments subscription boilerplate.
  </Card>

  <Card title="Live Demo" icon="rocket" href="https://dodopayments-supabase-nextjs.vercel.app">
    Explore the deployed demo.
  </Card>
</CardGroup>

## Overview

The Supabase subscription starter is a Next.js 15 and React 19 app that sells subscriptions with Dodo Payments. Supabase provides Google OAuth sign-in and the Postgres database, and Drizzle ORM defines the schema. A Supabase Edge Function receives Dodo Payments webhooks and stores payments and subscriptions, and a basic dashboard shows each user's plan and invoices.

If you need only checkout, Customer Portal, and webhook route handlers for an existing app, use a framework adaptor instead:

<CardGroup cols={2}>
  <Card title="Next.js Adaptor" icon="code-merge" href="/developer-resources/nextjs-adaptor" />

  <Card title="Express Adaptor" icon="code-merge" href="/developer-resources/express-adaptor" />
</CardGroup>

## Prerequisites

Before you begin, you need:

* Node.js 18 or later, or Bun 1.0 or later.
* A Supabase project. You need its URL, anon key, service role key, and database connection string.
* A Dodo Payments account, for an API key and a webhook signing secret.
* A Google Cloud OAuth client, for its Client ID and Client Secret.

## Quickstart

<Steps>
  <Step title="Clone and Install">
    Clone the repository, then install dependencies with Bun, npm, or pnpm:

    ```bash theme={null}
    git clone https://github.com/dodopayments/dodo-supabase-subscription-starter.git
    cd dodo-supabase-subscription-starter
    # choose one
    bun install
    # or
    npm install
    # or
    pnpm install
    ```
  </Step>

  <Step title="Create a Supabase Project">
    Create a Supabase project. Later steps use its project reference, the subdomain in `https://[your-project-ref].supabase.co`. Copy these values:

    * `NEXT_PUBLIC_SUPABASE_URL`, the project URL
    * `NEXT_PUBLIC_SUPABASE_ANON_KEY`, the anon key
    * `SUPABASE_SERVICE_ROLE_KEY`, the service role key
    * `DATABASE_URL`, the database connection string
  </Step>

  <Step title="Configure Google OAuth">
    In Google Cloud, add this authorized redirect URI to your OAuth client: `https://[your-project-ref].supabase.co/auth/v1/callback`. Then, in Supabase Auth, enable the Google provider with your Client ID and Client Secret.
  </Step>

  <Step title="Configure Dodo Payments">
    With the **Live Mode** switch in the sidebar off, create an API key under **Developer → API Keys** in the Dodo Payments dashboard. Keep `DODO_PAYMENTS_ENVIRONMENT` set to `test_mode` while you develop.
  </Step>

  <Step title="Add the Webhook in Dodo Payments">
    Under **Developer → Webhooks**, add an endpoint with this URL. You deploy the function that serves it in a later step.

    ```text theme={null}
    https://[your-project-ref].supabase.co/functions/v1/dodo-webhook
    ```

    Select the payment and subscription events that the function handles:

    * Payment events: `payment.succeeded`, `payment.failed`, `payment.processing`, and `payment.cancelled`
    * Subscription events: `subscription.active`, `subscription.plan_changed`, `subscription.renewed`, `subscription.on_hold`, `subscription.cancelled`, `subscription.expired`, and `subscription.failed`

    Copy the endpoint's signing secret. It's the value of `DODO_WEBHOOK_SECRET`.
  </Step>

  <Step title="Create .env.local">
    Create a `.env.local` file in the root directory:

    ```env theme={null}
    # Supabase
    NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
    SUPABASE_SERVICE_ROLE_KEY=your-service-role

    # Database
    DATABASE_URL=postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres

    # Dodo Payments
    DODO_PAYMENTS_API_KEY=your-dodo-api-key
    DODO_WEBHOOK_SECRET=your-webhook-secret
    DODO_PAYMENTS_ENVIRONMENT=test_mode
    ```

    The Next.js app reads every variable in this file except `DODO_WEBHOOK_SECRET`. The Edge Function reads `DODO_WEBHOOK_SECRET` from its Supabase secrets, which you set when you deploy it.

    <Warning>
      Don't commit secrets. In deployed environments, set them as environment variables.
    </Warning>
  </Step>

  <Step title="Provision the Database Schema">
    Push the Drizzle schema to your Supabase database:

    ```bash theme={null}
    bun run db:push
    # or
    npm run db:push
    # or
    pnpm run db:push
    ```

    <Check>
      Tables created: `users`, `subscriptions`, `payments`.
    </Check>
  </Step>

  <Step title="Deploy the Webhook Function">
    Log in to Supabase, store the signing secret as a function secret, and deploy the `dodo-webhook` Edge Function. Replace `[your-project-ref]` with your project reference:

    ```bash theme={null}
    # login (one-time)
    bunx supabase login
    # or
    npx supabase login

    # store the Dodo Payments signing secret for the function
    npx supabase secrets set DODO_WEBHOOK_SECRET=your-webhook-secret --project-ref [your-project-ref]

    # deploy the edge function
    bun run deploy:webhook --project-ref [your-project-ref]
    # or
    npm run deploy:webhook -- --project-ref [your-project-ref]
    # or
    pnpm run deploy:webhook --project-ref [your-project-ref]
    ```

    The `deploy:webhook` script runs `supabase functions deploy dodo-webhook --no-verify-jwt`, so Dodo Payments can call the function without a Supabase auth token. Supabase provides `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` to the function by default.

    To check the deployment, send an unsigned request:

    ```bash cURL theme={null}
    curl -X POST \
      'https://[your-project-ref].supabase.co/functions/v1/dodo-webhook' \
      -H 'Content-Type: application/json' \
      -d '{"type":"payment.succeeded","data":{}}'
    ```

    A `400` response with `Invalid webhook signature` means the function is running and has its secret. A `500` response with `Server configuration error` means `DODO_WEBHOOK_SECRET` isn't set. To send a signed test event, open the endpoint in **Developer → Webhooks** and use its **Testing** tab.
  </Step>

  <Step title="Create Products and Features">
    Under **Products** in the Dodo Payments dashboard, click **Add Product** to create a subscription product for each plan. Optionally, to list plan features in the app, add a metadata entry with the key `features` and a JSON array of strings as its value. The product's metadata then looks like this:

    ```json theme={null}
    {
      "features": "[\"Feature 1\", \"Feature 2\", \"Feature 3\"]"
    }
    ```

    Metadata values are strings, numbers, or booleans, so store the array as a JSON string. The pricing UI parses the `features` value and renders each item.
  </Step>

  <Step title="Run the Dev Server">
    ```bash theme={null}
    bun run dev
    # or
    npm run dev
    # or
    pnpm run dev
    ```

    Open [http://localhost:3000](http://localhost:3000) and sign in with Google.
  </Step>
</Steps>

<Check>
  You now have a working subscription SaaS scaffolded with Supabase and Dodo Payments. To confirm it end to end, subscribe to a plan with a [test card](/miscellaneous/testing-process). After the webhook arrives, the dashboard shows your new plan.
</Check>

## What’s Included

* Authentication through Supabase, with Google OAuth configured
* Subscription checkout through Dodo Payments
* A Supabase Edge Function for webhooks (`dodo-webhook`)
* A Drizzle ORM schema, with `db:generate` and `db:migrate` scripts for migrations
* A dashboard with invoices, subscription status, and plan features
* Server actions to change plans, cancel a subscription, and restore a cancelled subscription

<Tip>
  Keep `DODO_PAYMENTS_ENVIRONMENT` set to `test_mode` until you complete end-to-end tests. To go live, set it to `live_mode` and use a live mode API key.
</Tip>

## Key Files and Paths

The webhook handler, app routes, and database schema live in these files:

<Tabs>
  <Tab title="Edge Function">
    ```text theme={null}
    supabase/functions/dodo-webhook/
      index.ts            # verifies signatures, stores payments and subscriptions
      deno.json           # Deno configuration
    ```
  </Tab>

  <Tab title="Next.js Routes">
    ```text theme={null}
    app/page.tsx                    # redirects to /login or /dashboard
    app/login/page.tsx              # Google sign-in page
    app/dashboard/page.tsx          # plans, subscription, and invoices
    app/checkout/route.ts           # checkout handler
    app/api/auth/callback/route.ts  # OAuth callback
    actions/*                       # server actions
    ```
  </Tab>

  <Tab title="Database (Drizzle)">
    ```text theme={null}
    lib/drizzle/schema.ts   # users, subscriptions, payments
    lib/drizzle/client.ts   # client
    ```
  </Tab>
</Tabs>

## Environment Variables

The Next.js app and the Edge Function read these variables:

<AccordionGroup>
  <Accordion title="Supabase">
    ```env theme={null}
    NEXT_PUBLIC_SUPABASE_URL=
    NEXT_PUBLIC_SUPABASE_ANON_KEY=
    SUPABASE_SERVICE_ROLE_KEY=
    DATABASE_URL=
    ```

    `.env.example` doesn't list `SUPABASE_SERVICE_ROLE_KEY`, but the app's admin client in `lib/supabase/admin.ts` needs it. Add it yourself.
  </Accordion>

  <Accordion title="Dodo Payments">
    ```env theme={null}
    DODO_PAYMENTS_API_KEY=
    DODO_WEBHOOK_SECRET=
    DODO_PAYMENTS_ENVIRONMENT=test_mode|live_mode
    ```
  </Accordion>

  <Accordion title="Google OAuth">
    The app doesn't read Google credentials from environment variables. Enter the Client ID and Client Secret in the Google provider settings of Supabase Auth, and add this redirect URI to the OAuth client in Google Cloud:

    ```text theme={null}
    https://[your-project-ref].supabase.co/auth/v1/callback
    ```
  </Accordion>
</AccordionGroup>

## Verification and Troubleshooting

<AccordionGroup>
  <Accordion title="Webhook signature invalid (400)">
    * Make sure the function's `DODO_WEBHOOK_SECRET` secret matches the endpoint's signing secret in the Dodo Payments dashboard.
    * Confirm you deployed the latest `dodo-webhook` function.
    * The function verifies the [Standard Webhooks](https://standardwebhooks.com/) headers `webhook-id`, `webhook-signature`, and `webhook-timestamp`. Make sure a proxy doesn't strip them.
  </Accordion>

  <Accordion title="Database push fails">
    * Check the `DATABASE_URL` syntax.
    * The direct connection (`db.[project-ref].supabase.co:5432`) uses IPv6 unless your project has the IPv4 add-on. On an IPv4-only network, use the session pooler connection string instead ([Supabase connection docs](https://supabase.com/docs/guides/database/connecting-to-postgres)).
    * Wait 2–3 minutes after you create the project before the first push.
  </Accordion>

  <Accordion title="OAuth redirect mismatch">
    * The redirect URI must be `https://[ref].supabase.co/auth/v1/callback`.
    * Use the same URI in Google Cloud and in the Supabase Auth provider.
  </Accordion>
</AccordionGroup>

<Info>
  For the original repository and detailed steps, see [dodo-supabase-subscription-starter](https://github.com/dodopayments/dodo-supabase-subscription-starter).
</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.