[GH-ISSUE #4801] Stripe plugin not work, POST /api/auth/subscription/upgrade 404 #10075

Closed
opened 2026-04-13 05:59:16 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @luozhouyang on GitHub (Sep 21, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4801

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Install stripe plugin
  2. Config a function to create an auth instance
  3. Config auth client, and trigger an subscription upgrade request

Current vs. Expected behavior

Current:
When I trigger an subscription on front page using auth client, an 404 NOT FOUND error throws.
The client side code is:


    setIsLoading(priceId);
    try {
      // Create checkout session using better-auth stripe plugin
      const response = await authClient.subscription.upgrade({
        plan: planName.toLowerCase(),
        priceId,
        successUrl: `${window.location.origin}/dashboard?subscription=success`,
        cancelUrl: `${window.location.origin}/pricing?subscription=cancelled`,
      });

      if (response?.url) {
        window.location.href = response.url;
      }
    } catch (error) {
      console.error("Failed to create checkout session:", error);
    } finally {
      setIsLoading(null);
    }

Expected:
I got redirected to stripe subscription page.

What version of Better Auth are you using?

1.3.9

System info

2025-09-21T12:54:50.820Z ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config.

2025-09-21T12:54:50.822Z INFO [Better Auth]: [#better-auth]: Make sure to default export your auth instance or to export as a variable named auth.

Which area(s) are affected? (Select all that apply)

Backend, Client

Auth config (if applicable)

import { authServerOptions } from "@repo/edgeauth-meta-types/better-auth.options";
import { betterAuth, User } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "../db/schema";
import { sendEmailForPasswordReset, sendEmailForVerification } from "./betterauth/email.smtp";
import { stripePlugin } from "./betterauth/stripe.plugin";


const enableDatabaseLogger = process.env.NODE_ENV === "development";


const TRUST_ORIGINS = [
    'http://localhost:3000',
    'http://localhost:8080',
];

export const authServer = (env: CloudflareEnv): ReturnType<typeof betterAuth> => {
    const db = drizzle(env.DB, { schema, logger: enableDatabaseLogger });
    const baseURL = process.env.BETTER_AUTH_URL || "http://localhost:3000";
    return betterAuth({
        ...authServerOptions,
        database: drizzleAdapter(db, { provider: 'sqlite' }),
        appName: "xxx",
        baseURL: baseURL,
        secret: env.BETTER_AUTH_SECRET,
        trustedOrigins: TRUST_ORIGINS,
        session: {
            expiresIn: 60 * 60, // 1 hour
            updateAge: 60 * 30, // 0.5 hour
            disableSessionRefresh: true, // Disable session refresh, updateAge takes no effect
            freshAge: 0, // disable session freshness
            cookieCache: {
                enabled: true,
                maxAge: 5 * 60, // 5 minutes
            }
        },
        plugins: [
            ...(authServerOptions.plugins || []),
            stripePlugin(env.STRIPE_SECRET_KEY, env.STRIPE_WEBHOOK_SECRET),
        ],
        emailAndPassword: {
            enabled: true,
            requireEmailVerification: true,
            sendResetPassword: sendEmailForPasswordReset,
            onPasswordReset: async ({ user }) => {
                // your logic here
                console.log(`Password for user ${user.email} has been reset.`);
            },
        },
        emailVerification: {
            sendOnSignUp: true,
            sendOnSignIn: false,
            autoSignInAfterVerification: false,
            expiresIn: 5 * 60,
            onEmailVerification: async (user: User) => {
                // your logic here
                console.log(`Email for user ${user.email} has been verified.`);
            },
            afterVerification: async (user: User) => {
                // your logic here
                console.log(`Email for user ${user.email} has been verified.`);
            },
            sendVerificationEmail: sendEmailForVerification,
        },
    });
};

Additional context

I use better-auth on cloudflare workers, so i need to create the auth instance dynamically based on the cloudflare env. Here is my API handler for /api/auth/...[all]:

import { authServer } from "@/lib/auth";
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { toNextJsHandler } from "better-auth/next-js";

async function handleRequest(req: Request): Promise<Response> {
    const { env } = getCloudflareContext();
    const auth = authServer(env);
    return await auth.handler(req);
}

export const { GET, POST } = toNextJsHandler(handleRequest);


the auth client config:

export const authClient = createAuthClient({
    appName: 'xxx',
    baseURL: baseURL
    plugins: [
        stripeClient({
            subscription: true //if you want to enable subscription management
        })
    ],
    credentials: 'include',
})

Originally created by @luozhouyang on GitHub (Sep 21, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4801 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Install stripe plugin 2. Config a function to create an auth instance 3. Config auth client, and trigger an subscription upgrade request ### Current vs. Expected behavior Current: When I trigger an subscription on front page using auth client, an 404 NOT FOUND error throws. The client side code is: ```typescript setIsLoading(priceId); try { // Create checkout session using better-auth stripe plugin const response = await authClient.subscription.upgrade({ plan: planName.toLowerCase(), priceId, successUrl: `${window.location.origin}/dashboard?subscription=success`, cancelUrl: `${window.location.origin}/pricing?subscription=cancelled`, }); if (response?.url) { window.location.href = response.url; } } catch (error) { console.error("Failed to create checkout session:", error); } finally { setIsLoading(null); } ``` Expected: I got redirected to stripe subscription page. ### What version of Better Auth are you using? 1.3.9 ### System info ```bash 2025-09-21T12:54:50.820Z ERROR [Better Auth]: [#better-auth]: Couldn't read your auth config. 2025-09-21T12:54:50.822Z INFO [Better Auth]: [#better-auth]: Make sure to default export your auth instance or to export as a variable named auth. ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript import { authServerOptions } from "@repo/edgeauth-meta-types/better-auth.options"; import { betterAuth, User } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzle } from "drizzle-orm/d1"; import * as schema from "../db/schema"; import { sendEmailForPasswordReset, sendEmailForVerification } from "./betterauth/email.smtp"; import { stripePlugin } from "./betterauth/stripe.plugin"; const enableDatabaseLogger = process.env.NODE_ENV === "development"; const TRUST_ORIGINS = [ 'http://localhost:3000', 'http://localhost:8080', ]; export const authServer = (env: CloudflareEnv): ReturnType<typeof betterAuth> => { const db = drizzle(env.DB, { schema, logger: enableDatabaseLogger }); const baseURL = process.env.BETTER_AUTH_URL || "http://localhost:3000"; return betterAuth({ ...authServerOptions, database: drizzleAdapter(db, { provider: 'sqlite' }), appName: "xxx", baseURL: baseURL, secret: env.BETTER_AUTH_SECRET, trustedOrigins: TRUST_ORIGINS, session: { expiresIn: 60 * 60, // 1 hour updateAge: 60 * 30, // 0.5 hour disableSessionRefresh: true, // Disable session refresh, updateAge takes no effect freshAge: 0, // disable session freshness cookieCache: { enabled: true, maxAge: 5 * 60, // 5 minutes } }, plugins: [ ...(authServerOptions.plugins || []), stripePlugin(env.STRIPE_SECRET_KEY, env.STRIPE_WEBHOOK_SECRET), ], emailAndPassword: { enabled: true, requireEmailVerification: true, sendResetPassword: sendEmailForPasswordReset, onPasswordReset: async ({ user }) => { // your logic here console.log(`Password for user ${user.email} has been reset.`); }, }, emailVerification: { sendOnSignUp: true, sendOnSignIn: false, autoSignInAfterVerification: false, expiresIn: 5 * 60, onEmailVerification: async (user: User) => { // your logic here console.log(`Email for user ${user.email} has been verified.`); }, afterVerification: async (user: User) => { // your logic here console.log(`Email for user ${user.email} has been verified.`); }, sendVerificationEmail: sendEmailForVerification, }, }); }; ``` ### Additional context I use better-auth on cloudflare workers, so i need to create the auth instance dynamically based on the cloudflare env. Here is my API handler for `/api/auth/...[all]`: ```typescript import { authServer } from "@/lib/auth"; import { getCloudflareContext } from "@opennextjs/cloudflare"; import { toNextJsHandler } from "better-auth/next-js"; async function handleRequest(req: Request): Promise<Response> { const { env } = getCloudflareContext(); const auth = authServer(env); return await auth.handler(req); } export const { GET, POST } = toNextJsHandler(handleRequest); ``` the auth client config: ```typescript export const authClient = createAuthClient({ appName: 'xxx', baseURL: baseURL plugins: [ stripeClient({ subscription: true //if you want to enable subscription management }) ], credentials: 'include', }) ```
GiteaMirror added the lockedbug labels 2026-04-13 05:59:16 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 21, 2025):

The 404 error on /api/auth/subscription/upgrade is likely because the Better Auth Stripe plugin expects the auth instance to be exported as a static variable named auth, not as a factory function. This static export is required for proper route registration and plugin endpoint exposure, especially in serverless environments like Cloudflare Workers. Your current pattern dynamically creates the auth instance per request, which can prevent Better Auth from detecting and registering plugin endpoints, resulting in missing routes and the error: "Couldn't read your auth config" docs.

To fix this, export your auth instance as a static variable named auth and use it directly with toNextJsHandler(auth) for your API route handlers. For example:

// lib/auth.ts
export const auth = betterAuth({
  // ...your config
  plugins: [
    stripePlugin(process.env.STRIPE_SECRET_KEY, process.env.STRIPE_WEBHOOK_SECRET),
    // ...other plugins
  ],
  // ...other options
});

// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);

If you need to use environment variables per request (as with Cloudflare Workers), ensure all required variables are available at cold start and avoid using a factory function for the export. After making this change, do a full rebuild and reinstall dependencies to ensure routes are registered example.

Also, confirm your client is sending a POST request to /api/auth/subscription/upgrade. If you still get a 404, explicitly set fetchOptions.method: "POST" in your client call, as the Stripe client plugin sometimes defaults to GET reference.

Finally, double-check that both better-auth and @better-auth/stripe are on the same version, and your Stripe plugin config includes subscription: { enabled: true, plans: [...] } reference.

Let me know if you need a minimal reproducible example for Cloudflare Workers!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3315985089 --> @dosubot[bot] commented on GitHub (Sep 21, 2025): <!-- Answer --> The 404 error on `/api/auth/subscription/upgrade` is likely because the Better Auth Stripe plugin expects the auth instance to be exported as a static variable named `auth`, not as a factory function. This static export is required for proper route registration and plugin endpoint exposure, especially in serverless environments like Cloudflare Workers. Your current pattern dynamically creates the auth instance per request, which can prevent Better Auth from detecting and registering plugin endpoints, resulting in missing routes and the error: "Couldn't read your auth config" [docs](https://github.com/better-auth/better-auth/blob/57510365a393bca2f6a37e50c43571651b121978/docs/content/docs/plugins/stripe.mdx). To fix this, export your auth instance as a static variable named `auth` and use it directly with `toNextJsHandler(auth)` for your API route handlers. For example: ```typescript // lib/auth.ts export const auth = betterAuth({ // ...your config plugins: [ stripePlugin(process.env.STRIPE_SECRET_KEY, process.env.STRIPE_WEBHOOK_SECRET), // ...other plugins ], // ...other options }); // app/api/auth/[...all]/route.ts import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { GET, POST } = toNextJsHandler(auth); ``` If you need to use environment variables per request (as with Cloudflare Workers), ensure all required variables are available at cold start and avoid using a factory function for the export. After making this change, do a full rebuild and reinstall dependencies to ensure routes are registered [example](https://github.com/better-auth/better-auth/blob/57510365a393bca2f6a37e50c43571651b121978/demo/nextjs/app/api/auth/[...all]/route.ts). Also, confirm your client is sending a POST request to `/api/auth/subscription/upgrade`. If you still get a 404, explicitly set `fetchOptions.method: "POST"` in your client call, as the Stripe client plugin sometimes defaults to GET [reference](https://github.com/better-auth/better-auth/issues/4611). Finally, double-check that both `better-auth` and `@better-auth/stripe` are on the same version, and your Stripe plugin config includes `subscription: { enabled: true, plans: [...] }` [reference](https://github.com/better-auth/better-auth/issues/4267). Let me know if you need a minimal reproducible example for Cloudflare Workers! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5096ba7e-a865-4964-96dd-1d1509a4bb63?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4801)
Author
Owner

@luozhouyang commented on GitHub (Sep 21, 2025):

I think it not possible for me to use a static auth instance, because the auth instance needs an drizzle database, which is constructed by Cloudflare D1, so the drizzle database need an env bindings from Cloudflare runtime.

<!-- gh-comment-id:3316026150 --> @luozhouyang commented on GitHub (Sep 21, 2025): I think it not possible for me to use a static `auth` instance, because the auth instance needs an drizzle database, which is constructed by Cloudflare D1, so the drizzle database need an env bindings from Cloudflare runtime.
Author
Owner

@dosubot[bot] commented on GitHub (Sep 21, 2025):

You're right—Cloudflare Workers require runtime env bindings for D1, so you can't use a static auth instance with a pre-constructed Drizzle database. Better Auth's plugin endpoints (like /api/auth/subscription/upgrade) are registered when the auth instance is created, so dynamic per-request instantiation should technically expose those endpoints if the Stripe plugin is included correctly. However, the framework and tooling (like CLI and some integrations) expect a static export for schema generation and route detection, which creates a design tension in serverless environments reference.

The best-known workaround is to create a temporary static auth.ts file using a local SQLite database for CLI/schema generation, then run the CLI to produce migration files for D1. For runtime, you can continue to create the auth instance dynamically per request with your env bindings—just ensure the Stripe plugin is always included in the plugins array and your client sends a POST request to the endpoint reference.

There’s no officially documented way to support fully dynamic plugin endpoint registration per request in Better Auth, and this is a known limitation reference. If you need dynamic runtime bindings and plugin endpoints, keep using your current pattern for runtime, and use the static export workaround for CLI/tooling. If dynamic endpoint registration is critical for your use case, you may want to monitor for future updates or consider a custom routing/proxy layer.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3316027876 --> @dosubot[bot] commented on GitHub (Sep 21, 2025): <!-- Answer --> You're right—Cloudflare Workers require runtime env bindings for D1, so you can't use a static `auth` instance with a pre-constructed Drizzle database. Better Auth's plugin endpoints (like `/api/auth/subscription/upgrade`) are registered when the auth instance is created, so dynamic per-request instantiation should technically expose those endpoints if the Stripe plugin is included correctly. However, the framework and tooling (like CLI and some integrations) expect a static export for schema generation and route detection, which creates a design tension in serverless environments [reference](https://github.com/better-auth/better-auth/issues/3549). The best-known workaround is to create a temporary static `auth.ts` file using a local SQLite database for CLI/schema generation, then run the CLI to produce migration files for D1. For runtime, you can continue to create the auth instance dynamically per request with your env bindings—just ensure the Stripe plugin is always included in the plugins array and your client sends a POST request to the endpoint [reference](https://github.com/better-auth/better-auth/issues/3500). There’s no officially documented way to support fully dynamic plugin endpoint registration per request in Better Auth, and this is a known limitation [reference](https://github.com/better-auth/better-auth/issues/3549). If you need dynamic runtime bindings and plugin endpoints, keep using your current pattern for runtime, and use the static export workaround for CLI/tooling. If dynamic endpoint registration is critical for your use case, you may want to monitor for future updates or consider a custom routing/proxy layer. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a4ff2e7c-4c46-4ab6-baa6-5be1b1f622c1?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4801)
Author
Owner

@luozhouyang commented on GitHub (Sep 21, 2025):

The problem is, the client get an 404 NOT FOUND on POST /api/auth/subscription/upgrade on runtime!!! I can confirm that the runtime has auth instance configured because other /api/auth endpoints works fine. It means that even though I have not a static auth instance for better-auth cli, I can still expect the auth instances works on runtime! So how can i check the subscription endpoints are been registered or not?

<!-- gh-comment-id:3316057102 --> @luozhouyang commented on GitHub (Sep 21, 2025): The problem is, the client get an 404 NOT FOUND on POST /api/auth/subscription/upgrade on runtime!!! I can confirm that the runtime has auth instance configured because other `/api/auth` endpoints works fine. It means that even though I have not a static auth instance for better-auth cli, I can still expect the auth instances works on runtime! So how can i check the subscription endpoints are been registered or not?
Author
Owner

@dosubot[bot] commented on GitHub (Sep 21, 2025):

There’s no built-in Better Auth API to list registered endpoints at runtime, but you can check if the Stripe subscription endpoints are being registered by enabling debug logs. Set the environment variable DEBUG=better-auth:* (or similar for your platform) before starting your worker—this should print out all registered routes, including /subscription/upgrade, when the auth instance is created.

If you want to inspect programmatically, you can temporarily add a log statement inside your authServer function after creating the auth instance, like:

console.log("Registered routes:", Object.keys(auth.handler?.ctx?.routes || {}));

This will show you what endpoints are available at runtime.

If /subscription/upgrade is missing, double-check that the Stripe plugin is always included in the plugins array and that subscription: { enabled: true } is set in the plugin options. Also, make sure your handler exports both GET and POST directly from toNextJsHandler if you’re using Next.js, and that you’re sending a POST request from the client (not GET) reference.

If you see the endpoint in logs but still get a 404, there may be a routing or method mismatch. If it’s missing, the plugin registration logic may not be running as expected at runtime source.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3316058393 --> @dosubot[bot] commented on GitHub (Sep 21, 2025): <!-- Answer --> There’s no built-in Better Auth API to list registered endpoints at runtime, but you can check if the Stripe subscription endpoints are being registered by enabling debug logs. Set the environment variable `DEBUG=better-auth:*` (or similar for your platform) before starting your worker—this should print out all registered routes, including `/subscription/upgrade`, when the auth instance is created. If you want to inspect programmatically, you can temporarily add a log statement inside your `authServer` function after creating the auth instance, like: ```js console.log("Registered routes:", Object.keys(auth.handler?.ctx?.routes || {})); ``` This will show you what endpoints are available at runtime. If `/subscription/upgrade` is missing, double-check that the Stripe plugin is always included in the `plugins` array and that `subscription: { enabled: true }` is set in the plugin options. Also, make sure your handler exports both `GET` and `POST` directly from `toNextJsHandler` if you’re using Next.js, and that you’re sending a POST request from the client (not GET) [reference](https://github.com/better-auth/better-auth/issues/3684). If you see the endpoint in logs but still get a 404, there may be a routing or method mismatch. If it’s missing, the plugin registration logic may not be running as expected at runtime [source](https://github.com/better-auth/better-auth/blob/57510365a393bca2f6a37e50c43571651b121978/packages/stripe/src/index.ts). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/2d9afc8d-2e26-4067-ab4b-b85988eed373?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4801)
Author
Owner

@luozhouyang commented on GitHub (Sep 22, 2025):

Resolved. The stripe plugin options are misconfigured: subscriptions -> subscription

export const stripePlugin = (stripeSecretKey?: string, stripeWebhookSecretKey?: string) => {
    const stripeClient = new Stripe(stripeSecretKey || 'secret', {
        apiVersion: '2025-08-27.basil'
    })
    const stripeInstance = stripe({
        stripeClient,
        stripeWebhookSecret: stripeWebhookSecretKey || 'secret',
        createCustomerOnSignUp: true,
-        subscriptions: {
+        subscription: {
             enabled: true,
+            plans: SUBSCRIPTION_PLANS,
         }
-    })
+    });
 }

<!-- gh-comment-id:3316460425 --> @luozhouyang commented on GitHub (Sep 22, 2025): Resolved. The `stripe` plugin options are misconfigured: `subscriptions` -> `subscription` ```bash export const stripePlugin = (stripeSecretKey?: string, stripeWebhookSecretKey?: string) => { const stripeClient = new Stripe(stripeSecretKey || 'secret', { apiVersion: '2025-08-27.basil' }) const stripeInstance = stripe({ stripeClient, stripeWebhookSecret: stripeWebhookSecretKey || 'secret', createCustomerOnSignUp: true, - subscriptions: { + subscription: { enabled: true, + plans: SUBSCRIPTION_PLANS, } - }) + }); } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10075