npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stripe/extensibility-sdk

v1.1.0

Published

Stripe Apps Extensibility SDK - API objects, standard library, and ESLint rules

Readme

@stripe/extensibility-sdk

Runtime SDK for Stripe script extensions. Provides TypeScript extension interfaces, a Stripe-safe arbitrary-precision Decimal type, scalar primitives, and the wire-format transformation framework used by the platform dispatcher.


Subpath exports

| Import path | Use for | | ----------------------------------------- | ------------------------------------------------ | | @stripe/extensibility-sdk | Decimal, scalar types, Ref, wire errors | | @stripe/extensibility-sdk/extensions | Implementing extension interfaces | | @stripe/extensibility-sdk/internal | Internal registry metadata (used by build tools) | | @stripe/extensibility-sdk/jsonschema | JSON Schema types and helpers | | @stripe/extensibility-sdk/config-values | Configuration value types and helpers |


Extension interfaces

Each extension interface is a TypeScript interface your default-export class must implement. The Config type parameter is the shape of your extension's configuration object.

Billing

Billing.Bill.DiscountCalculation

Computes discounts applied to a bill.

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

interface MyConfig extends Record<string, unknown> {
  maxDiscountPercent: number;
}

export default class MyExtension implements Billing.Bill.DiscountCalculation<MyConfig> {
  computeDiscounts(
    request: Billing.Bill.DiscountCalculation.DiscountableItem,
    config: MyConfig,
    context: Context
  ): Billing.Bill.DiscountCalculation.DiscountResult {
    const { grossAmount } = request;
    const discountAmount = grossAmount.amount
      .mul(config.maxDiscountPercent)
      .div(100, 8, 'half-even');
    return {
      discount: {
        amount: { amount: discountAmount, currency: grossAmount.currency },
      },
    };
  }
}

Key types in the Billing.Bill.DiscountCalculation namespace:

| Type | Description | | ------------------ | ------------------------------------------------------------------------- | | DiscountableItem | Request: line items, gross amount, customer, billing reason, subscription | | DiscountResult | Response: discount with MonetaryAmount | | LineItem | A single line with subtotal, quantity, price, period | | Price | Price with scheme, tiers, recurring config | | BillingReason | Enum: 'subscription_cycle', 'manual', etc. | | AnyTimeRange | Discriminated union: oneTime | timeRange | | MonetaryAmount | { amount: Decimal; currency: Currency } |


Billing.CustomerBalanceApplication

Computes how much of a customer's credit balance to apply to a bill.

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

export default class MyExtension implements Billing.CustomerBalanceApplication<
  Record<string, unknown>
> {
  computeAppliedCustomerBalance(
    request: Billing.CustomerBalanceApplication.CustomerBalanceApplicationInput,
    _config: Record<string, unknown>,
    _context: Context
  ): Billing.CustomerBalanceApplication.CustomerBalanceApplicationResult {
    // Apply at most 50% of the bill total from the customer balance
    const halfTotal = request.totalAmount.amount.div(2, 8, 'half-even');
    const applied = request.customerBalance.amount.lt(halfTotal)
      ? request.customerBalance.amount
      : halfTotal;
    return {
      appliedCustomerBalance: { amount: applied, currency: request.totalAmount.currency },
    };
  }
}

Key types:

| Type | Description | | ---------------------------------- | ------------------------------------------------------------------ | | CustomerBalanceApplicationInput | { totalAmount: MonetaryAmount; customerBalance: MonetaryAmount } | | CustomerBalanceApplicationResult | { appliedCustomerBalance: MonetaryAmount } |


Billing.InvoiceCollectionSetting

Overrides collection settings (payment method, auto-advance, etc.) for an invoice.

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

export default class MyExtension implements Billing.InvoiceCollectionSetting<
  Record<string, unknown>
> {
  collectionOverride(
    request: Billing.InvoiceCollectionSetting.InvoiceCollectionRequest,
    _config: Record<string, unknown>,
    _context: Context
  ): Billing.InvoiceCollectionSetting.InvoiceCollectionResponse {
    const isSubscription = request.parent.type === 'subscription';
    return {
      autoAdvance: isSubscription,
    };
  }
}

Key types:

| Type | Description | | --------------------------- | ------------------------------------------------------------------------------------------------------------------- | | InvoiceCollectionRequest | Request: collection settings, parent, customer | | InvoiceCollectionResponse | Response: optional autoAdvance override | | CollectionSettings | Current settings with autoAdvance, collectionMethod, paymentMethods | | ParentType | 'subscription' | 'contract' | 'quote' | 'billing_cadence' | 'subscription_schedule' | 'standalone' | | CollectionMethod | 'charge_automatically' | 'send_invoice' | | PaymentMethodType | 'card' | 'sepa_debit' | … |


Billing.Prorations

Computes proration adjustments when subscription items change mid-period.

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

export default class MyExtension implements Billing.Prorations<Record<string, unknown>> {
  prorateItems(
    request: Billing.Prorations.ProrateItemsInput,
    _config: Record<string, unknown>,
    _context: Context
  ): Billing.Prorations.ProrateItemsResult {
    return {
      items: request.items.map((item) => ({
        key: item.key,
        prorationFactor: item.currentProrationFactor,
        lineItemPeriod: item.servicePeriod,
      })),
    };
  }
}

