# Get started with custom objects

Define your first custom object, upload it in a Stripe app, and create records with the API and a generated SDK.

In this guide, you define a support ticket object, upload it in a Stripe app, and create records with the API and with a generated Node.js SDK.

## Before you begin

To follow this guide, you need:

- Node.js 22 or later and pnpm 10.
- The [Stripe CLI](https://docs.stripe.com/stripe-cli.md) 1.52.1 or later, with the apps plugin 1.22.0 or later and the generate plugin 0.12.0 or later.
- A Stripe account with access to the custom objects private preview, and a [sandbox](https://docs.stripe.com/sandboxes.md) to build in.
- Your sandbox secret key in the `STRIPE_SECRET_KEY` environment variable. Use a restricted API key with `Write` access to `Object Records`, under **Extend** in the Dashboard, for record requests. The examples read the key from the environment, so you don’t put the key in your code. See [best practices for managing secret keys](https://docs.stripe.com/keys-best-practices.md).
- API version `2026-10-28.preview` or later. The Stripe CLI and the generated SDK don’t use this version by default, so set it on every request. Set it in the `Stripe-Version` header for API calls, with the `--stripe-version` flag for the Stripe CLI, and with `apiVersion` when you create an SDK client.

## Install the CLI plugins

Install the apps and generate plugins for the Stripe CLI:

```bash
stripe plugin install apps
stripe plugin install generate
```

If you already have them, upgrade them with `stripe plugin upgrade apps` and `stripe plugin upgrade generate`.

Sign in to the sandbox where you want to upload your private app:

```bash
stripe login
```

Choose the same sandbox that your `STRIPE_SECRET_KEY` belongs to. The CLI uploads your definitions to the account or sandbox you’re signed in to.

## Create a Stripe app

Create an app project:

```bash
stripe generate app support-desk --id com.example.support-desk --display-name "Support desk"
```

This creates a project with a `custom-objects/` folder for your definitions, a `ui/` folder for Dashboard UI, and a `stripe-app.yaml` file, which is your [app manifest](https://docs.stripe.com/stripe-apps/reference/app-manifest.md). Choose the app ID carefully. After you upload the app, you can’t change it.

## Define a custom object

Add a custom object to your app:

```bash
cd support-desk
stripe generate custom-object SupportTicket
```

This creates `custom-objects/src/support_ticket.object.ts` with a starter definition, adds a starter test file next to it, and adds the object to `stripe-app.yaml`. Replace the starter fields with your own:

```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 {
  /**
   * @displayName Subject
   * @primaryField
   */
  subject: string;

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

  /** @displayName Priority */
  priority: Integer;

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

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

Every definition needs exactly one `@primaryField`. For all field types and validators, see [Define custom objects](https://docs.stripe.com/custom-objects/define.md).

## Upload the app

To upload your app:

```bash
stripe apps upload
```

The command builds your app, checks your definitions, and uploads them. Stripe registers your definition, creates its API endpoints, and adds it to the Dashboard. Your first upload publishes version 0.0.1 of your custom object definitions and saves that version in `stripe-app.yaml`. Upload processing continues after the command finishes. If you don’t see your definition, or a record request returns `not_found`, wait a few minutes and try again. To have the command wait until processing finishes, add `--wait`.

## Check the definition

List the object definitions in your account:

```bash
stripe get /v2/extend/object_definitions --stripe-version 2026-10-28.preview
```

The Stripe CLI’s default API version doesn’t include custom objects endpoints, so include `--stripe-version`. The response lists the object definitions in your account, with each definition’s API names and latest version.

## Create a record

Requests that create, retrieve, update, list, or search records name the version of your custom object definitions to use, in the `Stripe-Custom-Object-Versions` header:

```bash
curl https://api.stripe.com/v2/extend/objects/support_tickets \
  -H "Authorization: Bearer $STRIPE_SECRET_KEY" \
  -H "Stripe-Version: 2026-10-28.preview" \
  -H "Stripe-Custom-Object-Versions: support_tickets@0.0.1" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "subject": "Order arrived damaged",
      "status": "open",
      "priority": 3,
      "customer": {"type": "customer", "id": "cus_abc123"}
    }
  }'
```

Replace `cus_abc123` with the ID of a customer in your sandbox. The response includes the new record and its id, which starts with `objrec_`.

## Generate and use an SDK

Generate a typed Node.js SDK from your app’s definitions:

```bash
stripe generate custom-objects-sdk --lang node
```

Then create a script in your app, such as `scripts/demo.mjs`, that creates a record:

```javascript
import SupportDesk from '../generated-sdk/node/cjs/stripe.cjs.node.js';

const client = new SupportDesk(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2026-10-28.preview',
  maxNetworkRetries: 0,
});
const versions = {
  headers: { 'Stripe-Custom-Object-Versions': 'support_tickets@0.0.1' },
};

const ticket = await client.v2.extend.objects.supportTickets.create(
  {
    fields: {
      subject: 'Order arrived damaged',
      status: 'open',
      priority: 3,
      customer: { type: 'customer', id: 'cus_abc123' },
    },
  },
  versions,
);
console.log(ticket.id);
```

The generated SDK retries some failed requests automatically. Creating a record doesn’t support idempotency keys, so a retry can create a duplicate record. This example sets `maxNetworkRetries: 0` to disable configurable retries, but the SDK can still retry one time after certain closed-connection errors. Store your own identifier in a field to reconcile an uncertain result. See [Private-preview limitations](https://docs.stripe.com/custom-objects/limitations.md).

Replace `cus_abc123` with the ID of a customer in your sandbox, create the `scripts` directory if it doesn’t exist, and run `node scripts/demo.mjs`. The example includes the `Stripe-Custom-Object-Versions` header in the request options. Object names with more than one word use camelCase in the SDK (`supportTickets`).

## View your records in the Dashboard

In the Dashboard, go to **More** > **Custom objects** to see your definition and its records. The Dashboard is read-only during the private preview. You create, update, and delete records with the API or your generated SDK.

## Next steps

- [Create and manage records](https://docs.stripe.com/custom-objects/records.md)
- [Version your custom objects](https://docs.stripe.com/custom-objects/versioning.md)
- [Use custom objects with Stripe Apps and Workflows](https://docs.stripe.com/custom-objects/apps-and-workflows.md)
