# Control which items appear on subscription invoices

Use scripts to control which items appear on subscription invoices.

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 

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](https://docs.stripe.com/billing/scripts/stripe-authored.md#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](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin) in a [subset of TypeScript](https://docs.stripe.com/billing/scripts/script-language.md).

> #### 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* (Personally identifiable information (PII) is information that, when used alone or with other relevant data, can identify an individual. Examples include passport numbers, driver's license, mailing address, or credit card information)), or malicious code.

## Before you begin

Before creating your script:

- Understand [how subscriptions work](https://docs.stripe.com/billing/subscriptions/overview.md), including how they [create invoices](https://docs.stripe.com/billing/invoices/subscription.md), interact with pending [invoice items](https://docs.stripe.com/api/invoiceitems.md), and handle [prorations](https://docs.stripe.com/billing/subscriptions/prorations.md).
- Learn more about [scripts](https://docs.stripe.com/billing/scripts.md).

In addition the [global limitations for user-authored scripts](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin), 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:

```typescript
import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';

interface MyRecurringBillingItemHandlingConfig extends Record<string, unknown> {
  /** * @displayName Product metadata key */
  metadataKey: string;
  /** * @displayName Product metadata value */
  metadataValue: string;
}

export default class MyRecurringBillingItemHandling implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
  beforeItemCreation(
    input: Billing.RecurringBillingItemHandling.BeforeItemCreationInput,
    config: MyRecurringBillingItemHandlingConfig,
    _context: Context,
  ) {
    return {
      items: input.items.map((item) => {
        const product = item.priceKind === 'price' ? item.price.product : null;
        const metadataValue = product?.metadata[config.metadataKey];
        const shouldSuppress = item.isProration && metadataValue === config.metadataValue;

        return {
          key: item.key,
          creationStrategy: shouldSuppress ? 'doNotCreate' : 'invoice',
        };
      }),
    };
  }
}
```

#### 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:

```typescript
import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';

interface MyRecurringBillingItemHandlingConfig extends Record<string, unknown> {}

export default class MyRecurringBillingItemHandling implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
  filterItems(
    input: Billing.RecurringBillingItemHandling.FilterItemsInput,
    _config: MyRecurringBillingItemHandlingConfig,
    _context: Context,
  ) {
    return {
      items: input.items
        .filter((item) => {
          const isRecurring =
            item.priceKind === 'price'
              ? item.price.recurring != null
              : item.priceKind === 'rateCardRate'
                ? true
                : item.priceKind === 'licenseFee';

          return isRecurring;
        })
        .map((item) => ({key: item.key})),
    };
  }
}
```

#### 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:

```typescript
import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';

interface MyRecurringBillingItemHandlingConfig extends Record<string, unknown> {}

function isMeteredPriceUnion(item: Billing.RecurringBillingItemHandling.Item): boolean {
  switch (item.priceKind) {
    case 'rateCardRate':
    case 'customPricingUnitOverageRate':
      return true;
    case 'price':
      return item.price.recurring?.usageType === 'metered';
    case 'licenseFee':
      return false;
    default:
      return false;
  }
}

export default class MyRecurringBillingItemHandling implements Billing.RecurringBillingItemHandling<MyRecurringBillingItemHandlingConfig> {
  groupItems(
    input: Billing.RecurringBillingItemHandling.GroupItemsInput,
    _config: MyRecurringBillingItemHandlingConfig,
    _context: Context,
  ) {
    const licensedItems = [];
    const meteredItems = [];

    for (const item of input.items) {
      const isMetered = isMeteredPriceUnion(item);

      if (isMetered) {
        meteredItems.push({key: item.key});
      } else {
        licensedItems.push({key: item.key});
      }
    }

    const groups = [];
    if (licensedItems.length > 0) {
      groups.push({
        items: licensedItems,
        setsLatestInvoice: true,
      });
    }
    if (meteredItems.length > 0) {
      groups.push({
        items: meteredItems,
        setsLatestInvoice: licensedItems.length === 0,
      });
    }

    return {groups};
  }
}
```

#### 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 `LICENSED` and `METERED` items.

### SDK reference

For complete TypeScript type definitions and interfaces, see the [Extensibility SDK on GitHub :external: link-to-extensibility-sdk](https://github.com/stripe/extensibility-sdk/tree/master/libs/extensibility-sdk/src/extensions/billing/recurring_billing_item_handling.ts).

### Extension method

The item handling extension consists of three separate methods that you can implement: [beforeItemCreation](https://docs.stripe.com/billing/scripts/item-handling.md#beforeitemcreation), [filterItems](https://docs.stripe.com/billing/scripts/item-handling.md#filteritems), and [groupItems](https://docs.stripe.com/billing/scripts/item-handling.md#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.

```typescript
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 

```typescript
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 

```json
{
  "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 

```typescript
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 

```json
{
  "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.

```typescript
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 

```typescript
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 

```json
{
  "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 

```typescript
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 

```json
{
  "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.

```typescript
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 

```typescript
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 

```json
{
  "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 

```typescript
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 

```json
{
  "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 in [test mode](https://docs.stripe.com/keys.md#test-live-modes) with various item handling scenarios.
1. 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 test mode 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](https://docs.stripe.com/billing/scripts.md)
- See the [script language](https://docs.stripe.com/billing/scripts/script-language.md)
