# Define custom objects

Describe your objects in TypeScript, with typed fields, validation, and references to Stripe objects.

An object definition is the schema for one type of custom object—its fields, their types and validation, and how the object displays in the Dashboard. You write definitions in TypeScript and package them in a [Stripe app](https://docs.stripe.com/stripe-apps.md). This page covers the field types and the annotations you use to describe them.

## Field types

Custom objects support these field types. You declare each field’s type in TypeScript, and Stripe checks values against it when you create or update a record.

- **String** (`string`): Text.
- **Integer** (`Integer`): A whole number from -1,000,000 to 1,000,000, sent as a JSON number.
- **Decimal** (`Decimal`): An exact decimal number with up to 12 digits after the decimal point, sent as a string, such as `"12.50"`. In TypeScript, create and combine decimal values with `Decimal` methods, such as `Decimal.from('12.50')` and `add`, instead of JavaScript numbers.
- **Boolean** (`boolean`): `true` or `false`.
- **Enum** (a union of string literals, such as `'open' | 'resolved'`): One value from a fixed set. Each value starts with a letter, contains only letters, digits, and underscores, and has 2 to 64 characters. A field can have up to 100 values, and values are case-sensitive.
- **Datetime** (`Date`): A point in time, sent as an ISO 8601 string in UTC with exactly three digits of fractional seconds, such as `2026-10-30T12:00:00.000Z`.
- **Object reference** (`Ref<T>`): A reference to a Stripe object or to another custom object.
- **Currency** (`Currency`): A lowercase [currency](https://docs.stripe.com/currencies.md) code, such as `usd`.
- **Monetary amount** (`MonetaryAmount`): An amount in the currency’s minor unit together with its currency.
- **Array** (`T[]` or `Array<T>`): An ordered list of values of one type, with up to 100 items.

Import `Integer`, `Decimal`, `Currency`, `MonetaryAmount`, and `Ref` from `@stripe/extensibility-sdk`. The `string`, `boolean`, and `Date` types don’t need an import. There’s no general `number` type, so use `Integer` or `Decimal`. Fields are flat, so to relate objects, use references instead of nested objects.

## Annotations

Add TSDoc annotations to describe how a field behaves and appears:

- `@primaryField`: Required on exactly one field. It’s the record’s main display field: the first column in the Dashboard list and the header in the detail view. The build fails if no field, or more than one field, has `@primaryField`.
- `@secondaryField`: Optional, at most one per definition. The secondary display field, which is the second column in the Dashboard list and the subheader in the detail view.
- `@displayName`: The field name shown in the Dashboard.
- `@apiName`: Overrides the `snake_case` API name derived from the field name.
- `@defaultValue`: A value Stripe applies when you create a record without the field. Defaults apply only when a record is created. Write the value as JSON, such as `@defaultValue` false.

To describe a field, write the description as the first line of the field’s comment, before any tags. For example, the comment in the complete definition starts with “Short description of the issue.” The build rejects a `@description` tag. You can’t use `@primaryField` or `@secondaryField` on an array field.

## Validators

Constrain values with validator annotations. Stripe checks them when you create or update a record.

- **Integer and decimal fields**: `@minimum`, `@maximum`, `@exclusiveMinimum`, `@exclusiveMaximum`
- **String fields**: `@minLength`, `@maxLength`
- **Array fields**: `@minItems`, `@maxItems`, `@uniqueItems`
- **Monetary amount fields**: `@minimum` :amount, `@maximum` :amount, `@exclusiveMinimum` :amount, and `@exclusiveMaximum` :amount, followed by a value in the currency’s minor unit, such as `@minimum` :amount 0

`Integer` validator values and integer defaults must be between -1,000,000 and 1,000,000, and an upload fails if one is outside this range. `@minLength` and `@maxLength` values must be between 0 and 1,000,000, and `@minItems` and `@maxItems` values must be between 0 and 100. String fields support length validators only. Regular expression patterns (`@pattern`) aren’t enforced.

### Validators, defaults, and new versions

You can upload new versions of definitions that use validators and defaults. Changes to those validators and defaults must follow the [schema change rules](https://docs.stripe.com/custom-objects/versioning.md#allowed-and-blocked-schema-changes). For example, loosening a numeric range is allowed, but tightening it is restricted.

## A complete definition

This example shows a definition’s first upload. Adding a validator to a field you already uploaded, such as adding `@minimum` to an existing priority field, is a restricted change. See [Version your custom objects](https://docs.stripe.com/custom-objects/versioning.md).

```typescript
import { BaseObject, CustomObject } from '@stripe/extensibility-custom-objects';
import type { Customer } from '@stripe/extensibility-api-objects';
import type { Integer, Ref } from '@stripe/extensibility-sdk';

export interface SupportTicketFields {
  /**
   * Short description of the issue.
   * @displayName Subject
   * @primaryField
   */
  subject: string;

  /** @displayName Status */
  status: 'open' | 'in_progress' | 'resolved';

  /**
   * @displayName Priority
   * @minimum 1
   * @maximum 5
   */
  priority: Integer;

  /** @displayName Customer */
  customer: Ref<Customer>;

  /**
   * @displayName Tags
   * @maxItems 10
   * @uniqueItems
   */
  tags?: string[];
}

@CustomObject
export class SupportTicket extends BaseObject<SupportTicketFields> {}
```

## Names

You write field names in camelCase, and the API uses them in `snake_case`, so `filedAt` becomes `filed_at`. To set a different API name, use `@apiName`. Object and field API names, such as `support_ticket`, must:

- Have at most 40 characters for object API names, or 64 characters for field API names.
- Use only lowercase letters, digits, and underscores.
- Start with a lowercase letter and end with a letter or digit, without two underscores in a row (pattern `[a-z](_?[a-z0-9]+)*`).
- Not be a reserved word in Dart, Elixir, Go, Java, JavaScript, .NET, PHP, Python, Ruby, or Rust, such as `class` or `box`.
- Not be a name that the Stripe SDKs use, such as `object_id`, `save`, or `values`.

A name that contains a reserved word, such as `class_name`, is allowed.

Object display names can have up to 64 characters and can contain any characters. For all naming limits, see [Limits and quotas](https://docs.stripe.com/custom-objects/limitations.md#limits-and-quotas).

## References

A reference field points to another object:

- `Ref<Customer>`: A Stripe object. Import Stripe object types, such as Customer, from `@stripe/extensibility-api-objects`.
- `Ref<Package>`: Another custom object in the same app.

Each reference field points to one object type. Send a reference to a Stripe object as `{ "type": "customer", "id": "cus_abc123" }`, and a reference to a custom object as `{ "type": "v2.extend.objects.package", "id": "objrec_test_61RfVn0yAJiQAQHk7O16UQupaQVSQsnLcmkMmyS57PkG" }`. Stripe returns each reference with a `url` that you can use to retrieve the referenced object. Stripe doesn’t check that the referenced object exists, and deleting a referenced object doesn’t change the records that reference it. You can reference most Stripe objects, including the following:

- [Customer](https://docs.stripe.com/api/customers/object.md)
- [Subscription](https://docs.stripe.com/api/subscriptions/object.md)
- [Subscription Schedule](https://docs.stripe.com/api/subscription_schedules/object.md)
- [Invoice](https://docs.stripe.com/api/invoices/object.md)
- [Invoice Item](https://docs.stripe.com/api/invoiceitems/object.md)
- [Credit Note](https://docs.stripe.com/api/credit_notes/object.md)
- [PaymentIntent](https://docs.stripe.com/api/payment_intents/object.md)
- [SetupIntent](https://docs.stripe.com/api/setup_intents/object.md)
- [Charge](https://docs.stripe.com/api/charges/object.md)
- [Dispute](https://docs.stripe.com/api/disputes/object.md)
- [Payout](https://docs.stripe.com/api/payouts/object.md)
- [Transfer](https://docs.stripe.com/api/transfers/object.md)
- [Product](https://docs.stripe.com/api/products/object.md)
- [Price](https://docs.stripe.com/api/prices/object.md)
- [Coupon](https://docs.stripe.com/api/coupons/object.md)
- [Promotion Code](https://docs.stripe.com/api/promotion_codes/object.md)
- [Quote](https://docs.stripe.com/api/quotes/object.md)
- [Tax Rate](https://docs.stripe.com/api/tax_rates/object.md)
- [Checkout Session](https://docs.stripe.com/api/checkout/sessions/object.md)
- [Terminal Reader](https://docs.stripe.com/api/terminal/readers/object.md)
- [Terminal Location](https://docs.stripe.com/api/terminal/locations/object.md)
- [Account](https://docs.stripe.com/api/accounts/object.md)

You can also reference many v2 objects. You can’t reference the following Stripe objects:

- [Refund](https://docs.stripe.com/api/refunds/object.md)
- [PaymentMethod](https://docs.stripe.com/api/payment_methods/object.md)
- [Person](https://docs.stripe.com/api/persons/object.md)
- [Tax ID](https://docs.stripe.com/api/tax_ids/object.md)
- [Customer Balance Transaction](https://docs.stripe.com/api/customer_balance_transactions/object.md)
- [Transfer Reversal](https://docs.stripe.com/api/transfer_reversals/object.md)

## Monetary amounts and arrays

A monetary amount stores a value and its currency in one field: an amount, as a string in the currency’s minor unit, and a lowercase currency code. For example, `{ "amount": "10000", "currency": "usd" }` is 100.00 USD. Set a default with `@defaultValue` `{ "amount": "10000", "currency": "usd" }`, and bound the amount with validators such as `@minimum` :amount 0 and `@maximum` :amount 100000. An array holds an ordered list whose items share one type, such as `string[]` or `Array<Ref<Package>>`. Item-level validators, such as `@maxLength` on a `string[]` field, apply to each item. Array-level validators (`@minItems`, `@maxItems`, and `@uniqueItems`) apply to the whole list. An optional array field without a value returns `null`. When you update an array field, the value you send replaces the whole array.

## Data you store

Don’t store sensitive information, such as payment card details, bank account details, government-issued ID numbers, passwords, or API keys, in custom objects. Stripe doesn’t inspect what you store in custom objects, and you’re responsible for the data you store, including having a lawful basis to process it. Stripe indexes the values of every field so you can search and filter records.

## Build and upload

Upload your app to register or change your definitions.

```bash
stripe apps upload
```

Each upload that changes your definitions publishes a new version. Stripe classifies each change as a patch, minor, or major version change, and blocks changes that aren’t allowed, such as changing a field’s type. Major changes, such as removing an optional field, can affect integrations that use that field, so existing integrations keep using the version they were built for until you update them. See [Version your custom objects](https://docs.stripe.com/custom-objects/versioning.md).
