Overview
The defineAddon() function defines an add-on product that can be purchased on top of a subscription plan. Add-ons can incrementally increase feature limits, set new absolute limits, or grant boolean access to features.
When used with a generic type parameter, it ensures that only features defined in your configuration can be referenced.
Function Signature
function defineAddon <
F extends Record < string , FeatureDefInput > = Record < string , FeatureDefInput >
>(
config : Omit < AddonDefInput , "features" > & {
features : F extends Record < string , FeatureDefInput >
? Partial < Record < keyof F , AddonFeatureValue >>
: Record < string , AddonFeatureValue >;
}
) : AddonDefInput
Type Parameters
F
Record<string, FeatureDefInput>
default: "Record<string, FeatureDefInput>"
Feature dictionary type. Pass typeof yourFeatures for strict key validation.
Parameters
The add-on configuration object. Human-readable display name for the add-on.
Optional description for marketing and documentation.
type
'recurring' | 'one_time'
required
Billing type:
recurring: Charged on every billing cycle
one_time: Charged once at purchase
Price amount in smallest currency unit (e.g., cents).
ISO 4217 currency code (e.g., “USD”, “EUR”).
billing_interval
'monthly' | 'quarterly' | 'yearly'
Required if type === 'recurring'. Must match the plan’s billing interval.
features
Record<string, AddonFeatureValue>
required
Feature entitlements this add-on modifies or grants. Show AddonFeatureValue properties
Numeric limit to add or set.
type
'increment' | 'set'
default: "increment"
How the limit is applied:
increment: Adds to the base plan’s limit
set: Completely overrides the base plan’s limit
Boolean toggle for granting access to a feature.
If false, allows overage (relaxes the limit to a soft limit).
Returns
The add-on configuration, normalized to AddonDefInput type.
Usage
Increment Addon (Default Behavior)
Adds to the base plan’s feature limit:
import { defineAddon } from "@revstack/core" ;
export const extraSeats = defineAddon ({
name: "Extra Seats (5)" ,
description: "Add 5 additional team members" ,
type: "recurring" ,
amount: 500 , // $5.00
currency: "USD" ,
billing_interval: "monthly" ,
features: {
seats: {
value_limit: 5 ,
type: "increment" , // Adds to the plan's limit
},
},
});
// If plan has 10 seats, customer gets 10 + 5 = 15 total seats
Set Addon (Override Behavior)
Completely replaces the base plan’s feature limit:
import { defineAddon } from "@revstack/core" ;
export const unlimitedStorage = defineAddon ({
name: "Unlimited Storage" ,
description: "Remove all storage limits" ,
type: "recurring" ,
amount: 4900 , // $49.00
currency: "USD" ,
billing_interval: "monthly" ,
features: {
storage: {
value_limit: 999999999999 , // Effectively unlimited
type: "set" , // Replaces the plan's limit entirely
},
},
});
// Regardless of plan's storage limit, customer gets 999999999999 bytes
Boolean Access Addon
Grants access to a premium feature:
import { defineAddon } from "@revstack/core" ;
export const ssoAddon = defineAddon ({
name: "SSO Add-on" ,
description: "Enable SAML/OAuth Single Sign-On" ,
type: "recurring" ,
amount: 9900 , // $99.00
currency: "USD" ,
billing_interval: "monthly" ,
features: {
sso: {
has_access: true , // Grants boolean access
},
},
});
One-Time Addon
import { defineAddon } from "@revstack/core" ;
export const onboarding = defineAddon ({
name: "Premium Onboarding" ,
description: "White-glove setup and migration service" ,
type: "one_time" ,
amount: 199900 , // $1,999.00
currency: "USD" ,
features: {
priority_support: {
has_access: true ,
},
},
});
Soft Limit Addon
Relaxes a hard limit to allow overage:
import { defineAddon } from "@revstack/core" ;
export const overageProtection = defineAddon ({
name: "Overage Protection" ,
description: "Never get blocked - pay only for what you use" ,
type: "recurring" ,
amount: 1900 , // $19.00
currency: "USD" ,
billing_interval: "monthly" ,
features: {
api_calls: {
is_hard_limit: false , // Allows overage instead of blocking
},
},
});
Multiple Features
import { defineAddon } from "@revstack/core" ;
export const growthPack = defineAddon ({
name: "Growth Pack" ,
description: "Extra seats, storage, and API calls" ,
type: "recurring" ,
amount: 2900 , // $29.00
currency: "USD" ,
billing_interval: "monthly" ,
features: {
seats: {
value_limit: 10 ,
type: "increment" ,
},
storage: {
value_limit: 50_000_000_000 , // 50 GB
type: "increment" ,
},
api_calls: {
value_limit: 25000 ,
type: "increment" ,
},
},
});
Type-Safe with Feature Dictionary
import { defineConfig , defineFeature , defineAddon } from "@revstack/core" ;
const features = {
seats: defineFeature ({
name: "Team Seats" ,
type: "static" ,
unit_type: "count" ,
}),
storage: defineFeature ({
name: "Storage" ,
type: "metered" ,
unit_type: "bytes" ,
}),
};
export default defineConfig ({
features ,
plans: { /* ... */ } ,
addons: {
extra_seats: defineAddon < typeof features >({
name: "Extra Seats (5)" ,
type: "recurring" ,
amount: 500 ,
currency: "USD" ,
billing_interval: "monthly" ,
features: {
seats: { value_limit: 5 , type: "increment" },
// typo_feature: { value_limit: 10 }, // ❌ Compile error!
},
}),
} ,
}) ;
Add-on Aggregation Logic
The EntitlementEngine aggregates add-ons with the following rules:
Processing Order : “set” add-ons are processed first, then “increment” add-ons
Increment Behavior : Limits are summed (plan: 5 seats + addon: 3 seats = 8 total)
Set Behavior : Completely replaces the previous limit and tracking source
Soft Limits : If ANY source sets is_hard_limit: false, the entire feature becomes soft-limited
Boolean Access : If ANY source grants has_access: true, access is granted
Validation
The validator ensures:
Recurring add-ons have a billing_interval that matches the plan’s interval
Add-ons only reference features defined in config.features
Add-ons referenced in price.available_addons actually exist
Source
Location : packages/core/src/define.ts:70-80