Overview
When a component's active version is updated (via the Canvas UI or CLI) and the new version introduces changed prop shapes, the layout API crashes with InvalidComponentInputsPropSourceException during auto-save creation — even for component
instances that were not modified by the auto-updater.
The root cause is that AutoSaveManager::normalizeEntity() calls optimizeInputs() on all component tree items in the entity, not just the ones that were auto-updated. A single item with a stale prop source shape poisons the entire auto-save.
This started to happen when I updated the site from Canvas 1.0.2 to Canvas 1.2.
Steps to reproduce
1. Create two JS components via Canvas UI/CLI:
- Component A: e.g., a simple card with a title (string) prop
- Component B: e.g., a hero with a backgroundImage (entity_reference targeting a single media bundle) and a body (string_long) prop
2. Create a Canvas page using instances of both Component A and Component B. Save the page.
3. Update Component A — add a new optional prop (e.g., subtitle). This is a safe change: canUpdate() will return TRUE for existing instances.
4. Update Component B — change the backgroundImage prop's target bundles (e.g., single-bundle → multi-bundle), or change body from string_long to text_long. This is an unsafe shape change: canUpdate() will return FALSE.
5. Open the page in the Canvas editor (triggers GET /canvas/api/layout/{page_id}).
Proposed resolution
Result:
500 Internal Server Error with InvalidComponentInputsPropSourceException.
Expected:
The page loads. Component A instances are auto-updated to the new active version. Component B instances stay at their pinned version (since canUpdate() correctly returns FALSE due to the shape change).
Error
InvalidComponentInputsPropSourceException: The shape of prop {prop_name} of component
{component_id} has the following shape: '{"sourceType":"static:field_item:entity_reference",
"expression":"...old..."}', but must match the default, which is:
'{"sourceType":"static:field_item:entity_reference","expression":"...new..."}'.
Thrown at GeneratedFieldExplicitInputUxComponentSourceBase::optimizeExplicitInput() (~line 1335).
Call chain
ApiLayoutController::buildRegion()
→ ComponentSourceManager::updateComponentInstances()
// Auto-updates Component A (canUpdate()=TRUE) → wasModified=TRUE
// Leaves Component B alone (canUpdate()=FALSE)
→ AutoSaveManager::saveEntity() // triggered because wasModified=TRUE
→ normalizeEntity()
→ ComponentTreeItem::optimizeInputs() // called on ALL items, including B
→ Component::loadVersion($pinned_version)
→ optimizeExplicitInput($inputs)
→ getDefaultStaticPropSource($prop, FALSE)
→ hasSameShapeAs($default)
// THROWS — Component B's stored shape ≠ new version's default shape
Root cause
AutoSaveManager::normalizeEntity() (~line 228):
if ($item instanceof ComponentTreeItem) {
$item->optimizeInputs();
}
This iterates every ComponentTreeItem in the entity. The auto-updater correctly skips Component B (shape changed → canUpdate()=FALSE), but normalizeEntity() still calls optimizeInputs() on it. Since Component B's stored inputs were written
with the old version's shapes, optimizeExplicitInput() compares them against the new version's defaults and throws.
The auto-updater does the right thing. The problem is that normalizeEntity() doesn't respect the updater's decision and validates items the updater intentionally left alone.
Key condition
This only crashes when both of the following are true on the same page:
1. At least one component instance can be auto-updated (canUpdate()=TRUE) — this triggers wasModified=TRUE → auto-save creation
2. At least one other component instance cannot be auto-updated (canUpdate()=FALSE) — its stored shapes are stale but the auto-updater intentionally left it alone
If all instances on a page can be auto-updated, or none can, the crash does not occur.
Concrete shape changes that trigger this
| Prop type | Change | Effect |
|---|---|---|
entity_reference |
Target bundles changed (e.g., single-bundle → multi-bundle) | Expression string differs → shape mismatch |
link |
link_type added/removed from field storage settings |
sourceTypeSettings differs → shape mismatch |
string_long → text_long |
Field type changed entirely | sourceType and expression both differ |
In all cases, canUpdate() correctly returns FALSE for the affected instances. The crash only happens because normalizeEntity() forces validation on them anyway.
Suggested fix
Option A — Scope optimizeInputs() to modified items only: normalizeEntity() should only call optimizeInputs() on items that were actually modified by updateComponentInstances(), or accept a list of modified item UUIDs to limit the scope.
Option B — Catch and log instead of crashing: Wrap the optimizeInputs() call in normalizeEntity() with a try/catch for InvalidComponentInputsPropSourceException, log a warning, and continue. The auto-save will contain the un-optimized data for
that item, which is acceptable since the item wasn't modified.
Option C — Skip optimizeInputs() in auto-save normalization entirely: Since auto-save is a transient store and optimizeInputs() already runs on preSave() when the entity is actually persisted, it may not need to run during auto-save creation
at all.
Comments
Comment #2
wim leersRoot cause is the stale prop source?
Isn't that the actual root cause then? 🤔
(What the auto-save system does may be suboptimal: it's slower than it could be, but it shouldn't crash for reasons outside the auto-save system.)
And: how can that even occur?
Timeline: this has been impossible since long before 1.0-beta
Canvas timeline for related key changes:
CollapseComponentInputsUpdateTest) only updates the config entities, not the content entities. That was intentional. Because we were not even in alpha!(Update paths/data integrity is only promised since beta.)
Cross-check
The epic wall of text (much appreciated 👏, gives me enough info to connect dots! 😊🙏) seems to imply places that this requires steps impossible since 1.0 (and actually much earlier):
This seems to be talking about the
Componentconfig entity, but I suspect you're actually referring to the component instance?If it were referring to the Component config entity, then the component version referenced by the component instance would mean that the shapes do stay in sync.
So that makes me think you're talking about an uncollapsed prop source for this input, and modifying that, directly in the
canvas_page__componentstable'scomponent_inputscolumn?InvalidComponentInputsPropSourceExceptionexception you mention can also only be thrown byGeneratedFieldExplicitInputUxComponentSourceBase::optimizeExplicitInput(), and only in the case if >=1 prop's input does NOT have a collapsed value — i.e. it can only happen if it contains an entire{ "sourceType": … }array.Correct?
Are my deductions correct?
SELECT * FROM canvas_page__components WHERE components_inputs LIKE '%"sourceType":"static%'would surface all affected rows.Comment #3
wim leersNot actionable per #2.