Database schema
Tables and fields added by the Paystack Better Auth plugin.
Run Better Auth CLI to generate migrations:
npx auth@latest generateSchema
The plugin extends the core user table and creates subscription, payment, and webhookEvent tables. Better Auth auto-adds an id field (string, primary key) on new tables. Numeric Paystack ID fields and payment.amount use bigint in PostgreSQL — see Numeric columns.
User
Table Name: user (extends core user table)
| Field | Type | Description |
|---|---|---|
paystackCustomerCode? | string | Paystack customer code (e.g. CUS_xxx) |
paystackCustomerId? | number (bigint) | Paystack customer ID |
Subscription
only when subscription.enabled is true
Table Name: subscription
| Field | Type | Description |
|---|---|---|
id | stringRequired | Unique identifier (primary key, added automatically by Better Auth) |
userId | stringRequired | The ID of the user. References user.id |
referenceId | stringRequired | Reference ID for the subscription (typically the user or organization ID) |
provider | stringRequired | Billing provider identifierDefault: paystack |
subscriptionCode | stringRequired | Paystack subscription code. Unique |
customerId | number (bigint)Required | Paystack customer ID |
customerCode | stringRequired | Paystack customer code |
planCode | stringRequired | Paystack plan code |
planName | stringRequired | Display name of the plan |
emailToken | stringRequired | Paystack email token used for cancel and resume actions |
status | stringRequired | Normalized subscription status |
currentPeriodStart | DateRequired | Start of the current billing period |
currentPeriodEnd? | Date | End of the current billing period |
cancelAtPeriodEnd | booleanRequired | Whether the subscription cancels at period endDefault: false |
metadata? | string | JSON metadata stored as a string |
Payment
omit when disablePaymentPersistence: true
Table Name: payment
| Field | Type | Description |
|---|---|---|
id | stringRequired | Unique identifier (primary key, added automatically by Better Auth) |
reference | stringRequired | Paystack transaction reference. Unique |
transactionId | number (bigint)Required | Paystack transaction ID |
userId? | string | The ID of the user (if linked). References user.id |
referenceId? | string | Optional reference ID for the payment |
provider | stringRequired | Billing provider identifierDefault: paystack |
customerCode | stringRequired | Paystack customer code |
customerId | number (bigint)Required | Paystack customer ID |
amount | number (bigint)Required | Amount in the smallest currency unit (kobo, pesewas, cents, etc.) |
currency | stringRequired | ISO currency code (e.g. GHS, NGN) |
status | stringRequired | Normalized payment status |
channel? | string | Payment channel (e.g. card, mobile_money) |
paidAt | DateRequired | When the payment was completed |
metadata? | string | JSON metadata stored as a string |
Webhook event
omit when disableWebhookPersistence: true
Table Name: webhookEvent
| Field | Type | Description |
|---|---|---|
id | stringRequired | Unique identifier (primary key, added automatically by Better Auth) |
eventId | stringRequired | Unique Paystack event ID for deduplication. Unique |
type | stringRequired | Paystack event type (e.g. charge.success) |
payload | stringRequired | Raw webhook payload as JSON string |
status | stringRequired | Processing status (pending, processed, failed)Default: pending |
processedAt? | Date | When the event was processed |
errorMessage? | string | Error message if processing failed |
Paystack numeric IDs and payment.amount can exceed PostgreSQL INTEGER (max 2,147,483,647). Use BIGINT for the columns listed below or inserts may fail. New projects using this plugin get bigint: true by default; existing databases that already migrated with INTEGER need a manual migration.
Numeric columns (use BIGINT)
Paystack returns large numeric IDs from its API. For example, customer ID 6293201508 is greater than the PostgreSQL INTEGER maximum (2,147,483,647), so an INTEGER column will reject the value on insert.
| Table | Column | Why BIGINT |
|---|---|---|
user | paystackCustomerId | Paystack customer ID |
subscription | customerId | Paystack customer ID |
payment | customerId | Paystack customer ID |
payment | transactionId | Paystack transaction ID |
payment | amount | Amount in smallest currency unit (can exceed INTEGER at high values) |
String fields (paystackCustomerCode, subscriptionCode, reference, eventId, etc.) are unaffected. amount is the payment value in kobo, pesewas, cents, etc. — not a Paystack resource ID.
Fix existing databases
If you already ran npx auth@latest generate before upgrading the plugin, edit your schema or migration to use BIGINT for the columns above.
Edit schema.prisma after generate, then run prisma migrate dev:
// user table
paystackCustomerId BigInt?
// subscription table
customerId BigInt
// payment table
customerId BigInt
transactionId BigInt
amount BigIntPrisma returns BigInt at runtime for these fields. Coerce with Number() when passing values to APIs that expect number if needed.
Change integer(...) to bigint(..., { mode: "number" }) in the generated auth schema:
import { bigint } from "drizzle-orm/pg-core";
paystackCustomerId: bigint("paystack_customer_id", { mode: "number" }),
customerId: bigint("customer_id", { mode: "number" }),
transactionId: bigint("transaction_id", { mode: "number" }),
amount: bigint("amount", { mode: "number" }),Use mode: "number" so values stay as JS number (safe for Paystack IDs under Number.MAX_SAFE_INTEGER). Run drizzle-kit generate / migrate after editing.
For Kysely adapter output or manual migrations (PostgreSQL):
ALTER TABLE "user" ALTER COLUMN "paystackCustomerId" TYPE BIGINT;
ALTER TABLE "subscription" ALTER COLUMN "customerId" TYPE BIGINT;
ALTER TABLE "payment" ALTER COLUMN "customerId" TYPE BIGINT;
ALTER TABLE "payment" ALTER COLUMN "transactionId" TYPE BIGINT;
ALTER TABLE "payment" ALTER COLUMN "amount" TYPE BIGINT;Adjust quoted identifiers and table names to match your generated output. For MySQL and SQLite, use BIGINT equivalently.
Persistence options
| Option | Effect |
|---|---|
disablePaymentPersistence: true | Skip payment table; still use onCheckoutComplete |
disableWebhookPersistence: true | Skip webhookEvent table |