Skip to content

Define and use a compound

This example creates a fresh compound definition. Run it once in a bootstrapped Drupal development environment, after enabling Compound Field.

use Drupal\compound_field\Entity\CompoundField;

$compound = CompoundField::create([
  'id' => 'speaker',
  'label' => 'Speaker card',
  'fields' => [
    'name' => [
      'field_type' => 'string',
      'label' => 'Name',
    ],
    'topic' => [
      'field_type' => 'string',
      'label' => 'Topics',
      'cardinality' => 3,
    ],
  ],
]);
$compound->save();

Attach compound_field:speaker through normal FieldStorageConfig and FieldConfig, or through Field UI. The host storage's cardinality can be FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. That does not change the sub-field caps.

Read, validate and save

With a loaded Conference node and its field_speakers field:

$node->set('field_speakers', [
  [
    'name' => 'Ada Lovelace',
    'topic' => [
      ['value' => 'Computing history'],
      ['value' => 'Analytical engines'],
    ],
  ],
]);
$violations = $node->validate();
if ($violations->count() !== 0) {
  throw new \RuntimeException('Conference values failed validation.');
}
$node->save();

$name = $node->get('field_speakers')->first()->get('name')->get('value')->getValue();

The normal entity API does not automatically validate every programmatic save. Handle violations before persisting user-supplied values. Do not expose detailed internal exceptions to an untrusted client.

storage_settings belongs to a sub-field's storage definition, such as a string's maximum length or a reference's target entity type. settings contains field settings, such as a reference handler and allowed bundles. Their defaults come from the field type.

For an existing, in-use compound, change its declared fields through the deployment guide. Read getDeclaredFields() before writing setFields() when the compound uses composition; getFields() also contains included fields.

Storage settings

Compound storage_settings use the field type's exported configuration shape. For Options fields, write allowed_values as [['value' => 'red', 'label' => 'Red']]. A standalone FieldStorageConfig instead takes the runtime map ['red' => 'Red']; mixing these shapes can cause schema errors. Compound Field converts sub-field settings for the plugin at runtime.

Changing datetime_type on an in-use sub-field is refused even when its column specification is unchanged, because it changes how stored values are interpreted. Retiring populated allowed-value keys needs explicit consent.

Include another compound's fields

Given a person compound, this export adds its sub-fields directly to employee:

id: employee
label: Employee
includes:
  - person
fields:
  cv:
    field_type: file
    label: CV

Use the compound's Composition tab or set includes in configuration. getDeclaredFields() returns only local definitions; getFields() resolves included fields first, transitively, then local fields. Repeated bases contribute once. Duplicate sub-field names, missing bases and cycles are refused.

An inclusion adds no value nesting. A sub-field of type compound_field:person instead holds a nested person record. Both count toward the expanded column budget. Edit inherited fields on their declaring compound; changes affect every dependent host storage. An in-use dependent prevents deleting its base. Deleting an unused base can cascade to unused dependents.

Change an in-use definition

Use the structural pipeline instead of saving the definition directly:

$fields = $compound->getDeclaredFields();
$fields['phone'] = ['field_type' => 'telephone', 'label' => 'Phone'];
unset($fields['fax']);
$compound->setFields($fields);
$request = \Drupal\compound_field\StructuralChange\CompoundFieldStructuralChangeRequest::create()
  ->acknowledgeRemoval('fax');
$pipeline = \Drupal::service('compound_field.structural_change_pipeline');
$plan = $pipeline->plan($compound, NULL, $request);
// Inspect $plan->describe() before applying.
$pipeline->apply($compound, $request);

Changes to includes use this pipeline too. Removing an inclusion requires acknowledging the sub-fields it removes. See the deployment guide for imports and recovery.