> Documentation index: https://docs.beetl.io/llms.txt

# The contract rule

> What is fixed once a connection type is installed, and how to ship a change as a new connection type.

Source: https://docs.beetl.io/connect/schema-change-contract/
Last updated: 2026-11-10

When a TenantAdmin installs a package, its manifest becomes the connection type's contract.
Connections, saved credentials and syncs are created against that contract, and each sync's dataset
takes its schema from it. An installed connection type stays as it is: to ship any change to a
package, install the new version as a new connection type and move your connections and syncs to it.

## What the contract covers

The manifest is generated from your `defineIntegration` call when the package is built.
The local `configure` and `sync` commands build it too; `pack` only creates the source archive. These are
the parts that connections, syncs and datasets depend on:

| Part                | Where it comes from                                                                                                                       |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Integration key     | `key`                                                                                                                                     |
| Provider origin     | `connection.origin`                                                                                                                       |
| Authentication      | `connection.auth`: the auth type, its options (such as OAuth endpoints and scopes, or a token exchange request) and its credential fields |
| Connection settings | `connection.inputs`                                                                                                                       |
| Verification        | Whether `connection.verify` is defined                                                                                                    |
| The list of syncs   | The keys under `syncs`                                                                                                                    |
| Sync settings       | Each sync's `inputs`                                                                                                                      |
| Record schema       | Each sync's `records`                                                                                                                     |
| Checkpoint schema   | Each sync's `checkpoint`                                                                                                                  |

Schemas are recorded as the JSON Schema the SDK generates from your Zod schemas, so field labels,
descriptions and defaults set with `.meta()` or `.default()` are part of the contract too.

A sync's write mode is not part of the contract. The person who adds a sync chooses Append or
Replace, and can change it later. See [Write modes](https://docs.beetl.io/connect/write-modes).

> **Decide the contract early.**
> Every change, including loosening a schema, means a new connection type and new datasets. Mark
> fields the provider may omit as `.optional()` or `.nullable()` from the start, and use
> `storageRecord` for objects whose keys vary. See [SDK reference](https://docs.beetl.io/connect/sdk-reference#helpers).
> Test against real accounts with the [local CLI](https://docs.beetl.io/connect/local-development) before you install.

## Ship a change

A new version is installed next to the old one, and connections and syncs move across.

1. Give the package a `displayName` that tells the two types apart, such as `Example CRM (v2)`, then
   pack it with `beetl-connect pack`.
2. Install it under Settings → Tenant → Custom connection types with **Add custom connection type**.
   See [Pack and upload](https://docs.beetl.io/connect/distribution).
3. Create connections of the new type. Saved credentials belong to one connection type, so enter
   them again.
4. Add the syncs. Each writes to a new dataset, because an output link only attaches to a dataset
   that has never been written.
5. Run the new syncs, compare their datasets with the old ones, then point your pipelines at the new
   datasets.
6. Delete the old syncs and connections, and the saved credentials of the old type. Then delete the
   old connection type, which Beetl refuses while connections or saved credentials still use it.
   Deleting never deletes the old datasets.

A new sync starts without a checkpoint, so its first run reads from the beginning. Make sure that
first run fits the [per-run limits](https://docs.beetl.io/connect/write-modes#per-run-limits).

Packages built with an SDK older than 0.5 are not accepted at all and must be rebuilt, which also
means installing them as a new connection type. See
[Rebuilding older packages](https://docs.beetl.io/connect/distribution#rebuilding-older-packages).

## Schema checks on every run

The record schema is also enforced at run time. The SDK parses each record your sync yields with
its `records` schema, and Beetl checks it again against the installed contract before staging it.
A record that does not match stops the run: nothing is written and the checkpoint does not advance.

To find the record and field that failed, run the sync locally with
`npx beetl-connect sync . <sync>`. The error names the record's position in its batch and the Zod
validation message. See [Local development](https://docs.beetl.io/connect/local-development).

## Related

- [Connection types, connections and syncs](https://docs.beetl.io/concepts/connections-and-integrations): How installed packages, connections, syncs and datasets relate.

- [Write modes](https://docs.beetl.io/connect/write-modes): Append and Replace, and what each run does to the dataset.
