Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Control which items appear on subscription invoices


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:

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
FieldTypeDescription
inputBeforeItemCreationInputThe items that are about to be created.
configMyRecurringBillingItemHandlingConfigYour custom configuration object, which contains custom parameters to modify the behavior of your script.
contextContextRuntime context including the extension identifier, livemode status, and optional Stripe-specific context values.
Returns
FieldDescription
BeforeItemCreationResultYour 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

FieldTypeDescription
itemsArray<Item>The list of items that are about to be created.
keystringUnique identifier for the item.
typeItemTypeEither credit or debit.
priceKind'price' | 'rateCardRate' | 'licenseFee' | 'customPricingUnitOverageRate' | 'other'The discriminant that tells you which pricing payload is available on the item.
isProrationbooleanWhether the item was generated from a proration event.
servicePeriodAnyTimeRangeThe time period this item covers.
prorationFactorDecimalThe 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

FieldTypeDescription
itemsArray<ItemWithCreationStrategy>Your decision for each item.
keystringMust 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
FieldTypeDescription
inputFilterItemsInputThe items that are candidates for inclusion on the invoice.
configMyRecurringBillingItemHandlingConfigYour custom configuration object, which contains custom parameters to modify the behavior of your script.
contextContextRuntime context including the extension identifier, livemode status, and optional Stripe-specific context values.
Returns
FieldDescription
FilterItemsResultThe 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

FieldTypeDescription
itemsArray<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

FieldTypeDescription
itemsArray<ItemToInvoice>The items to include on the invoice.
keystringMust 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
FieldTypeDescription
inputGroupItemsInputThe items that are about to be placed on invoices.
configMyRecurringBillingItemHandlingConfigYour custom configuration object, which contains custom parameters to modify the behavior of your script.
contextContextRuntime context including the extension identifier, livemode status, and optional Stripe-specific context values.
Returns
FieldDescription
GroupItemsResultThe 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

FieldTypeDescription
itemsArray<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

FieldTypeDescription
groupsArray<ItemGroup>The groups of items. Each group becomes a separate invoice.
itemsArray<GroupedItem>The items in this group.
keystringMust match a key from an input item.
setsLatestInvoicebooleanWhether 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:

  1. Test item handling scenarios in a sandbox .
  2. 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 setsLatestInvoice on 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
Last verified 2026-09-24

Is this helpful?