Key types:

| Type | Description | | -------------------- | ----------------------------------------------------------------- | | ProrateItemsInput | { items: ProratableItem[] } | | ProrateItemsResult | { items: ProratedItem[] } | | ProratableItem | Line item with key, type, price, proration factor, service period | | ProratedItem | Output: key, proration factor, line item period | | ItemType | 'credit' | 'debit' |


Core

Core.Workflows.CustomAction

Implements a custom action callable from Stripe workflows. execute is required; getFormState is optional and drives dynamic form rendering.

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

interface MyConfig extends Record<string, unknown> {
  webhookEndpoint: string;
  webhookPath: string;
}

export default class MyExtension implements Core.Workflows.CustomAction<MyConfig> {
  async execute(
    request: Core.Workflows.CustomAction.ExecuteCustomActionRequest,
    config: MyConfig,
    _context: Context
  ): Promise<Core.Workflows.CustomAction.ExecuteCustomActionResponse> {
    const { customInput } = request;

    await endpointFetch({
      endpoint: config.webhookEndpoint,
      path: config.webhookPath,
      method: 'POST',
      body: JSON.stringify(customInput),
    });

    return {};
  }

  async getFormState(
    request: Core.Workflows.CustomAction.GetFormStateRequest,
    _config: MyConfig,
    _context: Context
  ): Promise<Core.Workflows.CustomAction.GetFormStateResponse> {
    return { values: request.values, config: {} };
  }
}

Key types:

| Type | Description | | ----------------------------- | ---------------------------------------------------------------------- | | ExecuteCustomActionRequest | { customInput: Record<string, unknown> } | | ExecuteCustomActionResponse | Record<string, never> (empty object) | | GetFormStateRequest | { values: Record<string, unknown>; changedField?: string } | | GetFormStateResponse | { values: Record<string, unknown>; config: Record<string, unknown> } |

Both execute and getFormState may return synchronously or as Promise.


Context

Every extension method receives a Context as its third argument:

import type { Context } from '@stripe/extensibility-sdk/extensions';

function handleContext(context: Context) {
  console.log(context.type); // e.g. "core.workflows.custom_workflow_action"
  console.log(context.id); // unique call ID, for debugging
  console.log(context.livemode);
  console.log(context.stripeContext); // set for cross-account calls
  console.log(context.clockTime); // optional SDK-provided clock timestamp
}

Stdlib

Decimal

Arbitrary-precision decimal arithmetic backed by big.js. All monetary values in extension interface requests and responses use Decimal.

import { Decimal } from '@stripe/extensibility-sdk';

const a = Decimal.from('10.50');
const b = Decimal.from(3);

a.add(b); // Decimal('13.50')
a.sub(b); // Decimal('7.50')
a.mul(b); // Decimal('31.50')
a.div(b, 8, 'half-even'); // Decimal('3.50000000')

a.round({ precision: 2, direction: 'half-even' }); // Decimal('10.50')
a.round('penny'); // Decimal('10.50') — preset shorthand

a.eq(Decimal.from('10.50')); // true
a.lt(b); // false
a.lte(b); // false
a.gt(b); // true
a.gte(b); // true

a.toString(); // '10.50'
a.toFixed(4, 'half-even'); // '10.5000'

Rounding directions: 'ceil' · 'floor' · 'round-down' · 'round-up' · 'half-up' · 'half-down' · 'half-even'

Rounding presets: 'penny' (2 dp, half-even) · 'dollar' (0 dp, half-even)

div() and toFixed() always require an explicit precision and rounding direction — there are no implicit defaults to prevent silent precision loss.


Scalar types

| Type | Description | Constructor | | ----------------- | -------------------------- | ------------------------------------ | | Integer | Whole number (no decimals) | Integer.from(n, direction) | | PositiveInteger | Non-negative integer (≥ 0) | PositiveInteger.from(n, direction) |

These are branded types — they carry a compile-time brand so TypeScript rejects raw number or string where the stricter type is required.

import { Integer, PositiveInteger } from '@stripe/extensibility-sdk';

const count = Integer.from(42, 'round-down'); // Integer
const qty = PositiveInteger.from(3, 'round-down'); // PositiveInteger

Ref

A typed reference to a Stripe object (ID + optional resolved value).

import type { Ref } from '@stripe/extensibility-sdk';
import type { Customer } from '@stripe/extensibility-api-objects';

type CustomerRef = Ref<Customer>;

Wire errors

Thrown by the platform dispatch wrapper when wire-format data is invalid.

| Class | When thrown | | ---------------- | --------------------------------------------------------- | | WireReadError | Invalid or missing field in the incoming wire request | | WireWriteError | Invalid or missing field in the outgoing SDK response |

import { WireReadError, WireWriteError } from '@stripe/extensibility-sdk';

In production, the platform catches and surfaces these as structured errors. In tests, assert against them directly by invoking the generated interface-specific $platformWrap... helper for the interface under test. For example:

// Pseudocode: replace `$platformWrap...` with the generated wrapper for your interface.
expect(() => $platformWrap...(MyImpl, badInput, {}, ctx)).toThrow(WireReadError);

License

MIT