npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-auth-lemonsqueezy

v0.1.0

Published

Lemon Squeezy plugin for Better Auth — subscription management, webhooks, and access control

Readme

@better-auth/lemonsqueezy

A Better Auth plugin that integrates Lemon Squeezy subscription and payment management. Links authenticated users to Lemon Squeezy customers, syncs subscription state via webhooks, provides checkout and billing portal flows, and enables plan-based access control.

Installation

npm install @better-auth/lemonsqueezy

Peer Dependencies

  • better-auth >= 1.2.0

Quick Start

Server Setup

import { betterAuth } from "better-auth";
import { lemonSqueezy } from "@better-auth/lemonsqueezy";

const auth = betterAuth({
  // ...your existing config
  plugins: [
    lemonSqueezy({
      apiKey: process.env.LEMONSQUEEZY_API_KEY!,
      storeId: process.env.LEMONSQUEEZY_STORE_ID!,
      webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET!,
      createCustomerOnSignUp: true,
      subscription: {
        enabled: true,
        plans: [
          {
            name: "pro",
            productId: "prod_123",
            intervals: {
              monthly: "variant_456",
              annual: "variant_789",
            },
          },
        ],
      },
      defaultSuccessUrl: "https://myapp.com/billing?success=true",
      defaultCancelUrl: "https://myapp.com/billing?cancelled=true",
    }),
  ],
});

Client Setup

import { createAuthClient } from "better-auth/client";
import { lemonSqueezyClient } from "@better-auth/lemonsqueezy/client";

const authClient = createAuthClient({
  plugins: [lemonSqueezyClient()],
});

Configuration Reference

| Option | Type | Default | Description | | --- | --- | --- | --- | | apiKey | string | required | Lemon Squeezy API key | | storeId | string | required | Lemon Squeezy store ID | | webhookSecret | string | required | Webhook signing secret for signature verification | | createCustomerOnSignUp | boolean | false | Auto-create a Lemon Squeezy customer on user sign-up | | onCustomerCreated | (data) => void | — | Callback after customer creation. Receives { userId, lsCustomerId } | | onWebhookEvent | (event) => void | — | Callback on any webhook event | | subscription | SubscriptionConfig | — | Subscription plans configuration | | defaultSuccessUrl | string | — | Fallback success URL for checkout | | defaultCancelUrl | string | — | Fallback cancel URL for checkout | | allowEmailFallback | boolean | true | When false, disables email-based webhook correlation. Recommended for security-sensitive deployments | | usageEndpoint | boolean | false | Enable the usage reporting HTTP endpoint. Do not expose in untrusted environments |

Plan Definition

interface LemonSqueezyPlan {
  name: string;       // Display name (e.g., "pro", "enterprise")
  productId: string;  // Lemon Squeezy product ID
  intervals: {
    monthly?: string; // Variant ID for monthly billing
    annual?: string;  // Variant ID for annual billing
  };
}

Webhook Setup

The plugin exposes a POST /api/auth/lemonsqueezy/webhook endpoint that receives and verifies Lemon Squeezy events.

Lemon Squeezy Dashboard Steps

  1. Go to your Lemon Squeezy Dashboard > Settings > Webhooks
  2. Click Add Webhook (or the + button)
  3. Set the Callback URL to: https://your-domain.com/api/auth/lemonsqueezy/webhook
  4. Set a Signing Secret and use the same value as webhookSecret in your plugin config
  5. Subscribe to the following events:
    • subscription_created
    • subscription_updated
    • subscription_paused
    • subscription_unpaused
    • subscription_cancelled
    • subscription_expired
    • subscription_payment_success
    • subscription_payment_failed
    • subscription_payment_recovered
    • subscription_payment_refunded
  6. Click Save

Custom Webhook Logic

Use the onWebhookEvent callback to run custom logic on any event:

lemonSqueezy({
  // ...
  onWebhookEvent(event) {
    console.log(event.type);       // e.g., "subscription_created"
    console.log(event.userId);     // resolved user ID, or null
    console.log(event.resolved);   // whether user correlation succeeded
    console.log(event.data);       // raw Lemon Squeezy payload

    if (event.type === "subscription_payment_refunded") {
      // Handle refund logic
    }
  },
});

Client-Side Usage

The client plugin provides typed methods for all subscription operations:

Create a Subscription (Checkout)

const { url } = await authClient.subscription.create({
  plan: "pro",
  interval: "monthly", // optional, defaults to first configured interval
  successUrl: "https://myapp.com/success", // optional if server default is set
  cancelUrl: "https://myapp.com/cancel",   // optional if server default is set
});

// Redirect to the Lemon Squeezy checkout URL
window.location.href = url;

Cancel a Subscription

await authClient.subscription.cancel({
  subscriptionId: "sub_123",
});
// Subscription stays active until the end of the billing period

Resume a Cancelled Subscription

await authClient.subscription.resume({
  subscriptionId: "sub_123",
});

Change Plan (Upgrade/Downgrade)

await authClient.subscription.update({
  subscriptionId: "sub_123",
  plan: "enterprise",
  interval: "annual", // optional
});

List Subscriptions

const { subscriptions } = await authClient.subscription.list();

Get a Single Subscription

const { subscription } = await authClient.subscription.get({
  subscriptionId: "sub_123",
});

Billing Portal

const { url } = await authClient.subscription.portal({
  subscriptionId: "sub_123",
});

window.location.href = url;

Access Control

Gate features based on subscription plans using server-side helpers:

import { createAccessControlHelpers } from "@better-auth/lemonsqueezy";

const { hasActiveSubscription, hasActivePlan, requirePlan } =
  createAccessControlHelpers(auth);

// Check if user has any active subscription
const isSubscribed = await hasActiveSubscription(userId);

// Check if user has a specific plan
const isPro = await hasActivePlan(userId, "pro");

// Gate an endpoint — returns { allowed, subscription? }
const result = await requirePlan(userId, "pro");
if (!result.allowed) {
  return new Response("Upgrade required", { status: 403 });
}

Usage-Based Billing

For metered/usage-based subscriptions, use the server-side reportUsage helper (recommended):

import { createUsageReporter } from "@better-auth/lemonsqueezy";

const reportUsage = createUsageReporter(auth, process.env.LEMONSQUEEZY_API_KEY!);

// Report usage for a subscription
await reportUsage(userId, subscriptionId, 100);

An HTTP endpoint (POST /api/auth/lemonsqueezy/usage) is also available when usageEndpoint: true is set, but it is not recommended for production use as it allows authenticated users to report arbitrary usage.

Serverless Environments

The plugin uses in-memory rate limiting, checkout URL caching, and request deduplication. These are not shared across serverless instances — each cold start gets a fresh state. In serverless deployments (e.g., AWS Lambda, Vercel Functions), rate limiting will be best-effort only and checkout deduplication may not prevent all duplicate API calls. This is acceptable for most use cases; the Lemon Squeezy API and webhook-based state sync remain correct regardless.

Database Tables

The plugin creates two tables managed by Better Auth's migration system:

  • lsCustomer — links users to Lemon Squeezy customer IDs
  • lsSubscription — stores subscription state synced from webhooks

Run your Better Auth migrations to create these tables.

License

MIT