How to make a custom composite field? Is it possible to create a custom field widget or use hook_form_alter() to show multiple form elements that, used together, can set the value of a single field?
For example, I added a textfield Personalization.
But on the entity edit form, I do not want users to edit the Personalization field directly; instead, I want them to check some boxes and fill in some text fields, and then I want to process that data and set the value of the Personalization field as a result of that processing.  How do I set up the extra form elements and use them to set the value of the field?
I tried to do this by override the textarea FieldWidget:
/MYMODULE/src/Plugin/Field/FieldWidget/TextareaPersonalizationWidget.php
&lt;?php

namespace Drupal\MYMODULE\Plugin\Field\FieldWidget;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextareaWidget;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'text_textarea_personalization' widget.
 *
 * @FieldWidget(
 *   id = &quot;text_textarea_personalization&quot;,
 *   module = &quot;MYMODULE&quot;,
 *   label = @Translation(&quot;Personalization widget&quot;),
 *   field_types = {
 *     &quot;text_long&quot;,
 *     &quot;string_long&quot;
 *   }
 * )
 */
class TextareaPersonalizationWidget extends StringTextareaWidget {

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &amp;$form, FormStateInterface $form_state) {

    $element['a']['#type'] = 'textfield';
    $element['a']['#title'] = 'Your fav animal';
    $element['a']['#maxlength'] = '255';
    $element['b']['#type'] = 'textfield';
    $element['b']['#title'] = 'Your fav fruit';
    $element['b']['#maxlength'] = '255';
    return $element;
  }

}

This causes two textfields to be shown instead of the textarea.  Great!  Except the value never gets saved, and I can't process the input text in hook_entity_presave().  So how can I process the input in the custom form elements and save the result in the original field?
I also tried the same thing in MYMODULE_field_widget_string_textarea_form_alter() but I had the same problem.
More details about how my site is set up
Here's my setup: I have a flag called Collection.  This flag contains a textfield, Personalization.  This textfield will contain pairs of strings for replacing text with JavaScript in order to customize the text on terms that have been flagged by the user.
Of course, I don't want users to type in arbitrary strings into a textfield; this would be a terrible UI.  So, when a user flags a taxonomy term, I want to show some form elements that allow the user to input some options for personalization.  These form elements will be different depending on which term is being flagged.  For example, if flagging the Fruits term, I want to add a text field &quot;What is your favorite fruit?&quot;  And on the Cakes term, I want to add a list of checkboxes &quot;Choose all the types of frosting you like.&quot;
In other words, I want to define a custom form per taxonomy term that sets the value of the Personalization field, and I want to somehow embed this custom form on the flag add form.
Except the value never gets saved, and I can't process the input text
in hook_entity_presave().

You can add a new validate for your widget:
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &amp;$form, FormStateInterface $form_state) {
  // Your logic code

  // Add validate for your widget.
  $element['#element_validate'] = [
    [$this, 'validate'],
  ];

  return $element;
}

And convert value before save:
/**
* Validate the fields and convert data before save.
*/
public function validate($element, FormStateInterface $form_state) {
    // Logic validate.
    // Code here

    // Convert value and set.
    // Field type text_long
    $value = [
     'value' =&gt; 'your value',
     'format' =&gt; 'your format'
    ];

    // Field type string_long
    $value = [
      'value' =&gt; 'your value'
    ];

    $form_state-&gt;setValueForElement($element, $value);
}