diff --git a/core/modules/block_content/block_content.module b/core/modules/block_content/block_content.module
index 69717d6de6..72a51f30d8 100644
--- a/core/modules/block_content/block_content.module
+++ b/core/modules/block_content/block_content.module
@@ -94,7 +94,10 @@ function block_content_add_body_field($block_type_id, $label = 'Body') {
       'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'),
       'bundle' => $block_type_id,
       'label' => $label,
-      'settings' => ['display_summary' => FALSE],
+      'settings' => [
+        'display_summary' => FALSE,
+        'allowed_formats' => [],
+      ],
     ]);
     $field->save();
 
diff --git a/core/modules/block_content/migrations/block_content_body_field.yml b/core/modules/block_content/migrations/block_content_body_field.yml
index 9ed884f99e..bc30e0ff98 100644
--- a/core/modules/block_content/migrations/block_content_body_field.yml
+++ b/core/modules/block_content/migrations/block_content_body_field.yml
@@ -13,6 +13,7 @@ source:
       field_name: body
       label: Body
       display_summary: false
+      allowed_formats: {  }
   ids:
     entity_type:
       type: string
diff --git a/core/modules/book/config/optional/field.field.node.book.body.yml b/core/modules/book/config/optional/field.field.node.book.body.yml
index 7cf998dcf6..e45dcc27db 100644
--- a/core/modules/book/config/optional/field.field.node.book.body.yml
+++ b/core/modules/book/config/optional/field.field.node.book.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
index 0cc527ebac..03204f08ff 100644
--- a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
+++ b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
@@ -81,7 +81,10 @@ protected function setUp(): void {
       'field_storage' => $field_storage,
       'bundle' => 'page',
       'label' => 'Body',
-      'settings' => ['display_summary' => TRUE],
+      'settings' => [
+        'display_summary' => TRUE,
+        'allowed_formats' => [],
+      ],
       'required' => TRUE,
     ])->save();
 
diff --git a/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php b/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
index b5373b8fae..85a4aeb920 100644
--- a/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
+++ b/core/modules/ckeditor/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
@@ -62,7 +62,10 @@ protected function setUp(): void {
       'field_storage' => $field_storage,
       'bundle' => 'page',
       'label' => 'Body',
-      'settings' => ['display_summary' => TRUE],
+      'settings' => [
+        'display_summary' => TRUE,
+        'allowed_formats' => [],
+      ],
       'required' => TRUE,
     ])->save();
 
diff --git a/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php b/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
index 0cf5548b9c..9591438206 100644
--- a/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
+++ b/core/modules/config_translation/tests/src/Functional/ConfigTranslationListUiTest.php
@@ -414,7 +414,10 @@ public function doFieldListTest() {
       'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'),
       'bundle' => $block_content_type->id(),
       'label' => 'Body',
-      'settings' => ['display_summary' => FALSE],
+      'settings' => [
+        'display_summary' => FALSE,
+        'allowed_formats' => [],
+      ],
     ]);
     $field->save();
 
diff --git a/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml b/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml
index 4e0131622e..326f8d3ad2 100644
--- a/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml
+++ b/core/modules/editor/tests/modules/config/schema/editor_test.schema.yml
@@ -15,3 +15,7 @@ editor.settings.trex:
     stumpy_arms:
       type: boolean
       label: 'Stumpy arms'
+
+field.field_settings.editor_test_text_long:
+  label: 'Filter test text (formatted, long) settings'
+  type: field.field_settings.text
diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml
index 3e9c0a4c52..6de8ad8290 100644
--- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml
+++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import.yml
@@ -11,5 +11,6 @@ description: ''
 required: false
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text
diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml
index faf99c3a89..a721251113 100644
--- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml
+++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.entity_test.field_test_import_2.yml
@@ -11,5 +11,6 @@ description: ''
 required: false
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text
diff --git a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml
index 95a19af3c7..cfa151f499 100644
--- a/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml
+++ b/core/modules/field/tests/modules/field_test_config/config/install/field.field.entity_test.test_bundle.field_test_import_2.yml
@@ -11,5 +11,6 @@ description: ''
 required: false
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text
diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml
index 678c9fa19a..b8af4db0e3 100644
--- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml
+++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.entity_test.field_test_import_sync.yml
@@ -9,7 +9,8 @@ description: ''
 required: '0'
 default_value: {  }
 default_value_callback: ''
