Skip to content

Schema Breaking Changes Guide

This guide explains the implications of modifying data object properties in connectors that have dependent integrations. Use it alongside the Object Schema guide and the Best Practices criteria before making any property changes to a connector that is in use.

What Makes a Change Breaking

A breaking schema change is any modification to a connector's data object or action definitions that causes:

  • JSON schema validation failures on ingress: when a record is written to the platform and fails validation because the schema is now more restrictive or incompatible with the data being written.
  • Change detection false positives: when the SDK's hash-based change detector produces incorrect results due to property additions, removals, or type changes, causing all or many records to appear "Updated" when nothing may have actually changed in the source system.
  • Dependent integrations (flows) to encounter errors: when flows are built to depend on a specific contract shape, such as accessing a removed or renamed property via a lookup step.

The Xchange platform validates records against the data object's JSON schema only on ingress — when a record is written into the cache. Once a record is in the cache, it is not re-validated when the schema changes. If the schema is tightened after data is already cached, existing records are only affected when an operation attempts to write them under the new schema, which may fail validation and block that write.

Removing entire data objects from your schema is an extreme case that requires significant platform engineering effort to handle gracefully.

Avoid full object deletion if possible. There is no obsolete metadata flag for data objects, and App Xchange does not provide tooling to communicate deprecation to connector users. The best available approach is to update the data object's description with clear text indicating the object is deprecated, coordinate directly with dependent integration teams, and plan a sunset timeline. Note that properties used as keys cannot be made nullable.

Understanding Change Detection in Schema Changes

Before diving into specific scenarios, it helps to understand how the SDK's change detection works and why schema changes break it.

How Change Detection Works

The Xchange Connector SDK uses a hash-based change detection system to avoid re-syncing data that hasn't changed:

  1. Record fingerprinting: Each record is converted to a fingerprint (hash) based on its current property values.
  2. Comparison: On each sync, the SDK compares the current fingerprint against a stored fingerprint from the previous sync.
    • Fingerprints match → record is Unchanged (skipped from sync)
    • Fingerprints differ → record is Updated (sent to sync)
    • No previous fingerprint → record is Created (sent to sync)
  3. Deletion detection: Records that were previously fingerprinted but don't appear in the current data are inferred as Deleted.

Key insight: The fingerprint is based on all property values in the record. If any property is added, removed, or changed, the fingerprint changes.

Why Schema Changes Break Change Detection

Because the fingerprint depends on the complete set of property values, any schema change that affects which properties exist or how they're valued will change the fingerprint for existing records—even if nothing may have actually changed in the source system (though underlying data could have changed as well, so there are no absolutes here).

This mismatch causes the SDK to misclassify records as Updated, Created, or Deleted when they should be Unchanged. In production with many dependent integrations, this triggers a spike in unnecessary flow executions.

Real-world example:

If you add a new nullable property to a 100,000-record data object:

  • Old records' fingerprints were based on 5 properties.
  • New records' fingerprints are based on 6 properties (including the new one).
  • On the first sync, all 100,000 old records will appear as "Updated" because their fingerprints changed.
  • If flows are configured to trigger on changes to this data object, that single sync fires 100,000 unnecessary flow executions.

Quick Reference

Change Safe? Notes
Add a nullable property Safe Existing cached records are unaffected; expect a one-time change-detection spike on first sync
Make a required property nullable Safe Only relaxes the constraint; existing data is unaffected
Make validation less restrictive (e.g. raise MaxLength) Safe Existing records still meet the relaxed constraint
Remove a nullable property Risky Safe only if never used in composite keys and no flows reference it. Recommendation: Deprecate instead by making it nullable and stopping population.
Add a required (non-nullable) property Breaking Writes may fail if the data reader does not supply the new property; stale cache entries corrected after cache write
Remove a required property Breaking Triggers a one-time change-detection spike; flows that reference the property may fail depending on how they are written
Make a nullable property required Breaking Writes may fail if the source data still has null for that property
Rename a property Breaking Equivalent to removing the old property and adding a new one
Change a property type (e.g. stringint) Breaking Triggers a one-time change-detection spike; data corrected after cache write of the affected object
Make validation more restrictive (e.g. lower MaxLength) Breaking Writes may fail if cached records exceed the new limit when next written
Add or change a PrimaryKey property Breaking Breaks change detection and cached record identity; requires admin support for cache and change-detection DB clear
Add a key property component Breaking Changes the path used to identify cached records; requires admin support for cache and change-detection DB clear

Property Change Scenarios

