products.ts

Products, plans, displayed prices, and access granted after purchase.

config/products.ts exports a Record<string, ProductConfig> object. Top-level keys are product IDs, and each product's plans object is keyed by plan ID. You can define your own product and plan IDs. Names such as saavo_starter, license, and startup_monthly are only template examples.

Replace the Price IDs

All six default priceId values end in _replace_me and are placeholders. Before enabling Checkout, you must replace them with real Price IDs from the current Stripe account. The displayed salePrice does not determine the charge. The Stripe Price defines the actual amount and billing interval.

Product keys

Product keys must be nonempty strings. You can add, remove, or change them to suit your project. The configured product keys form the ProductId union type. If there are no products, you can export an empty object.

Product objects

Prop

Type

plans

plans contains the product's plans and has the type Record<string, PlanConfig>. Plan keys must be nonempty strings. Different products can use different keys. The configured plan keys form the PlanIdOf<P> and PlanId union types.

Plan objects

Prop

Type

Subscription plan fields

When type is recurring, the following fields are also required:

Prop

Type

features

An array of plan selling points with the type LocalizedText[]. The UI displays them in array order. Omit this field if you do not need to display selling points.

presentation

Purchase page display settings. These affect only the text on public product and purchase pages. They do not change prices, purchase status, or access rights.

Prop

Type

purchaseFacts has the type LocalizedText[] and contains information buyers should know. specifications is an array of specifications, each containing label: LocalizedText, value: LocalizedText, and an optional tone: "default" | "positive". highlights is an array of highlight cards, each containing icon, title, and description. icon accepts documentation, access, source, or payment.

When you specify presentation, you must include purchaseFacts, specifications, and highlights. These arrays can be empty.

couponOffers

An array of dynamic coupon offers activated through /offer/:id links. If specified, the array must contain at least one entry.

Prop

Type

Offers can also override features and presentation, using the same format as the plan fields. Offers cannot change priceId, billing intervals, purchase status, or access rights, and cannot contain nested offers.

access

The roles and entitlements granted after a successful purchase. This field is optional for one-time plans and required for subscription plans. For a subscription plan, at least one of the roles and entitlements arrays must be nonempty.

roles

An array of roles. Each entry can be a role key or an object with grant parameters.

Prop

Type

config accepts priority: number, description: string, metadata: Record<string, unknown>, and the Unix millisecond timestamps effective.startsAt and effective.endsAt.

entitlements

An array of entitlements. One-time plans can use entitlement keys or objects. Subscription plans must use objects.

Prop

Type

The entitlement object's config is required for subscription plans.

Prop

Type

When kind is numeric, you can also specify:

Prop

Type

reset defines the quota reset period. alignment: "calendar" supports days, weeks, months, and years. alignment: "fixed" supports only days and weeks. count must be a positive integer. quotaUsedStrategy accepts clear, drain, or keep. You can also use { mode: "override", quotaUsed: number } to specify the used quota.

Template defaults

The template's default configuration contains only one product. The product and plan IDs in the tables are examples that you can replace to suit your business. Before renaming or removing products, you must also update references in pages, purchase flows, the affiliate program, upgrade suggestions, and other affected features. Do not assume these features will still work after changing only this configuration.

Product IDname.valuename.keyPlan count
saavo_starterSample SaaS subscriptioncomponents.billing.product.name6

description.value is Example monthly and yearly plans to replace with your product terms., and its text key is components.billing.product.description.

Plans

Plan IDTitleIntervalsalePricepriceIdFlags
licenseHobbyMonthly$19price_hobby_monthly_replace_medefault: true
hobby_yearlyHobbyYearly$190price_hobby_yearly_replace_me—
startup_monthlyStartupMonthly$49price_startup_monthly_replace_merecommended: true
startup_yearlyStartupYearly$490price_startup_yearly_replace_merecommended: true
enterprise_monthlyEnterpriseMonthly$149price_enterprise_monthly_replace_me—
enterprise_yearlyEnterpriseYearly$1,490price_enterprise_yearly_replace_me—

All six plans have type set to recurring, intervalCount set to 1, and allowRepurchase set to false. The three yearly plans also have a valueHint of “Two months free”.

Access rights

PlansRole keyEntitlement keyEntitlement typePriority
license, hobby_yearlypremiumstarter_downloadboolean100
startup_monthly, startup_yearlypremiumstarter_downloadboolean200
enterprise_monthly, enterprise_yearlypremiumstarter_downloadboolean300

premium and starter_download are also example keys in the template's default configuration, defined in roles.ts and entitlements.ts, respectively. When changing these definitions, update their references in this file as well.