Plans
A plan is a recurring price you charge customers on a fixed cadence — IDR 99,000 per month, USD 49 per year. Plans are the price catalog that subscriptions point at; the subscription itself owns the customer-and-billing relationship, the plan just owns the amount and the interval. This page covers the plugipay.plans namespace; for the full field reference see API: Plans, and for how plans fit alongside subscriptions and invoices see Concepts → Plan.
Namespace
plugipay.plans — every method on this namespace:
plugipay.plans.create(input)
plugipay.plans.get(id)
plugipay.plans.list(params?)
plugipay.plans.update(id, patch)
plugipay.plans.archive(id)
There's no hard delete — once a plan has been on a subscription, it has to stick around for historical records. archive is the soft-delete equivalent.
Methods
plans.create
Signature. plugipay.plans.create(input): Promise<Plan>
Creates a plan. The four required fields are name, currency, amount (in the currency's minor unit — rupiah, not thousand-rupiah), and interval. The SDK auto-attaches an Idempotency-Key.
import { PlugipayClient } from '@forjio/plugipay-node';
const plugipay = new PlugipayClient({
keyId: process.env.PLUGIPAY_KEY_ID!,
secret: process.env.PLUGIPAY_SECRET!,
});
const pro = await plugipay.plans.create({
name: 'Pro Monthly',
currency: 'IDR',
amount: 99_000_00, // → IDR 99,000.00 — minor units
interval: 'month',
});
console.log(pro.id); // → 'plan_01HX...'
Amount is in the currency's minor unit. For IDR that's "sen" (1/100 of a rupiah); for USD that's cents.
99000meansIDR 990(probably not what you want);9900000isIDR 99,000. Double-check this against Conventions → Money before going live.
plans.get
Signature. plugipay.plans.get(id): Promise<Plan>
const plan = await plugipay.plans.get('plan_01HX...');
console.log(`${plan.name} — ${plan.amount} ${plan.currency} / ${plan.interval}`);
plans.list
Signature. plugipay.plans.list(params?): Promise<{ data: Plan[]; cursor: string | null; hasMore: boolean }>
Filters: active (boolean), order ('asc' / 'desc', on createdAt), limit, cursor. The default order is 'desc' — newest first — which is what dashboards want; for catalog UIs you usually want 'asc'.
const { data } = await plugipay.plans.list({ active: true, order: 'asc', limit: 100 });
for (const plan of data) {
console.log(`${plan.id}: ${plan.name}`);
}
plans.update
Signature. plugipay.plans.update(id, patch): Promise<Plan>
PATCH semantics. Note what's not updatable: you can't change currency, amount, or interval on an existing plan — those would silently change the price every existing subscriber is paying. To change the price, create a new plan and migrate subscriptions (see Versioning a plan below).
const updated = await plugipay.plans.update('plan_01HX...', {
name: 'Pro Monthly (legacy)',
description: 'Closed to new signups',
metadata: { gtm_tag: 'legacy_2026' },
});
plans.archive
Signature. plugipay.plans.archive(id): Promise<Plan>
Sets active: false and removes the plan from public price lists. Existing subscriptions continue to renew on it; only new subscriptions are blocked.
await plugipay.plans.archive('plan_01HX...');
Unarchiving is done via update({ active: true }).
Types
interface Plan {
id: string; // 'plan_...'
accountId: string;
name: string;
currency: 'IDR' | 'USD';
interval: 'day' | 'week' | 'month' | 'year';
amount: number; // minor units
active: boolean;
createdAt: string; // ISO-8601
updatedAt: string;
}
For the full field reference (including server-set fields and the description / metadata shape on update), see API: Plans.
Common patterns
Build a public pricing page
Fetch only active plans, sorted by amount, grouped by currency:
async function publicPricing() {
const { data } = await plugipay.plans.list({ active: true, order: 'asc', limit: 100 });
const byCurrency: Record<string, Plan[]> = {};
for (const p of data) {
(byCurrency[p.currency] ??= []).push(p);
}
for (const list of Object.values(byCurrency)) {
list.sort((a, b) => a.amount - b.amount);
}
return byCurrency;
}
Versioning a plan (price change)
You can't mutate amount on an existing plan, so price changes are a two-step:
// 1. Archive the old plan so nobody new signs up on it.
await plugipay.plans.archive('plan_pro_monthly_v1');
// 2. Create v2 at the new price.
const v2 = await plugipay.plans.create({
name: 'Pro Monthly',
currency: 'IDR',
amount: 129_000_00,
interval: 'month',
});
// 3. Migrate existing subscribers (one at a time — see subscriptions docs).
Migrating subscribers is a separate operation on the Subscriptions namespace; you typically grandfather existing subscribers on v1 and let them renew there, or call subscriptions.create for v2 with initialDiscount to handle proration.
Soft-deleting a plan
There's no delete. Use archive for the "this plan no longer exists from a product perspective" case:
await plugipay.plans.archive(planId);
Existing subscriptions on the plan continue to bill normally; they just can't be created against it after archival.
Build a "compare plans" matrix
For a feature-comparison table, attach feature flags via metadata:
await plugipay.plans.update('plan_pro', {
metadata: {
feature_priority_support: 'true',
feature_advanced_analytics: 'true',
feature_user_limit: '25',
},
});
await plugipay.plans.update('plan_starter', {
metadata: {
feature_priority_support: 'false',
feature_advanced_analytics: 'false',
feature_user_limit: '3',
},
});
Metadata is string–to–string; serialize booleans/numbers as strings and parse on the way out.
Errors
| Code | Status | Cause |
|---|---|---|
validation_error |
400 | Missing required field, bad currency, non-positive amount, unknown interval. |
not_found |
404 | Plan ID doesn't exist or lives in another workspace. |
forbidden |
403 | Key lacks the right scope. |
conflict |
409 | (Rare) Plan archival when there are blocking dependent objects on certain enterprise configs. |
See Errors for the SDK's error model and API errors for the full catalog.
Next
- Subscriptions — put a plan on a customer.
- Checkout sessions — one-off charges (the alternative to plans).
- Templates — customize how plans appear on hosted checkout.
- API: Plans — HTTP reference.