Private preview
Control which items appear on subscription invoices Private preview
Use scripts to control which items appear on subscription invoices.
Interested in getting early access to customize Billing with scripts?
Enter your email to request access.
Item handling scripts allow you to decide how subscriptions create invoice items, when they assign them to invoices, and how they group line items onto one or more invoices.
Here are some example use cases:
- Create line-by-line proration behaviors, such as generating proration items for changes to one product but not another.
- Defer the billing of invoice items until a particular condition is reached or until a specific point in time.
- Group items on multiple invoices. For example, you can create a supplementary invoice for prorations caused by seat changes and include only recurring charges on a main invoice.
Customize item handling
Note
You’re responsible for ensuring your configuration is correct. Stripe doesn’t take responsibility for any issues that arise as a result of your configuration.
In preview, if your script raises an error (for example, returns an invalid result, throws an exception, or times out), Stripe defaults to standard functionality for the operation that caused the failure. If your script encounters a large volume of errors or causes harm, Stripe may disable the script and default to standard functionality for all operations.
By default, subscriptions generate line items that appear on invoices in a number of situations, including when the subscription is created, updated, or canceled, and when it cycles. Whether new invoice items or invoices are created depends on properties of the subscription like the proration_behavior.
Subscriptions that are attached to billing cadences (a preview feature) don’t create invoices right away, but instead create invoice items when their period ends. When the cadence’s period ends, an invoice is generated containing the invoice items created by the associated subscriptions.
The item handling extension consists of three separate methods that are executed at different moments in the lifecycle of a subscription: before items are created, when un-invoiced items are selected for invoicing, and when items are grouped onto invoices.
beforeItemCreation: Runs before invoice items are created. Your script decides whether each item should be created or skipped.filterItems: Runs before invoices are created. Your script decides which items to include on the invoice. Items not included are deferred as pending invoice items for a future invoice.groupItems: Runs before invoices are created. Your script decides how items are grouped across one or more invoices.
Together, these extensions allow you to customize what invoice items your subscriptions create and how those invoice items get included on invoices.
Stripe-authored scripts
To get you started, Stripe has authored a script that addresses a common item handling customization:
- Separate invoice for metered items : If a subscription contains both metered and licensed products, they all end up on one invoice by default. With this script, line items are routed to different invoices based on the if the price is metered or licensed. When two invoices are created, the invoice with licensed items becomes the latest invoice for the subscription.
Author your own script
To further customize recurring item behavior, you can author your own script in a subset of TypeScript.
Verify your script
You’re responsible for verifying that your script reflects your desired functionality. Make sure you don’t enter any proprietary, confidential information (for example, PII), or malicious code.
Before you begin
Before creating your script:
- Understand how subscriptions work , including how they create invoices , interact with pending invoice items , and handle prorations .
- Learn more about scripts .
In addition the global limitations for user-authored scripts, item handling scripts have the following limitations:
- This script is configured at the account level. You can’t apply different scripts to individual customers or subscriptions.
Example implementations
Here are complete examples that implement custom item handling:
Filter proration items based on product metadata
This example demonstrates how to implement beforeItemCreation to selectively suppress proration items based on product metadata:
before-item-creation.ts
Key implementation points
This example illustrates the following points. You should keep these in mind when you author your own script:
- Map each item : Process each Item and return an ItemWithCreationStrategy with matching keys.
- Check price type : Use discriminated union checking ( item. priceKind === 'price' ) to safely access price fields.
- Access metadata : Navigate to product metadata to apply business rules based on custom fields.
- Filter prorations : Use the isProration flag to target only proration items.
- Return strategy : Choose between doNotCreate and invoice based on your logic.
Filter non-recurring items for manual invoicing
This example demonstrates how to implement filterItems to exclude non-recurring items from the subscription invoice, leaving them as pending invoice items that you can add to a manually created one-off invoice:
filter-items.ts
Key implementation points
This example illustrates the following points. You should keep these in mind when you author your own script:
- Return only included items : Items you return are included on the invoice. Items you omit are deferred as pending invoice items.
- Return item keys : The output only requires the key for each item you want to include.
- Handle all price types : Check for price , rateCardRate , and licenseFee variants when accessing pricing fields.
- Deferred items become pending : Non-recurring items are left as pending invoice items that you can later add to a one-off invoice you create manually.
Separate metered items onto supplementary invoices
This example shows how to implement groupItems to group metered usage items on separate invoices from licensed subscriptions:
group-items.ts
Key implementation points
This example illustrates the following points. You should keep these in mind when you author your own script:
- Return groups of items : Each group in the output becomes a separate invoice.
- Set
setsLatestInvoice: Exactly one group is set setsLatestInvoice: true . That group’s invoice updates the subscription’s latest _ invoice field. - Group with item keys : Each GroupedItem only needs the key matching an input item.
- Check usage type : Use the Item discriminated union to access recurring. usageType on price variants to distinguish between the related setting and the related setting items.
Extension method
The item handling extension consists of three separate methods that you can implement: beforeItemCreation, filterItems, and groupItems.
Execution order
The methods execute in order: beforeItemCreation, then filterItems, then groupItems. The operation inside of each function impacts the subsequent function. For example, items filtered out by filterItems don’t appear in the items available to groupItems.
beforeItemCreation
This method runs before items are created. Your script receives a list of items and returns a decision either to create the item for inclusion on an invoice (the default behavior) or to skip creating the item.
Function signature
export default class MyRecurringBillingItemHandling
implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
beforeItemCreation(
input: BeforeItemCreationInput,
config: MyRecurringBillingItemHandlingConfig,
context: Context,
): BeforeItemCreationResult {
// ...
}
}
Parameters
| Field | Type | Description |
|---|---|---|
input | BeforeItemCreationInput | The items that are about to be created. |
config | MyRecurringBillingItemHandlingConfig | Your custom configuration object, which contains custom parameters to modify the behavior of your script. |
context | Context | Runtime context including the extension identifier, livemode status, and optional Stripe-specific context values. |
Returns
| Field | Description |
|---|---|
BeforeItemCreationResult | Your decision for each item: create it as an invoice item or invoice line item or skip creating it depending on your custom logic. |
Input type
Input types
export interface BeforeItemCreationInput {
items: Array<Item>;
}
export type ItemType = 'credit' | 'debit';
export type Item = {
key: string;
type: ItemType;
isProration: boolean;
servicePeriod: AnyTimeRange;
prorationFactor: Decimal;
} & (
| {priceKind: 'price'; price: Price}
| {priceKind: 'rateCardRate'; rateCardRate: RateCardRate}
| {priceKind: 'licenseFee'; licenseFee: LicenseFee}
| {priceKind: 'customPricingUnitOverageRate'; customPricingUnitOverageRate: CustomPricingUnitOverageRate}
| {priceKind: 'other'; otherPriceKind: string}
);
Field descriptions
| Field | Type | Description |
|---|---|---|
items | Array<Item> | The list of items that are about to be created. |
key | string | Unique identifier for the item. |
type | ItemType | Either credit or debit. |
priceKind | 'price' | 'rateCardRate' | 'licenseFee' | 'customPricingUnitOverageRate' | 'other' | The discriminant that tells you which pricing payload is available on the item. |
isProration | boolean | Whether the item was generated from a proration event. |
servicePeriod | AnyTimeRange | The time period this item covers. |
prorationFactor | Decimal | The fraction of the billing period that this item covers. A value of 1 represents a full period. Values less than 1 represent prorated periods. |
Example input
Sample beforeItemCreation input
{
"items": [
{
"key": "ubi_123456",
"type": "debit",
"isProration": false,
"prorationFactor": 1,
"priceKind": "price",
"price": {
"id": "price_123456",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 2000,
"product": {
"id": "prod_123456",
"name": "Premium Plan",
"metadata": {
"suppress_prorations": "false"
}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
},
{
"key": "ubi_654321",
"type": "credit",
"isProration": true,
"prorationFactor": 0.5,
"priceKind": "price",
"price": {
"id": "price_654321",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 500,
"product": {
"id": "prod_123456",
"name": "Premium Plan",
"metadata": {
"suppress_prorations": "true"
}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
}
]
}
In this example:
- The first item is a regular subscription charge for 20 USD (2000 cents) covering a full billing period ( prorationFactor: 1 ).
- The second item is a proration credit of 5 USD (500 cents) covering half a billing period ( prorationFactor: 0. 5 ) for a product with suppress _ prorations metadata set to true .
Output type
Output types
export interface BeforeItemCreationResult {
items: Array<ItemWithCreationStrategy>;
}
export type ItemWithCreationStrategy =
| {key: string; creationStrategy: 'doNotCreate'}
| {key: string; creationStrategy: 'invoice'}
| {key: string; creationStrategy: 'other'; otherCreationStrategy: string};
Field descriptions
| Field | Type | Description |
|---|---|---|
items | Array<ItemWithCreationStrategy> | Your decision for each item. |
key | string | Must match the key from the input item. |
creationStrategy | 'doNotCreate' | 'invoice' | 'other' | Your decision for how to create this item: doNotCreate: Skip creating this item entirely invoice: Create this item as an invoice item or invoice line item (default behavior) other: Using this option falls back to the original behavior of the item. We recommend being more explicit by using either the doNotCreate or invoice options. |
Example output
Sample beforeItemCreation result
{
"items": [
{
"key": "ubi_123456",
"creationStrategy": "invoice"
},
{
"key": "ubi_654321",
"creationStrategy": "doNotCreate"
}
]
}
In this example:
- The regular subscription item is created normally as an invoice item.
- The proration item is suppressed because the product metadata suppression key is set.
filterItems
This method runs before invoices are created. Your script receives a list of items and returns the subset of items that should be included on the invoice. Items that you don’t return are deferred, leaving them as pending invoice items for a future invoice.
Function signature
export default class MyRecurringBillingItemHandling
implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
filterItems(
input: FilterItemsInput,
config: MyRecurringBillingItemHandlingConfig,
context: Context,
): FilterItemsResult {
// ...
}
}
Parameters
| Field | Type | Description |
|---|---|---|
input | FilterItemsInput | The items that are candidates for inclusion on the invoice. |
config | MyRecurringBillingItemHandlingConfig | Your custom configuration object, which contains custom parameters to modify the behavior of your script. |
context | Context | Runtime context including the extension identifier, livemode status, and optional Stripe-specific context values. |
Returns
| Field | Description |
|---|---|
FilterItemsResult | The items to include on the invoice. Items not listed are deferred as pending invoice items. |
Input type
Input types
export interface FilterItemsInput {
items: Array<Item>;
}
The Item type is the same as in BeforeItemCreationInput.
Field descriptions
| Field | Type | Description |
|---|---|---|
items | Array<Item> | The list of items that are candidates for inclusion on the invoice. |
Example input
Sample filterItems input
{
"items": [
{
"key": "ubi_123456",
"type": "debit",
"isProration": false,
"prorationFactor": 1,
"priceKind": "price",
"price": {
"id": "price_123456",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 2000,
"product": {
"id": "prod_123456",
"name": "Premium Plan",
"metadata": {}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
},
{
"key": "ubi_654321",
"type": "debit",
"isProration": false,
"prorationFactor": 1,
"priceKind": "price",
"price": {
"id": "price_654321",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 50,
"product": {
"id": "prod_654321",
"name": "Small Add-on",
"metadata": {}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
}
]
}
In this example:
- The first item is a subscription charge for 20 USD (2000 cents).
- The second item is a small add-on charge for 0.50 USD (50 cents).
Output type
Output types
export interface FilterItemsResult {
items: Array<ItemToInvoice>;
}
export interface ItemToInvoice {
key: string;
}
Field descriptions
| Field | Type | Description |
|---|---|---|
items | Array<ItemToInvoice> | The items to include on the invoice. |
key | string | Must match the key from an input item. Only items whose keys appear in this list are included on the invoice. All other items are deferred. |
Example output
Sample filterItems result
{
"items": [
{
"key": "ubi_123456"
}
]
}
In this example:
- The main subscription item is included on the invoice.
- The small add-on item is omitted, so it is deferred as a pending invoice item for a future billing cycle.
groupItems
This method runs before invoices are created. Your script receives a list of items and returns groups, where each group becomes a separate invoice. Use this to split items across multiple invoices. For example, you can separate metered usage charges from recurring license fees.
Function signature
export default class MyRecurringBillingItemHandling
implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
groupItems(
input: GroupItemsInput,
config: MyRecurringBillingItemHandlingConfig,
context: Context,
): GroupItemsResult {
// ...
}
}
Parameters
| Field | Type | Description |
|---|---|---|
input | GroupItemsInput | The items that are about to be placed on invoices. |
config | MyRecurringBillingItemHandlingConfig | Your custom configuration object, which contains custom parameters to modify the behavior of your script. |
context | Context | Runtime context including the extension identifier, livemode status, and optional Stripe-specific context values. |
Returns
| Field | Description |
|---|---|
GroupItemsResult | The grouping of items across invoices. |
Input type
Input types
export interface GroupItemsInput {
items: Array<Item>;
}
The Item type is the same as in BeforeItemCreationInput.
Field descriptions
| Field | Type | Description |
|---|---|---|
items | Array<Item> | The list of items that are about to be placed on invoices. |
Example input
Sample groupItems input
{
"items": [
{
"key": "ubi_123456",
"type": "debit",
"isProration": false,
"prorationFactor": 1,
"priceKind": "price",
"price": {
"id": "price_123456",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 2000,
"product": {
"id": "prod_123456",
"name": "Premium Plan",
"metadata": {}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
},
{
"key": "ubi_654321",
"type": "debit",
"isProration": false,
"prorationFactor": 1,
"priceKind": "price",
"price": {
"id": "price_654321",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "metered",
"meter": "meter_api_calls"
},
"type": "recurring",
"unitAmount": 1500,
"product": {
"id": "prod_654321",
"name": "API Usage",
"metadata": {}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-04-08T00:00:00Z"
}
},
{
"key": "ubi_789012",
"type": "credit",
"isProration": true,
"prorationFactor": 0.065,
"priceKind": "price",
"price": {
"id": "price_123456",
"metadata": {},
"recurring": {
"interval": "month",
"intervalCount": 1,
"usageType": "licensed",
"meter": null
},
"type": "recurring",
"unitAmount": 300,
"product": {
"id": "prod_123456",
"name": "Premium Plan",
"metadata": {}
},
"tiersMode": null,
"tiers": null,
"currency": "usd",
"billingScheme": "per_unit"
},
"servicePeriod": {
"startDate": "2026-03-08T00:00:00Z",
"endDate": "2026-03-10T00:00:00Z"
}
}
]
}
In this example:
- The first item is a licensed subscription charge for 20 USD (2000 cents).
- The second item is a metered usage charge for 15 USD (1500 cents).
- The third item is a proration credit for 3 USD (300 cents).
Output type
Output types
export interface GroupItemsResult {
groups: Array<ItemGroup>;
}
export interface ItemGroup {
items: Array<GroupedItem>;
setsLatestInvoice: boolean;
}
export interface GroupedItem {
key: string;
}
Field descriptions
| Field | Type | Description |
|---|---|---|
groups | Array<ItemGroup> | The groups of items. Each group becomes a separate invoice. |
items | Array<GroupedItem> | The items in this group. |
key | string | Must match a key from an input item. |
setsLatestInvoice | boolean | Whether the invoice created for this group updates the subscription’s latest_invoice field. Up to one group can be set to true, typically the group containing the primary recurring charges. If no group is set to true, an empty invoice is created and used as the latest invoice. |
Example output
Sample groupItems result
{
"groups": [
{
"items": [
{"key": "ubi_123456"}
],
"setsLatestInvoice": true
},
{
"items": [
{"key": "ubi_654321"},
{"key": "ubi_789012"}
],
"setsLatestInvoice": false
}
]
}
In this example:
- The licensed subscription item appears on the main invoice (which updates latest _ invoice on the subscription).
- The metered usage item and the proration credit are grouped together on a separate supplementary invoice.
Test your script
Before deploying to production:
- Test item handling scenarios in a sandbox .
- Verify your invoices are handling the items as expected.
Data validation requirements
Your script must meet these requirements to execute successfully. Violations result in a validation error.
- Return a result for every key in the input. Every item must have a corresponding entry in the output. This applies to both beforeItemCreation (return a CreationStrategy per key) and beforeInvoiceCreation (return an Assignment per key).
- CreationStrategy must be one of do _ not _ create or invoice .
- Assignment must be one of main _ invoice , supplementary _ invoice , or future _ invoice .
- Supplementary invoices are capped at 4.
Best Practices
- Handle all items : Process every item in the request and return a corresponding response.
- Set
setsLatestInvoiceon one group : When using groupItems , set one group as setsLatestInvoice: true so the subscription’s latest _ invoice field stays accurate. - Limit the number of groups : Remain below the max limit of 4 supplementary invoices.
- Test thoroughly : Use a sandbox to validate your logic before production deployment.
- Document your logic : Add comments explaining your business rules and decision-making process.
- Handle edge cases : Consider different scenarios that might produce invoices, like upgrades, downgrades, cancellations, or subscription schedule phases.
- Additionally:
- Check is _ proration before suppressing items: Suppressing non-proration items may cause unexpected billing behavior.
- Use product metadata to drive suppression logic rather than hardcoding price IDs.
- Use the same supplementary invoice key to group related items on the same invoice.
- Test with different subscription change scenarios (upgrades, downgrades, cancellations).
Next steps
- Learn more about scripts
- See the Stripe Billing Extensions repository on GitHub for additional examples