-settings: { }
+settings:
+  allowed_formats: {  }
 field_type: text
 dependencies:
   config:
diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml
index e483418f93..1f8f54fcba 100644
--- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml
+++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle.field_test_import_sync_2.yml
@@ -9,7 +9,8 @@ description: ''
 required: '0'
 default_value: {  }
 default_value_callback: ''
-settings: { }
+settings:
+  allowed_formats: {  }
 field_type: text
 dependencies:
   config:
diff --git a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml
index 3afd2e255c..bc68be6378 100644
--- a/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml
+++ b/core/modules/field/tests/modules/field_test_config/sync/field.field.entity_test.test_bundle_2.field_test_import_sync_2.yml
@@ -9,7 +9,8 @@ description: ''
 required: '0'
 default_value: {  }
 default_value_callback: ''
-settings: { }
+settings:
+  allowed_formats: {  }
 field_type: text
 dependencies:
   config:
diff --git a/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php b/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php
index 025b820df1..f13eb9865d 100644
--- a/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php
+++ b/core/modules/field/tests/src/Functional/Rest/FieldConfigResourceTestBase.php
@@ -82,7 +82,9 @@ protected function getExpectedNormalizedEntity() {
       'label' => 'field_llama',
       'langcode' => 'en',
       'required' => FALSE,
-      'settings' => [],
+      'settings' => [
+        'allowed_formats' => [],
+      ],
       'status' => TRUE,
       'translatable' => TRUE,
       'uuid' => $this->entity->uuid(),
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
index 47edfd6d64..489ef4af9c 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
@@ -33,7 +33,7 @@ public function testFieldInstanceMigration() {
     $field = FieldConfig::load('node.story.field_test');
     $this->assertSame('Text Field', $field->label());
     // field_test is a text_long field, which have no settings.
-    $this->assertSame([], $field->getSettings());
+    $this->assertSame(['allowed_formats' => []], $field->getSettings());
     $this->assertSame('text for default value', $entity->field_test->value);
 
     // Test a number field.
diff --git a/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml b/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml
index 215199cc22..8c6fa4c571 100644
--- a/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml
+++ b/core/modules/forum/config/optional/field.field.comment.comment_forum.comment_body.yml
@@ -16,5 +16,6 @@ required: true
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/modules/forum/config/optional/field.field.node.forum.body.yml b/core/modules/forum/config/optional/field.field.node.forum.body.yml
index 4289cdb2b2..ac1fa82a5d 100644
--- a/core/modules/forum/config/optional/field.field.node.forum.body.yml
+++ b/core/modules/forum/config/optional/field.field.node.forum.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
index eb81f50912..d5ed11bede 100644
--- a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
@@ -119,7 +119,7 @@ protected function getExpectedDocument() {
           'label' => 'field_llama',
           'langcode' => 'en',
           'required' => FALSE,
-          'settings' => [],
+          'settings' => ['allowed_formats' => []],
           'status' => TRUE,
           'translatable' => TRUE,
           'drupal_internal__id' => 'node.camelids.field_llama',
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 71cf4edad7..9661f9a44e 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -266,7 +266,10 @@ function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
       'field_storage' => $field_storage,
       'bundle' => $type->id(),
       'label' => $label,
-      'settings' => ['display_summary' => TRUE],
+      'settings' => [
+        'display_summary' => TRUE,
+        'allowed_formats' => [],
+      ],
     ]);
     $field->save();
 
diff --git a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml
index 8e21dfb0c0..70a991652c 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml
@@ -17,4 +17,5 @@ default_value: {  }
 default_value_callback: ''
 settings:
   display_summary: true
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/modules/text/config/schema/text.schema.yml b/core/modules/text/config/schema/text.schema.yml
index 08bc9aad87..e0fa5d3d83 100644
--- a/core/modules/text/config/schema/text.schema.yml
+++ b/core/modules/text/config/schema/text.schema.yml
@@ -19,6 +19,12 @@ field.storage_settings.text:
 field.field_settings.text:
   type: mapping
   label: 'Text (formatted) settings'
+  mapping:
+    allowed_formats:
+      type: sequence
+      label: 'Allowed text formats'
+      sequence:
+        type: string
 
 field.value.text:
   type: mapping
@@ -33,11 +39,17 @@ field.value.text:
 
 field.storage_settings.text_long:
   label: 'Text (formatted, long) settings'
-  type: mapping
+  type: field.field_settings.text
 
 field.field_settings.text_long:
   label: 'Text (formatted, long) settings'
   type: mapping
+  mapping:
+    allowed_formats:
+      type: sequence
+      label: 'Allowed text formats'
+      sequence:
+        type: string
 
 field.value.text_long:
   type: mapping
@@ -64,6 +76,11 @@ field.field_settings.text_with_summary:
     required_summary:
       type: boolean
       label: 'Require summary'
+    allowed_formats:
+      type: sequence
+      label: 'Allowed text formats'
+      sequence:
+        type: string
 
 field.value.text_with_summary:
   type: mapping
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php b/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php
new file mode 100644
index 0000000000..80a2533c6d
--- /dev/null
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextFieldItemList.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\text\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Defines an item list class for text fields.
+ */
+class TextFieldItemList extends FieldItemList {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
+    if ($allowed_formats = $this->getSetting('allowed_formats') && !empty($allowed_formats)) {
+      $field_name = $this->definition->getName();
+      $submitted_values = $form_state->getValue([
+        'default_value_input',
+        $field_name,
+      ]);
+      foreach ($submitted_values as $delta => $value) {
+        if (!in_array($value['format'], $allowed_formats, TRUE)) {
+          $form_state->setErrorByName(
+            "default_value_input][{$field_name}][{$delta}][format",
+            $this->t("The selected text format is not allowed.")
+          );
+        }
+      }
+    }
+    parent::defaultValuesFormValidate($element, $form, $form_state);
+  }
+
+}
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
index 47a23dcf77..8926a4acb6 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextItem.php
@@ -14,7 +14,8 @@
  *   description = @Translation("This field stores a text with a text format."),
  *   category = @Translation("Text"),
  *   default_widget = "text_textfield",