Adding a Nullable Property

This is the safe, preferred way to extend a data object.

// Before
[PrimaryKey("id", nameof(Id))]
public class EquipmentDataObject
{
    [Required]
    public required string Id { get; init; }
    [Required]
    public required string Name { get; init; }
}

// After — adding a nullable property is non-breaking
[PrimaryKey("id", nameof(Id))]
public class EquipmentDataObject
{
    [Required]
    public required string Id { get; init; }
    [Required]
    public required string Name { get; init; }
    [Nullable(true)]
    public string? Category { get; init; }  // safe addition
}

Platform behavior:

  • Existing cached records are unaffected because Category is optional and no write occurs for unmodified records.
  • New records from the data reader will include Category; historical records will have null until re-synced.
  • Change detection will produce a one-time spike: all existing records appear "Updated" on the first sync after the change because their fingerprints now include the new property.
  • Flows that do not reference Category are unaffected; flows that do reference it will see null for historical records until those records are re-synced.

Adding a Required (Non-Nullable) Property

This is a breaking change.

// After — adding a required property without a value in existing records is breaking
[PrimaryKey("id", nameof(Id))]
public class EquipmentDataObject
{
    [Required]
    public required string Id { get; init; }
    [Required]
    public required string Name { get; init; }
    [Required]
    public required string SiteCode { get; init; }  // breaking addition
}

Platform behavior:

  • A write only fails if the data reader does not provide SiteCode. If the source system returns it, records update normally.
  • Records already in cache that were written before the schema change will be stale (they will not have SiteCode). This is corrected after a cache write of the affected data object.
  • Change detection will produce a one-time spike: records may appear as "Updated" even if nothing may have actually changed in the source system.
  • Flows are not stalled by validation failures. Change-triggered flows fire because of the new payload shape. Lookup steps may return stale data for records not yet re-synced.

There is no platform backfill feature. The only way to populate the new property across existing records is to run a cache write after the data object contract and connector meta data are updated.

If the property is needed, there are two approaches — neither is ideal:

  • Add the new property and accept the platform noise (one-time update spike).
  • Add a new version of the data object, update the integration, and accept the platform noise.
When you must add a required property

If you cannot make the property nullable, plan a full data migration following this order:

  1. Update the data reader to always provide a value for the new property.
  2. Test locally using the local testing guide to confirm the data reader produces the new property for all records.
  3. Deploy the updated connector (with both the new schema definition AND the updated data reader that provides the property).
  4. Run a full cache write of the affected data object so all cached records are rewritten with the new property populated.
  5. Verify that records are successfully written.

Why this order matters: If you deploy the schema before the data reader provides values, any write of a record without the new property will fail validation on ingress. Deploy the data reader and schema together so the cache can accept writes from day one.

Removing a Property

Removing a property is generally breaking (and always risky), especially if it is required, referenced by flows, used in change detection hashing, or included in PrimaryKey/AlternateKey definitions.

// Before
public class EquipmentDataObject
{
    [Required]
    public required string Id { get; init; }
    [Required]
    public required string Name { get; init; }
    [Required]
    public required string LegacyCode { get; init; }
}

// After — removing a property is breaking
public class EquipmentDataObject
{
    [Required]
    public required string Id { get; init; }
    [Required]
    public required string Name { get; init; }
    // LegacyCode removed
}

Platform behavior:

  • Cached records still contain LegacyCode in their stored JSON until overwritten. The platform does not re-validate records already in cache; validation only occurs on ingress.
  • Flows that read LegacyCode may fail or receive unexpected data, depending on how the flow is written. A flow can be authored to handle the property existing or not existing.
  • Change detection produces a one-time spike: all existing records appear "Updated" because their fingerprints no longer include LegacyCode, even though nothing may have actually changed in the source system. Data is corrected after a cache write of the affected data object. Operations are full PUT updates.

Preferred approach: deprecate rather than delete. Mark the property [Nullable(true)] and stop populating it from the data reader. Communicate to integrators that the property is deprecated, agree on a removal timeline, and remove it only after all dependent flows have been updated. When the property is no longer present in the target system, remove it from the backing model rather than relying on workarounds to suppress it in change detection.

Changing Nullability

Making a Required Property Nullable (Safe)

// Before
[Required]
public string Status { get; init; }

// After — relaxing nullability is safe
[Nullable(true)]
public string? Status { get; init; }

Existing cached records already have a value for Status, so they continue to pass validation. This change only adds flexibility.

Making a Nullable Property Required (Breaking)

