custom_field's 'custom' field type not recognized as compound
Problem
FieldWidgetSupport::isCompoundFieldType() probes a bare BaseFieldDefinition::create($field_type) and calls a type compound when the probe reports anything other than exactly one stored property. custom_field's CustomItem::defaultStorageSettings() always seeds one placeholder value column, purely so the field's schema can be created before a site builder adds real ones. That default makes the bare probe report exactly one stored property, so custom is misclassified as simple.
Verified with drush php:eval: BaseFieldDefinition::create('custom') reports isCompound() = FALSE, and custom is absent from EcaFieldWidgetDeriver::compoundFieldTypes(). A real custom field with several configured columns still gets isCompound() = TRUE at fill time, because EcaFieldWidget::usesCompoundFill() probes the field's actual FieldStorageDefinition, not a bare one. But the deriver's field_types allowlist excludes custom entirely, so a model scoped to the "Compound fields" strategy in Manage form display is never offered on a custom_field field.
This contradicts the module's own README, which lists Custom Field alongside Address, Link and Smart Date as a supported compound example, and contradicts isCompound()'s own doc comment, which assumes a settings-dependent field type reports zero properties on a bare probe - custom_field's default column means it reports one instead.
Proposed resolution
Add an explicit override list to FieldWidgetSupport for field types whose bare probe cannot be trusted, seeded with custom, and extend EcaFieldWidgetDeriverTest to assert that both custom and Smart Date's smartdate land in the compound field_types catalog. smartdate already does today, since its property set does not depend on field settings; it is asserted as a regression guard, not a fix.
LLM-summary - Click to expand
Root cause
FieldWidgetSupport::isCompound(FieldStorageDefinitionInterface $definition) counts
non-computed properties from $definition->getPropertyDefinitions() and treats any
count other than 1 as compound. FieldWidgetSupport::isCompoundFieldType(string $field_type) answers this for a bare, unconfigured field by calling
BaseFieldDefinition::create($field_type) and running it through isCompound().
custom_field's CustomItem::defaultStorageSettings() returns:
return [
'columns' => [
'value' => ['name' => 'value', 'type' => 'string'],
],
] + parent::defaultStorageSettings();CustomItem::propertyDefinitions() iterates the columns setting to build the
property list. Because the default setting always seeds one column, a bare
BaseFieldDefinition::create('custom') always yields exactly one stored property
(value), regardless of how a real, configured custom field is set up. So
isCompound() returns FALSE and isCompoundFieldType('custom') returns FALSE.
EcaFieldWidgetDeriver::compoundFieldTypes() uses this bare probe across every
installed field type plugin ID to build the field_types allowlist for any ECA
model scoped to the FILL_COMPOUND fill strategy. Because the probe misclassifies
custom, that allowlist never includes it, and FieldWidgetActionManager:: getAllowedFieldWidgetActions() (in field_widget_actions) then filters out the
derived action for any custom_field-typed field, no matter how many columns it
is actually configured with.
The real-time fill path is unaffected: EcaFieldWidget::usesCompoundFill() calls
FieldWidgetSupport::isCompound($items->getFieldDefinition()->getFieldStorageDefinition()),
which uses the field's actual, configured FieldStorageDefinition. A real custom
field with 3 columns correctly reports isCompound() = TRUE there. The break is
purely in the model-scoping catalog built at plugin-derivation time.
Verification performed
Ran directly against a bootstrapped Drupal 11 site via drush php:eval:
BaseFieldDefinition::create('custom') -> 1 stored property ('value')
FieldWidgetSupport::isCompound($def) = FALSE
FieldWidgetSupport::isCompoundFieldType('custom') = FALSE
Full compound-type catalog (all installed field-type plugins, before fix):
address, comment, file, image, link, map, password, path,
simplenews_issue, text, text_long, text_with_summarycustom was absent. A real custom field storage config with 3 columns still
correctly reported isCompound() = TRUE against its own FieldStorageDefinition,
confirming the break was isolated to the bare-probe catalog.
Also checked Smart Date's smartdate field type, since the module's README lists
it as a supported compound example too:
BaseFieldDefinition::create('smartdate') -> 8 properties (6 stored, 2 computed)
FieldWidgetSupport::isCompound($def) = TRUE
FieldWidgetSupport::isCompoundFieldType('smartdate') = TRUEsmartdate's properties are declared unconditionally, not derived from field
settings, so the existing bare-probe logic already classifies it correctly. No
code change was needed for it; a kernel-test assertion was added instead, to
guard against a future regression.
Fix
Added FieldWidgetSupport::ALWAYS_COMPOUND_FIELD_TYPES, a short override list
checked before the bare probe in isCompoundFieldType(). It currently holds only
custom. Updated the doc comment on isCompound() to stop asserting that a
settings-dependent type's bare probe reports zero properties, since custom_field
demonstrates that assumption does not hold universally.
Tests
Extended EcaFieldWidgetDeriverTest::testCompoundStrategyScopesFieldTypes() (a
kernel test) to install custom_field and smart_date alongside the existing
link/text fixtures, and assert that both custom and smartdate are present
in the derived field_types allowlist for a model scoped to the compound fill
strategy. Ran the full EcaFieldWidgetDeriverTest suite (11 tests, 44 assertions)
and the FieldWidgetSupportTest unit suite (31 tests, 59 assertions): both pass.
AI-Generated: Yes (agent diagnosed the bug via source review and drush php:eval probes against a live Drupal 11 kernel, wrote the fix and the kernel-test coverage, and drafted this issue; reviewed by jurgenhaas).