- *   default_formatter = "text_default"
+ *   default_formatter = "text_default",
+ *   list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
  * )
  */
 class TextItem extends TextItemBase {
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
index 3fdd385d1e..e40849cbcd 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\TypedData\DataDefinition;
 
 /**
@@ -13,6 +14,62 @@
  */
 abstract class TextItemBase extends FieldItemBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultFieldSettings() {
+    return ['allowed_formats' => []] + parent::defaultFieldSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
+    $element = parent::fieldSettingsForm($form, $form_state);
+    $settings = $this->getSettings();
+
+    $element['allowed_formats'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Allowed text formats'),
+      '#options' => $this->get('format')->getPossibleOptions(),
+      '#default_value' => !empty($settings['allowed_formats']) ? $settings['allowed_formats'] : [],
+      '#description' => $this->t('Select the allowed text formats. If no formats are selected, all available text formats will be displayed to the user.'),
+      '#element_validate' => [[static::class, 'validateAllowedFormats']],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Render API callback: Processes the allowed formats value.
+   *
+   * Ensure the element's value is an indexed array of selected format IDs.
+   * This function is assigned as an #element_validate callback.
+   *
+   * @see static::fieldSettingsForm()
+   */
+  public static function validateAllowedFormats(array &$element, FormStateInterface $form_state) {
+    $value = array_values(array_filter($form_state->getValue($element['#parents'])));
+    $form_state->setValueForElement($element, $value);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
+    // Add explicitly allowed formats as config dependencies.
+    $format_dependencies = [];
+    $dependencies = parent::calculateDependencies($field_definition);
+    if (!is_null($field_definition->getSetting('allowed_formats'))) {
+      $format_dependencies = array_map(function (string $format_id) {
+        return 'filter.format.' . $format_id;
+      }, $field_definition->getSetting('allowed_formats'));
+    }
+    $config = $dependencies['config'] ?? [];
+    $dependencies['config'] = array_merge($config, $format_dependencies);
+    return $dependencies;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -22,7 +79,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
       ->setRequired(TRUE);
 
     $properties['format'] = DataDefinition::create('filter_format')
-      ->setLabel(t('Text format'));
+      ->setLabel(t('Text format'))
+      ->setSetting('allowed_formats', $field_definition->getSetting('allowed_formats'));
 
     $properties['processed'] = DataDefinition::create('string')
       ->setLabel(t('Processed text'))
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
index 15e4eb6a84..ce35873326 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextLongItem.php
@@ -13,7 +13,8 @@
  *   description = @Translation("This field stores a long text with a text format."),
  *   category = @Translation("Text"),
  *   default_widget = "text_textarea",
- *   default_formatter = "text_default"
+ *   default_formatter = "text_default",
+ *   list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
  * )
  */
 class TextLongItem extends TextItemBase {
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
index 9437c381fc..088986d576 100644
--- a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php
@@ -16,7 +16,8 @@
  *   description = @Translation("This field stores long text with a format and an optional summary."),
  *   category = @Translation("Text"),
  *   default_widget = "text_textarea_with_summary",
- *   default_formatter = "text_default"
+ *   default_formatter = "text_default",
+ *   list_class = "\Drupal\text\Plugin\Field\FieldType\TextFieldItemList"
  * )
  */
 class TextWithSummaryItem extends TextItemBase {
@@ -87,7 +88,7 @@ public function isEmpty() {
    * {@inheritdoc}
    */
   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
-    $element = [];
+    $element = parent::fieldSettingsForm($form, $form_state);
     $settings = $this->getSettings();
 
     $element['display_summary'] = [
diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig
new file mode 100644
index 0000000000..9437c381fc
--- /dev/null
+++ b/core/modules/text/src/Plugin/Field/FieldType/TextWithSummaryItem.php.orig
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\text\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Plugin implementation of the 'text_with_summary' field type.
+ *
+ * @FieldType(
+ *   id = "text_with_summary",
+ *   label = @Translation("Text (formatted, long, with summary)"),
+ *   description = @Translation("This field stores long text with a format and an optional summary."),
+ *   category = @Translation("Text"),
+ *   default_widget = "text_textarea_with_summary",
+ *   default_formatter = "text_default"
+ * )
+ */
+class TextWithSummaryItem extends TextItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultFieldSettings() {
+    return [
+      'display_summary' => 0,
+      'required_summary' => FALSE,
+    ] + parent::defaultFieldSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties = parent::propertyDefinitions($field_definition);
+
+    $properties['summary'] = DataDefinition::create('string')
+      ->setLabel(new TranslatableMarkup('Summary'));
+
+    $properties['summary_processed'] = DataDefinition::create('string')
+      ->setLabel(new TranslatableMarkup('Processed summary'))
+      ->setDescription(new TranslatableMarkup('The summary text with the text format applied.'))
+      ->setComputed(TRUE)
+      ->setClass('\Drupal\text\TextProcessed')
+      ->setSetting('text source', 'summary');
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    return [
+      'columns' => [
+        'value' => [
+          'type' => 'text',
+          'size' => 'big',
+        ],
+        'summary' => [
+          'type' => 'text',
+          'size' => 'big',
+        ],
+        'format' => [
+          'type' => 'varchar_ascii',
+          'length' => 255,
+        ],
+      ],
+      'indexes' => [
+        'format' => ['format'],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $value = $this->get('summary')->getValue();
+    return parent::isEmpty() && ($value === NULL || $value === '');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
+    $element = [];
+    $settings = $this->getSettings();
+
+    $element['display_summary'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Summary input'),
+      '#default_value' => $settings['display_summary'],
+      '#description' => $this->t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display type.'),
+    ];
+
+    $element['required_summary'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Require summary'),
+      '#description' => $this->t('The summary will also be visible when marked as required.'),
+      '#default_value' => $settings['required_summary'],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraints() {
+    $constraints = parent::getConstraints();
+    if ($this->getSetting('required_summary')) {
+      $manager = $this->getTypedDataManager()->getValidationConstraintManager();
+      $constraints[] = $manager->create('ComplexData', [
+        'summary' => [
+          'NotNull' => [
+            'message' => $this->t('The summary field is required for @name', ['@name' => $this->getFieldDefinition()->getLabel()]),
+          ],
+        ],
+      ]);
+    }
+    return $constraints;
+  }
+
+}
diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php
index fc02ac1e9c..bebc8ab55a 100644
--- a/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php
+++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextareaWidget.php
@@ -34,11 +34,17 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
     $main_widget = parent::formElement($items, $delta, $element, $form, $form_state);
+    $allowed_formats = $this->getFieldSetting('allowed_formats');
 
     $element = $main_widget['value'];
     $element['#type'] = 'text_format';
     $element['#format'] = $items[$delta]->format;
     $element['#base_type'] = $main_widget['value']['#type'];
+
+    if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) {
+      $element['#allowed_formats'] = $allowed_formats;
+    }
+
     return $element;
   }
 
diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
index 29934378f0..8942ec338d 100644
--- a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
+++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
@@ -25,11 +25,17 @@ class TextfieldWidget extends StringTextfieldWidget {
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
     $main_widget = parent::formElement($items, $delta, $element, $form, $form_state);
+    $allowed_formats = $this->getFieldSetting('allowed_formats');
 
     $element = $main_widget['value'];
     $element['#type'] = 'text_format';
     $element['#format'] = $items[$delta]->format ?? NULL;
     $element['#base_type'] = $main_widget['value']['#type'];
+
+    if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) {
+      $element['#allowed_formats'] = $allowed_formats;
+    }
+
     return $element;
   }
 
diff --git a/core/modules/text/tests/src/Functional/TextFieldTest.php b/core/modules/text/tests/src/Functional/TextFieldTest.php
index b68c0d1f37..54b0b87c3f 100644
--- a/core/modules/text/tests/src/Functional/TextFieldTest.php
+++ b/core/modules/text/tests/src/Functional/TextFieldTest.php
@@ -21,6 +21,13 @@ class TextFieldTest extends StringFieldTest {
     getTestFiles as drupalGetTestFiles;
   }
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = ['entity_test', 'file', 'field_ui'];
+
   /**
    * A user with relevant administrative privileges.
    *
@@ -39,7 +46,10 @@ class TextFieldTest extends StringFieldTest {
   protected function setUp(): void {
     parent::setUp();
 
-    $this->adminUser = $this->drupalCreateUser(['administer filters']);
+    $this->adminUser = $this->drupalCreateUser([
+      'administer filters',
+      'administer entity_test fields',
+    ]);
   }
 
   // Test fields.
@@ -157,13 +167,94 @@ public function testTextfieldWidgetsFormatted() {
     $this->_testTextfieldWidgetsFormatted('text_long', 'text_textarea');
   }
 
+  /**
+   * Test widgets for fields with selected allowed formats.
+   */
+  public function testTextfieldWidgetsAllowedFormats() {
+    // Create one text format.
+    $this->drupalLogin($this->adminUser);
+    $format1 = FilterFormat::create([
+      'format' => mb_strtolower($this->randomMachineName()),
+      'name' => $this->randomMachineName(),
+    ]);
+    $format1->save();
+
+    // Create a second text format.
+    $format2 = FilterFormat::create([
+      'format' => mb_strtolower($this->randomMachineName()),
+      'name' => $this->randomMachineName(),
+    ]);
+    $format2->save();
+
+    // Grant access to both formats to the user.
+    $roles = $this->webUser->getRoles();
+    $rid = $roles[0];
+    user_role_grant_permissions($rid, [
+      $format1->getPermissionName(),
+      $format2->getPermissionName(),
+    ]);
+
+    // Create a field with multiple formats allowed.
+    $field_name = mb_strtolower($this->randomMachineName());
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'text',
+    ]);
+    $field_storage->save();
+    $field = FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'entity_test',
+      'label' => $this->randomMachineName() . '_label',
+      'settings' => ['allowed_formats' => []],
+    ]);
+    $field->save();
+
+    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository */
+    $entity_display_repository = $this->container->get('entity_display.repository');
+    $entity_display_repository->getFormDisplay('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, [
+        'type' => 'text_textfield',
+      ])
+      ->save();
+    $entity_display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
+      ->setComponent($field_name)
+      ->save();
+
+    // Display the creation form.
+    $this->drupalLogin($this->webUser);
+    $this->drupalGet('entity_test/add');
+    $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL);
+    $this->assertSession()->optionExists("{$field_name}[0][format]", $format1->id());
+    $this->assertSession()->optionExists("{$field_name}[0][format]", $format2->id());
+    // Log back in as admin.
+    $this->drupalLogin($this->adminUser);
+    // Change field to allow only one format.
+    $path = "entity_test/structure/entity_test/fields/entity_test.entity_test.$field_name";
+    $this->drupalGet($path);
+    $this->submitForm(["settings[allowed_formats][{$format1->id()}]" => $format1->id()], 'Save settings');
+    $this->drupalGet($path);
+    // Display the creation form.
+    $this->drupalLogin($this->webUser);
+    // We shouldn't have the 'format' selector since only one format is allowed.
+    $this->drupalGet('entity_test/add');
+    $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL);
+    $this->assertSession()->fieldNotExists("{$field_name}[0][format]");
+
+    // Change field to allow all formats by configuring none as allowed.
+    $field->setSetting('allowed_formats', []);
+    $field->save();
+    $this->drupalGet('entity_test/add');
+    // We should see the 'format' selector again.
+    $this->assertSession()->fieldExists("{$field_name}[0][value]", NULL);
+    $this->assertSession()->optionExists("{$field_name}[0][format]", $format1->id());
+    $this->assertSession()->optionExists("{$field_name}[0][format]", $format2->id());
+  }
+
   /**
    * Helper function for testTextfieldWidgetsFormatted().
    */
   public function _testTextfieldWidgetsFormatted($field_type, $widget_type) {
-    /** @var \Drupal\Core\Render\RendererInterface $renderer */
-    $renderer = $this->container->get('renderer');
-
     // Create a field.
     $field_name = mb_strtolower($this->randomMachineName());
     $field_storage = FieldStorageConfig::create([
diff --git a/core/modules/text/tests/src/Kernel/TextItemBaseTest.php b/core/modules/text/tests/src/Kernel/TextItemBaseTest.php
index 50abbc2612..3087cb7fa3 100644
--- a/core/modules/text/tests/src/Kernel/TextItemBaseTest.php
+++ b/core/modules/text/tests/src/Kernel/TextItemBaseTest.php
@@ -3,6 +3,9 @@
 namespace Drupal\Tests\text\Kernel;
 
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\filter\Entity\FilterFormat;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\text\Plugin\Field\FieldType\TextItemBase;
 
@@ -16,7 +19,7 @@ class TextItemBaseTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['filter', 'text'];
+  protected static $modules = ['filter', 'text', 'entity_test', 'field'];
 
   /**
    * Tests creation of sample values.
@@ -57,4 +60,42 @@ public function providerTextFieldSampleValue() {
     ];
   }
 
+  /**
+   * @covers ::calculateDependencies
+   */
+  public function testCalculateDependencies() {
+    $format = FilterFormat::create([
+      'format' => 'test_format',
+      'name' => 'Test format',
+    ]);
+    $fieldName = mb_strtolower($this->randomMachineName());
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => $fieldName,
+      'entity_type' => 'entity_test',
+      'type' => 'text',
+    ]);
+    $field_storage->save();
+    $field = FieldConfig::create([
+      'field_name' => $fieldName,
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+      'settings' => [
+        'allowed_formats' => [$format->id()],
+      ],
+    ]);
+    $field->save();
+
+    $field->calculateDependencies();
+    $this->assertEquals([
+      'module' => [
+        'entity_test',
+        'text',
+      ],
+      'config' => [
+        "field.storage.entity_test.$fieldName",
+        'filter.format.test_format',
+      ],
+    ], $field->getDependencies());
+  }
+
 }