// Before
[Nullable(true)]
public string? Status { get; init; }

// After — tightening nullability is breaking
[Required]
public string Status { get; init; }

Platform behavior:

When you make a nullable property required, validation only happens when a record is written to the platform (on ingress).

  • Unmodified records already in cache: Stay cached with their null values. No validation occurs because no write happens.
  • Records that are updated or re-synced: Are validated against the new schema on ingress. If Status is still null in the source data, the write fails.
  • Flows reading unmodified cached records: Still see null for Status, which may cause downstream failures if the flow expects a non-null value.

Key takeaway: Historical records with null won't fail immediately, but your flows and data reader must handle the transition. Any operation that writes those records will fail validation on ingress until Status has a value.

Before making this change, verify that your data reader always provides non-null values going forward.

Changing a Property Type

Type changes are always breaking.

// Before
[Required]
public string EquipmentId { get; init; }

// After — type change is breaking
[Required]
public int EquipmentId { get; init; }

Platform behavior:

  • Once the metadata change is applied and the new connector is deployed, nothing should fail at the platform level in the normal case.
  • Change detection produces a one-time spike: all existing records appear "Updated" because their fingerprints reflect the new serialization. Data is corrected after a cache write of the affected data object.
  • Flows may be affected depending on how they are written, particularly lookup steps that assume the old type.

Preferred approach: fix the type to align with the target system. If the wrong data type was used, the integration is already broken — correct the type rather than adding a parallel property with the new type.

Tightening Validation Constraints

Adding or reducing a MaxLength, MinLength, Minimum, Maximum, or Pattern constraint on an existing property is a breaking change if any cached records already violate the new constraint.

// Before
[MaxLength(255)]
public string Description { get; init; }

// After — more restrictive validation is potentially breaking
[MaxLength(100)]
public string Description { get; init; }

Platform behavior:

  • Any cached record with a Description longer than 100 characters will fail validation on ingress the next time it is written.
  • The severity depends on how much historical data exists that exceeds the new constraint.

Before tightening a constraint, audit your data to confirm no existing records violate it.

Modifying Key Definitions

Changes to PrimaryKey or AlternateKey attributes are always breaking.

// Before
[PrimaryKey("id", nameof(Id))]
public class EquipmentDataObject { ... }

// After — adding a key component is breaking
[PrimaryKey("id", nameof(Id), nameof(SiteCode))]
public class EquipmentDataObject { ... }

Platform behavior:

  • The key value is used to construct the cache path for each record (for example id/123 becomes id/site-a/123). Changing the key changes the path, so the platform treats all existing records as new and the old records become orphans in the cache.
  • Change detection checkpoints become invalid because they are tied to the previous key structure.
  • Flows that look up records by key may stop finding the records they expect, depending on how the flow is written.

Key changes require a full cache clear, deletion of the change-detection database (which can only be done by Xchange admins at the moment), and a re-sync. Create a support request to coordinate the cache clear, change-detection DB deletion, and timing of service pauses and resumptions.

Cache Implications

The Xchange platform stores connector data in a cache. Schema validation is applied only on ingress — when a record is written into the cache. Validation is determined by the connector metadata at the time of the write. Once a record is in the cache, it is not re-validated.

Operation Validation applied
Initial data reader sync Every record is validated on ingress before being written
Incremental sync / update Every updated record is validated on ingress before being written
Action result write The returned data object is validated on ingress before being written
Reading from cache No validation: records are returned as stored
Change detection Operates on the connector side; schema shape changes cause fingerprint mismatches and one-time update spikes

The cache is not automatically regenerated when the schema changes. If you deploy a breaking schema change, records already in the cache are not modified. Those records are only affected when an operation attempts to write them under the new schema, at which point ingress validation may reject the write. Depending on the platform's error handling, this can:

  • Block individual record updates while others succeed.
  • Stall an entire data sync batch.
  • Leave stale records in cache until a full cache write of the affected data object rewrites them.

To recover from a breaking schema change, a full cache write of the affected data object is required so all cached records are written under the new schema.

Change Detection Implications

When you change a data object's schema, the SDK's fingerprint (from the "Understanding Change Detection" section) changes for existing records. This causes false positives: records appear "Updated" when nothing may have actually changed in the source system.

Key Distinction
  • One-time spikes (added, removed, or changed properties; type changes): false positives occur on the first sync after the change. Operations are full updates, and data is corrected after a cache write of the affected data object.
  • Key changes (critical): the change detector cannot match existing records to new records. All existing records appear "Created". Requires admin support for cache clear.

