Nuda Kit stores billing plans in the database and links them to Stripe products. This allows you to display plans in your app and create checkout sessions dynamically.
The plans system provides:
Database Plans
Stripe Integration
Multiple Modes
Active/Inactive
Plans are stored in the plans table:
| Column | Type | Description |
|---|---|---|
id | Integer | Primary key |
name | String | Display name (e.g., "Pro") |
key | String | Unique identifier (e.g., "pro") |
stripe_price_id | String | Stripe Price ID (price_xxx) |
mode | Enum | subscription or payment |
is_active | Boolean | Whether plan is available |
created_at | Timestamp | Creation date |
updated_at | Timestamp | Last update |
| Mode | Description | Use Case |
|---|---|---|
subscription | Recurring billing | Monthly/yearly plans |
payment | One-time payment | Lifetime deals |
price_)Run the plan seeder or create plans manually:
node ace db:seed --files=database/seeders/plan_seeder.ts
The seeder creates example plans — update the stripePriceId values with your actual Stripe Price IDs.
Edit database/seeders/plan_seeder.ts with your plans:
| Field | Example | Description |
|---|---|---|
name | "Pro" | Display name |
key | "pro" | Unique slug for API |
stripePriceId | "price_1234..." | From Stripe Dashboard |
mode | "subscription" | Payment type |
isActive | true | Available for purchase |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET | /plans | No | List all active plans |
Response:
{
"data": {
"plans": [
{
"id": 1,
"name": "Pro",
"key": "pro",
"stripePriceId": "price_xxx",
"mode": "subscription",
"isActive": true
}
]
}
}
/plans endpoint is public (no authentication required) so it can be used on landing pages.Nuda Kit includes two example plans:
| Plan | Key | Mode | Description |
|---|---|---|---|
| Pro | pro | Subscription | Standard paid plan |
| Ultimate | ultimate | Subscription | Premium plan |
Customize these or add more plans as needed.
# Using AdonisJS REPL
node ace repl
# In REPL:
> const Plan = (await import('#models/plan')).default
> await Plan.create({
name: 'Enterprise',
key: 'enterprise',
stripePriceId: 'price_xxx',
mode: 'subscription',
isActive: true
})
To hide a plan without deleting it:
# Using AdonisJS REPL
node ace repl
> const Plan = (await import('#models/plan')).default
> const plan = await Plan.findByOrFail('key', 'old-plan')
> plan.isActive = false
> await plan.save()
Deactivated plans:
/plans responseThe frontend fetches plans and displays pricing:
| Component | Location | Description |
|---|---|---|
| Pricing Section | Landing page | Public pricing cards |
| Plan Cards | Billing settings | Upgrade options |
| PlanItem | Components | Reusable plan display |
Pricing, features, and styling are customizable in the frontend components.
| Practice | Description |
|---|---|
| Use keys | Reference plans by key, not ID |
| Keep Stripe in sync | Update database when changing Stripe |
| Soft deactivate | Set isActive: false instead of deleting |
| Test mode first | Create plans in Stripe test mode |