diff --git a/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php b/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php
index 6630330bdb..9559f03eea 100644
--- a/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php
+++ b/core/modules/text/tests/src/Kernel/TextWithSummaryItemTest.php
@@ -67,7 +67,7 @@ public function testCrudAndUpdate() {
     $entity = $storage->create();
     $entity->summary_field->value = $value = $this->randomMachineName();
     $entity->summary_field->summary = $summary = $this->randomMachineName();
-    $entity->summary_field->format = NULL;
+    $entity->summary_field->format = 'plain_text';
     $entity->name->value = $this->randomMachineName();
     $entity->save();
 
@@ -76,7 +76,7 @@ public function testCrudAndUpdate() {
     $this->assertInstanceOf(FieldItemInterface::class, $entity->summary_field[0]);
     $this->assertEquals($value, $entity->summary_field->value);
     $this->assertEquals($summary, $entity->summary_field->summary);
-    $this->assertNull($entity->summary_field->format);
+    $this->assertEquals('plain_text', $entity->summary_field->format);
     // Even if no format is given, if text processing is enabled, the default
     // format is used.
     $this->assertEquals("<p>{$value}</p>\n", $entity->summary_field->processed);
@@ -115,6 +115,9 @@ protected function createField($entity_type) {
     $this->field = FieldConfig::create([
       'field_storage' => $this->fieldStorage,
       'bundle' => $entity_type,
+      'settings' => [
+        'allowed_formats' => ['plain_text'],
+      ],
     ]);
     $this->field->save();
   }
diff --git a/core/modules/text/text.post_update.php b/core/modules/text/text.post_update.php
index 25322eb977..bcae897684 100644
--- a/core/modules/text/text.post_update.php
+++ b/core/modules/text/text.post_update.php
@@ -8,6 +8,8 @@
 use Drupal\Core\Config\Entity\ConfigEntityUpdater;
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
 use Drupal\text\Plugin\Field\FieldWidget\TextareaWithSummaryWidget;
+use Drupal\text\Plugin\Field\FieldType\TextItemBase;
+use Drupal\field\FieldConfigInterface;
 
 /**
  * Implements hook_removed_post_updates().
@@ -46,3 +48,24 @@ function text_post_update_add_required_summary_flag_form_display(&$sandbox = NUL
 
   $config_entity_updater->update($sandbox, 'entity_form_display', $widget_callback);
 }
+
+/**
+ * Add allowed_formats setting to existing text fields.
+ */
+function test_post_update_allowed_formats(&$sandbox = NULL) {
+  \Drupal::classResolver(ConfigEntityUpdater::class)
+    ->update($sandbox, 'field_config', function (FieldConfigInterface $field_config) {
+      $class = get_class($field_config);
+      // Deal only with text fields and descendants.
+      if ($class == TextItemBase::class || is_subclass_of($class, TextItemBase::class)) {
+        // Get the existing allowed_formats setting.
+        $allowed_formats = $field_config->get('settings.allowed_formats');
+        if (!is_array($allowed_formats) && empty($allowed_formats)) {
+          // Save default value if existing value not present.
+          $field_config->set('settings.allowed_formats', []);
+        }
+        return TRUE;
+      }
+      return FALSE;
+    });
+}
diff --git a/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php b/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php
index 8bf1de1183..dd248e2174 100644
--- a/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php
+++ b/core/modules/views/tests/src/Kernel/Entity/ViewEntityDependenciesTest.php
@@ -80,7 +80,10 @@ protected function setUp($import_test_views = TRUE): void {
       'field_storage' => FieldStorageConfig::loadByName('node', 'body'),
       'bundle' => $content_type->id(),
       'label' => $this->randomMachineName() . '_body',
-      'settings' => ['display_summary' => TRUE],
+      'settings' => [
+        'display_summary' => TRUE,
+        'allowed_formats' => [],
+      ],
     ])->save();
 
     ViewTestData::createTestViews(static::class, ['views_test_config']);
diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml b/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml
index dab4f98181..40968daa4d 100644
--- a/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml
+++ b/core/profiles/demo_umami/config/install/field.field.block_content.basic.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: false
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml
index 964a88887c..30cbbdbb05 100644
--- a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml
+++ b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_copyright.yml
@@ -16,5 +16,6 @@ required: false
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml
index d2d3c91850..1aab290261 100644
--- a/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml
+++ b/core/profiles/demo_umami/config/install/field.field.block_content.disclaimer_block.field_disclaimer.yml
@@ -16,5 +16,6 @@ required: false
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/profiles/demo_umami/config/install/field.field.node.article.body.yml b/core/profiles/demo_umami/config/install/field.field.node.article.body.yml
index b36fbd5844..66f00ac4a5 100644
--- a/core/profiles/demo_umami/config/install/field.field.node.article.body.yml
+++ b/core/profiles/demo_umami/config/install/field.field.node.article.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/profiles/demo_umami/config/install/field.field.node.page.body.yml b/core/profiles/demo_umami/config/install/field.field.node.page.body.yml
index 4ff17d0e71..c81d7034f3 100644
--- a/core/profiles/demo_umami/config/install/field.field.node.page.body.yml
+++ b/core/profiles/demo_umami/config/install/field.field.node.page.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml
index ff090cac35..788f56d189 100644
--- a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml
+++ b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_recipe_instruction.yml
@@ -16,5 +16,6 @@ required: true
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml
index b648b97889..7ea393bc51 100644
--- a/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml
+++ b/core/profiles/demo_umami/config/install/field.field.node.recipe.field_summary.yml
@@ -16,5 +16,6 @@ required: true
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/profiles/standard/config/install/field.field.block_content.basic.body.yml b/core/profiles/standard/config/install/field.field.block_content.basic.body.yml
index dab4f98181..40968daa4d 100644
--- a/core/profiles/standard/config/install/field.field.block_content.basic.body.yml
+++ b/core/profiles/standard/config/install/field.field.block_content.basic.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: false
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml b/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml
index 1337070d16..8d97e03507 100644
--- a/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml
+++ b/core/profiles/standard/config/install/field.field.comment.comment.comment_body.yml
@@ -16,5 +16,6 @@ required: true
 translatable: true
 default_value: {  }
 default_value_callback: ''
-settings: {  }
+settings:
+  allowed_formats: {  }
 field_type: text_long
diff --git a/core/profiles/standard/config/install/field.field.node.article.body.yml b/core/profiles/standard/config/install/field.field.node.article.body.yml
index b36fbd5844..66f00ac4a5 100644
--- a/core/profiles/standard/config/install/field.field.node.article.body.yml
+++ b/core/profiles/standard/config/install/field.field.node.article.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
diff --git a/core/profiles/standard/config/install/field.field.node.page.body.yml b/core/profiles/standard/config/install/field.field.node.page.body.yml
index 4ff17d0e71..c81d7034f3 100644
--- a/core/profiles/standard/config/install/field.field.node.page.body.yml
+++ b/core/profiles/standard/config/install/field.field.node.page.body.yml
@@ -19,4 +19,5 @@ default_value_callback: ''
 settings:
   display_summary: true
   required_summary: false
+  allowed_formats: {  }
 field_type: text_with_summary
