# Version your custom objects

Publish new versions of your object definitions while existing integrations keep working against the version they were built for.

As your business changes, your object definitions change with them. You publish new versions of your custom object definitions, and existing integrations keep working against the version they were built for.

## How versioning works

Versions describe your schema, not your data:

- **One version for all definitions**: Your app’s custom object definitions are versioned together, with a single semantic version (major.minor.patch). Publishing a new version never changes existing records.
- **Records are stored one time, and each version is a view of them**: Stripe stores each record without a version. When you read or write a record, Stripe uses the fields that the requested version declares.
- **Reads**: Return the fields the requested version declares. A declared field without a value returns `null`, and a stored field that the version doesn’t declare is left out.
- **Writes**: Can include only the fields that the requested version declares, and updates are partial, so they change only the fields you include. A create or update that includes any other field fails, so a client on one version can’t change fields that only other versions declare.
- **Several versions in use at the same time**: Every published version stays readable and writable, so you can change your schema without upgrading every integration at the same time.

## Select a version

Record requests name the version to use in the `Stripe-Custom-Object-Versions` header. The value is the object’s plural API name, `@`, and a version:

```text
Stripe-Custom-Object-Versions: shipments@1.0.0
```

To name versions for several objects, separate them with semicolons, such as `shipments@1.0.0; packages@1.0.0`. The object in the request path must appear exactly once, and Stripe ignores entries for other objects, so you can send the same header value on every request. The header is required to create, retrieve, update, list, and search records. Deleting a record doesn’t need it. A request with a problem in the header returns one of these errors:

- **Missing header, or no entry for the object in the request path**: 400 with `version_required`.
- **A value that isn’t `<plural_name>@major.minor.patch`, or the same object listed twice**: 400 with `invalid_version`.
- **A version that doesn’t exist, or that doesn’t include the object**: 404 with `not_found`.

For example, version 0.0.1 defines a shipment object with `tracking_number` and an optional `legacy_id`. Version 1.0.0 removes `legacy_id` and adds an optional carrier field. A record created at 0.0.1 looks like this at each version:

```text
Stripe-Custom-Object-Versions: shipments@0.0.1
GET /v2/extend/objects/shipments/objrec_test_61RfVn0yAJiQAQHk7O16UQupaQVSQsnLcmkMmyS57ShP
-> { "fields": { "tracking_number": "1Z999AA10123456784", "legacy_id": "SHIP-42" } }

Stripe-Custom-Object-Versions: shipments@1.0.0
GET /v2/extend/objects/shipments/objrec_test_61RfVn0yAJiQAQHk7O16UQupaQVSQsnLcmkMmyS57ShP
-> { "fields": { "tracking_number": "1Z999AA10123456784", "carrier": null } }
```

Stripe still stores `legacy_id`, but the 1.0.0 view doesn’t include it. A 1.0.0 write that includes `legacy_id` fails:

```text
Stripe-Custom-Object-Versions: shipments@1.0.0
POST /v2/extend/objects/shipments/objrec_test_61RfVn0yAJiQAQHk7O16UQupaQVSQsnLcmkMmyS57ShP
{ "fields": { "legacy_id": "SHIP-43" } }
-> 400 invalid_fields: Property 'legacy_id' is not defined in the schema and the schema does not allow additional properties
```

## Publish a new version

Change your definitions and upload your app:

```bash
stripe apps upload
```

The upload compares your definitions with the version in your `stripe-app.yaml` file, which is your latest published version, and lists each change with its classification: a patch, minor, or major version change. It then publishes the next version and, after the upload succeeds, saves the new version number in `stripe-app.yaml`. If any change is restricted, the upload fails and lists the definitions with restricted changes. You can’t choose the version number yourself, and versions only move forward. You can’t publish a version lower than your latest one.

## Allowed and restricted schema changes 

These changes are allowed, with the version change each one causes:

- Add an optional field (minor).
- Remove an optional field (major). The stored values stay in the record, hidden from the new version, and you can’t reuse the field’s name later.
- Add or change a default value (minor), or remove a required field’s default value (major).
- Loosen validation other than maximum lengths and item counts, for example widen a numeric range or lower a minimum length (minor).
- Raise a maximum length or a maximum item count (major).
- Add an enum value (minor).
- Change a field’s or definition’s display name or description (patch).
- Add a definition (minor).
- Remove a definition (major). Earlier published versions keep the definition.

These changes are restricted:

- Remove an enum value.
- Add a required field. You can add required fields only in a definition’s first upload.
- Remove a required field, or switch a field between required and optional.
- Tighten validation, for example add a validator to a field that didn’t have one, narrow a numeric range, raise a minimum length, or lower a maximum length or item count.
- Change a field’s type, its reference target, or whether it’s an array.
- Reuse the name of a removed field.

Restricted changes remain restricted even if no records exist. To change an optional field’s type, add a new optional field, copy your data to it, and then remove the old field. You can change a field’s or definition’s display name without changing its API name. You can’t change a field’s type. If you change a field’s API name, the upload treats the change as removing the old field and adding a new one. Values stored under the old name don’t move to the new name, and you can’t use the old name again. If the old field is required, the upload fails, because you can’t remove a required field. Don’t change a definition’s API names. The upload treats a definition with new API names as a different definition.

## Versions in private preview

During private preview, versions work like this:

- Each upload that changes your definitions publishes a new version, and every published version stays available. You can’t unpublish, deprecate, or retire a version. To fix a version, publish a newer one.
- Removing a definition from a new version doesn’t delete it from earlier published versions. Integrations that use those versions can still access it.

## Versioning and your SDK

Generate the SDK from the definitions your integration uses. The examples in [Create and manage records](https://docs.stripe.com/custom-objects/records.md) include the `Stripe-Custom-Object-Versions` header in the request options, using the version in `stripe-app.yaml`. After you publish a new version, regenerate the SDK and update any explicit header values to match. There’s no option to generate an SDK for an earlier version. To get one, generate it from an earlier commit of your app.

## Versioning in other parts of Stripe

Other Stripe features work with versions in these ways:

- **Events**: Events don’t have a version. They don’t include the record’s data, so you retrieve the record at the version you want (see [React to custom object events](https://docs.stripe.com/custom-objects/events.md)).
- **Workflows**: All custom object steps and triggers from the same app in a workflow share one version of that app’s definitions. Stripe selects the latest version when you add the first step or trigger. They keep using that version after you publish a new one. To use a newer version, remove every custom object step and trigger from that app in the workflow, then add them again.
- **Dashboard**: The Dashboard always shows the latest version.
