Patch packages modify existing packages incrementally. Advanced patterns — multi-patch chains, component patching, and array transformations — require understanding the patch processing rules.
When to Patch vs. New Version
| Scenario | Approach | Reason |
|---|---|---|
| Adding a new attribute type | Patch (add) | Additive change, no structural impact |
| Changing an attribute name | Patch (update) | Single property modification |
| Restructuring the entire template | New package version | Too many interdependent changes for a patch |
| Adding one field to a template | Patch (add with addToEnd) | Targeted insertion |
| Removing an entire object type | Patch (delete) | Use with caution — breaks references |
| Major schema changes across multiple object types | New package version | Patch complexity would exceed benefit |
One change per patch is the recommended practice. It makes debugging straightforward and rollback granular. Name patches descriptively: cust_core_patch_add_calories_attribute.
Patch Structure
A patch package declares itself with type: "patch" and references the package it modifies:
{
"type": "patch",
"key": "cust_core_patch_add_rating",
"name": "Add Rating Attribute to Core",
"basePackageKey": "cust_core",
"runAfter": "cust_core",
"dependsOn": ["cust_core"]
}
| Property | Purpose |
|---|---|
type | Must be "patch" |
basePackageKey | Identifies the package this patch modifies |
runAfter | Specifies which package (or patch) must be installed before this one executes |
dependsOn | Array of package keys that must be present in the system |
runAfter controls execution order among patches. dependsOn declares the broader dependency graph.
Action Types
Every asset entry in a patch carries an action property that determines how the system processes it.
Add
Creates a new asset. All mandatory properties for the asset type must be specified.
{
"action": "add",
"key": "cust_rating",
"name": "Rating",
"features": [
{ "key": "is_number", "value": true },
{ "key": "sort_by_number", "value": true }
]
}
Omitting a mandatory property causes validation failure.
Update
Modifies an existing asset. Specify the key to identify the target and only the properties that change. Properties not included in the update retain their current values.
{
"action": "update",
"key": "cust_rating",
"name": "Customer Rating"
}
This changes only the name property. All other properties on cust_rating remain unchanged.
Delete
Removes an asset. Only the key property is required.
{
"action": "delete",
"key": "cust_obsolete_field"
}
Deleting an asset that other packages or patches reference causes cascading failures. The referencing packages report “reference not found” errors on their next validation. Verify the dependency graph before deleting.
Action Scope: Asset Level vs. Property Level
Patch actions operate at two scopes.
Asset-Level Actions
Target the top-level array item. Add, update, or delete an entire objectType, attributeType, relationType, or other first-class asset.
{
"attributeTypes": [
{
"action": "add",
"key": "cust_calories",
"name": "Calories",
"features": [
{ "key": "is_number", "value": true }
]
}
]
}
Property-Level Actions
Target a nested property within an existing asset. Add an attribute to an object type, add a component to a template, or add a relation mapping.
{
"objectTypes": [
{
"action": "update",
"key": "cust_recipe",
"attributeTypes": [
{
"action": "add",
"key": "cust_calories"
}
]
}
]
}
This adds the cust_calories attribute to the existing cust_recipe object type without modifying any other property of that object type.
Component Patching
Template component patching follows stricter rules than other property-level patches.
Full Component Specification Required
When patching a component, specify the entire component definition. Partial component patches silently drop unspecified properties — the system does not merge with the existing component definition.
{
"action": "add",
"componentId": "rating_panel",
"componentType": "attributePanel",
"title": "Rating",
"attributes": [
{ "key": "cust_rating" },
{ "key": "cust_review_count" }
],
"addToEnd": true
}
Specifying only componentId and one property in an update does not merge with the existing component. All unspecified properties reset to defaults. Always include the complete component definition.
Positioning Controls
| Property | Effect |
|---|---|
addToStart | Inserts the component at the beginning of the container |
addToEnd | Inserts the component at the end of the container |
beforeComponentId | Inserts before the specified component |
afterComponentId | Inserts after the specified component |
Positioning properties require that the target components have componentId values. If the base package omits componentId on its components, only addToStart and addToEnd are available.
Visual Container Exception
Components inside visual containers (panels, splitters, tab groups) can be patched individually by referencing the container’s componentId and the target component’s componentId. This is the only case where the system resolves the patch target within a nested structure.
String Array Patching
String arrays in the base package must be transformed into object arrays in the patch. Each element requires action and key properties.
Base package — objectTypeKeys as a string array:
{
"key": "my_application",
"objectTypeKeys": ["cust_recipe", "cust_ingredient"]
}
Patch — adding a new entry using object array syntax:
{
"action": "update",
"key": "my_application",
"objectTypeKeys": [
{
"action": "add",
"key": "cust_menu_item"
}
]
}
This adds cust_menu_item to the objectTypeKeys array without removing the existing entries.
Using plain string syntax in a patch replaces the entire array. Always use object array syntax with explicit action properties when modifying arrays in patches.
Multi-Patch Chains
Complex modifications that span multiple asset types require ordered patch chains. Use runAfter to enforce execution sequence.
Patch A — adds the attribute type:
{
"key": "cust_core_patch_a_add_attr",
"basePackageKey": "cust_core",
"runAfter": "cust_core",
"attributeTypes": [
{
"action": "add",
"key": "cust_calories",
"name": "Calories",
"features": [
{ "key": "is_number", "value": true },
{ "key": "sort_by_number", "value": true }
]
}
]
}
Patch B — adds the attribute to the object type and template (runs after Patch A):
{
"key": "cust_core_patch_b_add_to_template",
"basePackageKey": "cust_core",
"runAfter": "cust_core_patch_a_add_attr",
"objectTypes": [
{
"action": "update",
"key": "cust_recipe",
"attributeTypes": [
{ "action": "add", "key": "cust_calories" }
]
}
]
}
Patch B references cust_calories, which Patch A creates. The runAfter property guarantees Patch A installs first.
Chain Validation
All patches in a chain must be validated and installed in sequence. Installing Patch B before Patch A produces a “reference not found” error for cust_calories.
Gotchas
Key renames are destructive. Renaming an asset key in a patch is equivalent to deleting the old key and creating a new one. All references to the old key — in other packages, patches, templates, and automations — break silently or with “reference not found” errors. To rename safely: create the new key, migrate all references, then delete the old key in a subsequent patch.
Component patches are full replacements. Specifying a subset of component properties in an update does not merge with the existing definition. Unspecified properties revert to defaults. Always include every property of the component, even unchanged ones.
Base package rollback is blocked by dependent patches. Uninstalling or rolling back a base package fails if any patch packages depend on it. Delete all dependent patches first, then roll back the base package.
String-to-object array transformation is required. Patching a string array without converting elements to the object format ({ "action": "add", "key": "..." }) replaces the entire array instead of appending. This is a common source of data loss in patches.
Validate incrementally. After writing each patch in a chain, validate it individually before proceeding to the next. Catching errors early prevents cascading validation failures across the chain.
Descriptive naming. Name patches to reflect their content: cust_core_patch_add_calories_attribute, cust_core_patch_add_calories_to_template. This makes the installation log readable and simplifies debugging when chains grow long.