Configuration
All configuration options for BillSDK
Options
const billing = billsdk({
database: drizzleAdapter(db, { schema, provider: "pg" }),
secret: process.env.BILLSDK_SECRET!,
trustedOrigins: ["https://myapp.com"],
payment: stripePayment({ ... }),
basePath: "/api/billing",
features: [...],
plans: [...],
plugins: [...],
hooks: { before, after },
logger: { level: "info", disabled: false },
});| Option | Type | Default | Description |
|---|---|---|---|
database | DBAdapter | memoryAdapter() | Database adapter |
payment | PaymentAdapter | paymentAdapter() | Payment provider adapter |
basePath | string | /api/billing | API base path |
secret | string | - | Required. Secret for signing CSRF tokens and Bearer auth. Min 32 chars. Falls back to BILLSDK_SECRET env var |
trustedOrigins | string[] | [] | Origins allowed to make mutating requests. Supports wildcards (*.example.com). Falls back to BILLSDK_TRUSTED_ORIGINS env var |
features | FeatureConfig[] | [] | Feature definitions |
plans | PlanConfig[] | [] | Plan definitions |
plugins | Plugin[] | [] | Plugins |
hooks | object | - | Request hooks |
logger | object | - | Logger configuration |
Features
Features are capabilities you can gate per plan. Currently only boolean (on/off) features are supported.
features: [
{ code: "export", name: "Export Data" },
{ code: "api_access", name: "API Access" },
{ code: "priority_support", name: "Priority Support" },
]| Property | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Unique identifier |
name | string | Yes | Display name |
type | "boolean" | "metered" | "seats" | No | Feature type (default: "boolean") |
Feature codes are type-safe. When you call checkFeature, TypeScript only accepts codes you defined:
// Type error: "invalid_feature" is not a valid feature code
await billing.api.checkFeature({ customerId, feature: "invalid_feature" });Plans
Plans define your pricing tiers. They exist only in code, not in the database.
plans: [
{
code: "free",
name: "Free",
description: "Get started",
isPublic: true,
prices: [{ amount: 0, interval: "monthly" }],
features: ["export"],
},
{
code: "pro",
name: "Pro",
prices: [
{ amount: 2000, interval: "monthly", currency: "usd" },
{ amount: 20000, interval: "yearly", trialDays: 14 },
],
features: ["export", "api_access"],
},
{
code: "enterprise",
name: "Enterprise",
isPublic: false, // Hidden from listPlans()
prices: [{ amount: 9900, interval: "monthly" }],
features: ["export", "api_access", "priority_support"],
},
]Plan Properties
| Property | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Unique identifier |
name | string | Yes | Display name |
description | string | No | Plan description |
isPublic | boolean | No | Show in listPlans() (default: true) |
prices | PlanPriceConfig[] | Yes | Pricing options |
features | string[] | No | Enabled feature codes |
Price Properties
| Property | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Price in cents (2000 = $20.00) |
interval | "monthly" | "quarterly" | "yearly" | Yes | Billing cycle |
currency | string | No | ISO 4217 code (default: "usd") |
trialDays | number | No | Trial period length |
Database Adapters
Drizzle
import { drizzleAdapter } from "@billsdk/drizzle-adapter";
database: drizzleAdapter(db, {
schema,
provider: "pg", // "pg" | "mysql" | "sqlite"
})Memory (Testing)
import { memoryAdapter } from "@billsdk/memory-adapter";
database: memoryAdapter()Payment Adapters
Default
Activates subscriptions immediately without payment processing. Used automatically if you don't specify a payment adapter.
import { paymentAdapter } from "@billsdk/payment-adapter";
payment: paymentAdapter()Use when:
- All your plans are free
- You're in development/testing
- You handle payments outside BillSDK
Stripe
import { stripePayment } from "@billsdk/stripe";
payment: stripePayment({
secretKey: process.env.STRIPE_SECRET_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
})Hooks
Execute code before or after every request.
hooks: {
before: async ({ request, path, method }) => {
// Return Response to short-circuit
if (!isAuthenticated(request)) {
return new Response("Unauthorized", { status: 401 });
}
// Return undefined to continue
},
after: async ({ request, path, method }) => {
console.log(`[billing] ${method} ${path}`);
},
}Logger
logger: {
level: "debug", // "debug" | "info" | "warn" | "error"
disabled: false,
}Security
BillSDK protects all mutating endpoints (POST, PUT, PATCH, DELETE) automatically. GET requests and the /webhook endpoint are exempt.
How It Works
Browser requests are protected by two layers:
- Origin validation — the
Originheader must match one of yourtrustedOrigins - CSRF token — a signed token (using your
secret) is required via cookie + header
The BillSDK client handles CSRF tokens automatically. You don't need to do anything.
Server-to-server requests can bypass origin and CSRF checks by sending the secret as a Bearer token:
curl -X POST https://myapp.com/api/billing/customer \
-H "Authorization: Bearer $BILLSDK_SECRET" \
-H "Content-Type: application/json" \
-d '{"externalId": "user_123", "email": "user@example.com"}'Server-side direct calls via billing.api.* don't go through HTTP, so security checks don't apply.
Configuration
const billing = billsdk({
// Required. Throws if missing. Falls back to BILLSDK_SECRET env var.
secret: process.env.BILLSDK_SECRET!,
// Origins allowed to make mutating requests from the browser.
// Supports wildcards. Falls back to BILLSDK_TRUSTED_ORIGINS env var (comma-separated).
trustedOrigins: ["https://myapp.com", "*.myapp.com"],
});Generate a secret:
openssl rand -base64 32Trusted Origins
| Pattern | Matches | Doesn't match |
|---|---|---|
https://myapp.com | https://myapp.com | http://myapp.com, https://sub.myapp.com |
*.myapp.com | https://sub.myapp.com, http://app.myapp.com | https://myapp.com |
https://*.myapp.com | https://sub.myapp.com | http://sub.myapp.com |
http://localhost:3000 | http://localhost:3000 | http://localhost:3001 |
Webhooks
The /webhook endpoint is exempt from origin and CSRF checks. It relies on the payment provider's signature verification (e.g., Stripe's webhook secret).