Impact by change type

Added, removed, or changed properties; type changes (Low–medium severity)

  • Existing records' fingerprints change because the property set or serialization differs.
  • All existing records appear "Updated" on the first sync after the change.
  • This is a one-time false-positive spike that resolves after a cache write of the affected data object.
  • Action: Communicate to dependent teams to expect higher change activity on the first sync. Consider deactivating flows during the cache write and re-enabling once complete.

Key changes (Critical severity)

  • Changing which properties make up the key completely breaks the change detector.
  • The detector cannot match existing records to new records.
  • All existing records appear "Created" on the first sync.
  • Action: Requires a full cache clear (admin only), and re-sync. Create a support request and coordinate with all dependent teams.

Production impact

False positives in change detection cause flows to trigger unnecessarily. For example:

  • If you add a nullable property to a 100,000-record data object, those 100,000 records appear "Updated" on first sync.
  • If flows are configured to trigger on changes, that single sync fires 100,000 unnecessary flow executions.
  • This consumes quota and can block other workloads.

After the cache write completes, teams may need to manually identify which records to re-run if real changes occurred during the migration window. As an advanced option, a short-lived filter trigger can be configured to ignore updates where only the changed property differs.

Flow Execution Effects

Flows are affected when they are built to depend on a specific contract shape or when change detection produces update spikes:

  • Change-triggered flows may fire for every record on the first sync after a schema change, even if nothing may have actually changed in the source system.
  • Lookup steps that reference a removed or renamed property may receive undefined if the flow accesses the property using dot notation or subscripts, depending on how the flow is written.
  • Flows that depend on key-based lookups may stop finding records if a key change has orphaned the cached data.

For SDK-based connectors deployed in sync with their metadata, action handlers should already produce output that matches the current contract. Validation failures from action writes are an edge case — typically the result of unexpected platform errors or misaligned connector code — not a normal consequence of schema migration.

Migration Strategy

Use this approach when you need to make a breaking schema change to a connector with active integrations.

Step 1: Announce the change (2–4 weeks before)

  • Notify all teams whose flows depend on the affected connector.
  • Document the specific properties being changed and how the change affects each property.
  • Provide a timeline with specific dates for each step below.

Step 2: Prepare dual-write (if applicable)

If you are adding a new property to replace an existing one (such as a rename), add both properties to the data object at the same time. Have the data reader populate both the old and the new property. This allows existing flows to continue using the old property while integrators migrate to the new one.

// Transition state — both old and new properties exist
[Required]
public required string LegacyCode { get; init; }  // retained temporarily
[Nullable(true)]
public string? NewCode { get; init; }              // new property added as nullable

This pattern applies to renames and replacements, not to type corrections — if the wrong type was used, fix the type directly.

Step 3: Prepare the data reader

Update the data reader to always provide values for any new required properties. Test locally using the local testing guide to confirm that all records are fully populated before deploying.

Step 4: Schedule a migration window

Coordinate with App Xchange and dependent teams to schedule a window during which:

  1. Dependent flows are deactivated.
  2. The updated connector is deployed (metadata and connector image together).
  3. A full cache write of all affected data objects is run to regenerate the cache.
  4. If key definitions changed, create a support request for cache clear operation (admin only).
  5. Dependent flows are re-enabled and verified.
  6. Manually identify which records to re-run if real changes may have occurred during the window.

As an advanced option, configure a short-lived filter trigger to ignore updates where only the changed property differs.

Step 5: Remove deprecated properties (separate release)

After all integrators have confirmed their flows are working with the new schema, remove any deprecated properties in a separate release. Treat this removal as a new breaking change and follow this process again if any integrations still reference the old property.

Checklist Before Deploying a Schema Change

Use this checklist to assess the risk of your change before deployment.

  • Is this connector used by active integrations?
    • If yes, notify dependent teams and plan a migration window.
  • Are any modified properties used in PrimaryKey or AlternateKey definitions?
    • If yes, plan a full cache clear operation (admin only via support request), and re-sync.
  • Will any records fail ingress validation under the new schema when next written?
    • If yes, plan a full cache write before or during the migration window.
  • Have you added all new required properties as nullable first?
    • If not, consider whether the property can be nullable during a transition period.
  • Has the data reader been updated and tested locally to produce values for all new properties?
  • Have dependent teams been given enough time to update their flows?
  • Have flows been deactivated during the cache write, with a plan to manually re-run records that may have had real changes?
  • Is there a rollback plan if the migration window encounters problems?