diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index df2a2fd..8554532 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -248,6 +248,26 @@ field.widget.settings.entity_reference_autocomplete:
       type: label
       label: 'Placeholder'
 
+field.widget.settings.machine_name:
+  type: mapping
+  label: 'Machine Name'
+  mapping:
+    source_field:
+      type: string
+      label: 'The source field for the machine name'
+    disable_on_edit:
+      type: boolean
+      label: 'Disable the machine name after initial creation'
+    exists:
+      type: string
+      label: 'A callable to invoke for checking whether a submitted machine name value already exists'
+    replace_pattern:
+      type: string
+      label: 'A regular expression (without delimiters) matching disallowed characters in the machine name'
+    replace:
+      type: string
+      label: 'A character to replace disallowed characters in the machine name'
+
 field.formatter.settings.boolean:
   type: mapping
   mapping:
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
index 6c31df5..482f12a 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
@@ -163,8 +163,15 @@ public function buildForm(FieldableEntityInterface $entity, array &$form, FormSt
     // Set #parents to 'top-level' by default.
     $form += ['#parents' => []];
 
+    // Sort the components by their weight in order to allow a form element from
+    // a widget to depend on the processed elements of another form element from
+    // another widget. For example, the 'machine_name' widget needs the
+    // processed '#id' property of its source field.
+    $components = $this->getComponents();
+    uasort($components, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
+
     // Let each widget generate the form elements.
-    foreach ($this->getComponents() as $name => $options) {
+    foreach ($components as $name => $options) {
       if ($widget = $this->getRenderer($name)) {
         $items = $entity->get($name);
         $items->filterEmptyItems();
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/MachineNameWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/MachineNameWidget.php
new file mode 100644
index 0000000..f1a40d3
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/MachineNameWidget.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'machine_name' field widget.
+ *
+ * @FieldWidget(
+ *   id = "machine_name",
+ *   label = @Translation("Machine name"),
+ *   field_types = {
+ *     "string"
+ *   }
+ * )
+ */
+class MachineNameWidget extends WidgetBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity field manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface;
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The element info manager.
+   *
+   * @var \Drupal\Core\Render\ElementInfoManagerInterface
+   */
+  protected $elementInfo;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityFieldManagerInterface $entity_field_manager, ElementInfoManagerInterface $element_info) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+
+    $this->entityFieldManager = $entity_field_manager;
+    $this->elementInfo = $element_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['third_party_settings'],
+      $container->get('entity_field.manager'),
+      $container->get('element_info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'source_field' => '',
+      'disable_on_edit' => TRUE,
+      'replace_pattern' => '[^a-z0-9_]+',
+      'replace' => '_',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
+    $form_display = $form_state->getFormObject()->getEntity();
+
+    $available_fields = $this->entityFieldManager->getFieldDefinitions($form_display->getTargetEntityTypeId(), $form_display->getTargetBundle());
+    $displayed_fields = $form_display->getComponents();
+
+    $options = [];
+    /**@var \Drupal\Core\Field\FieldDefinitionInterface $field */
+    foreach (array_intersect_key($available_fields, $displayed_fields) as $field_name => $field) {
+      // The source field can only be another string field.
+      if ($field->getType() === 'string' && $field->getName() !== $this->fieldDefinition->getName()) {
+        $options[$field_name] = $field->getLabel();
+      }
+    }
+    $element['source_field'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Source field'),
+      '#default_value' => $this->getSetting('source_field'),
+      '#options' => $options,
+      '#description' => $this->t('The field that should be used as a source for the machine name element. This field needs to be displayed in the entity form <em>before</em> the @field_label field.', ['@field_label' => $this->fieldDefinition->getLabel()]),
+    ];
+    $element['disable_on_edit'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Disable after initial creation'),
+      '#default_value' => $this->getSetting('disable_on_edit'),
+      '#description' => $this->t('Disable the machine name after the content has been saved for the first time.'),
+    ];
+    $element['replace_pattern'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Replace pattern'),
+      '#default_value' => $this->getSetting('replace_pattern'),
+      '#description' => $this->t('A regular expression (without delimiters) matching disallowed characters in the machine name.'),
+      '#size' => 30,
+    ];
+    $element['replace'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Replace character'),
+      '#default_value' => $this->getSetting('replace'),
+      '#description' => $this->t("A character to replace disallowed characters in the machine name. When using a different character than '_', <em>Replace pattern</em> needs to be set accordingly."),
+      '#size' => 1,
+      '#maxlength' => 1,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = [];
+
+    $field_definitions = $this->entityFieldManager->getFieldDefinitions($this->fieldDefinition->getTargetEntityTypeId(), $this->fieldDefinition->getTargetBundle());
+    if (!empty($this->getSetting('source_field')) && isset($field_definitions[$this->getSetting('source_field')])) {
+      $summary[] = $this->t('Source field: @source_field', ['@source_field' => $field_definitions[$this->getSetting('source_field')]->getLabel()]);
+      $summary[] = $this->t('Disable on edit: @disable_on_edit', ['@disable_on_edit' => $this->getSetting('disable_on_edit') ? $this->t('Yes') : $this->t('No')]);
+      $summary[] = $this->t('Replace pattern: @replace_pattern', ['@replace_pattern' => $this->getSetting('replace_pattern')]);
+      $summary[] = $this->t('Replace character: @replace', ['@replace' => $this->getSetting('replace')]);
+    }
+    else {
+      $summary[] = $this->t('<em>Missing configuration</em>.');
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $element_info = $this->elementInfo->getInfo('machine_name');
+    $element['value'] = $element + [
+      '#type' => 'machine_name',
+      '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL,
+      '#maxlength' => $this->getFieldSetting('max_length'),
+      '#source_field' => $this->getSetting('source_field'),
+      '#process' => array_merge([[get_class($this), 'processMachineNameSource']], $element_info['#process']),
+      '#machine_name' => [
+        'exists' => [get_called_class(), 'exists'],
+        'label' => $this->fieldDefinition->getLabel(),
+        'replace_pattern' => $this->getSetting('replace_pattern'),
+        'replace' => $this->getSetting('replace'),
+      ],
+      '#disabled' => $this->getSetting('disable_on_edit') && !$items->getEntity()->isNew(),
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Form API callback: Sets the 'source' property of a machine_name element.
+   *
+   * This method is assigned as a #process callback in formElement() method.
+   */
+  public static function processMachineNameSource($element, FormStateInterface $form_state, $form) {
+    $source_field_state = static::getWidgetState($form['#parents'], $element['#source_field'], $form_state);
+
+    // Hide the field widget if the source field is not configured properly or
+    // if it doesn't exist in the form.
+    if (empty($element['#source_field']) || empty($source_field_state['array_parents'])) {
+      $element['#access'] = FALSE;
+    }
+    else {
+      $source_field_element = NestedArray::getValue($form_state->getCompleteForm(), $source_field_state['array_parents']);
+      $element['#machine_name']['source'] = $source_field_element[$element['#delta']]['value']['#array_parents'];
+    }
+
+    return $element;
+  }
+
+  /**
+   * Determines if the source field value already exists.
+   *
+   * @param mixed $value
+   *   The value entered into the machine name widget.
+   * @param array $element
+   *   The form element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return bool
+   *   TRUE if the source field value already exists, FALSE otherwise.
+   */
+  public static function exists($value, array $element, FormStateInterface $form_state) {
+    return (bool) \Drupal::entityTypeManager()
+      ->getStorage($form_state->getFormObject()->getEntity()->getEntityTypeId())
+      ->getQuery()
+      ->condition(reset($element['#array_parents']), $value, '=')
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    // This widget is available only to entity identifier fields.
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($field_definition->getTargetEntityTypeId());
+
+    return $field_definition->getName() === $entity_type->getKey('id');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
index 85a5e66..05c322e 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
@@ -50,7 +50,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setReadOnly(TRUE)
       // In order to work around the InnoDB 191 character limit on utf8mb4
       // primary keys, we set the character set for the field to ASCII.
-      ->setSetting('is_ascii', TRUE);
+      ->setSetting('is_ascii', TRUE)
+      ->setDisplayConfigurable('form', TRUE);
+
+    // Make the label field configurable in the UI.
+    $fields['name']->setDisplayConfigurable('form', TRUE);
+
     return $fields;
   }
 
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/Widget/MachineNameWidgetTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/Widget/MachineNameWidgetTest.php
new file mode 100644
index 0000000..e7313f2
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Field/Widget/MachineNameWidgetTest.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests\Core\Field\Widget;
+
+use Drupal\entity_test\Entity\EntityTestStringId;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests for the machine name field widget.
+ *
+ * @group Field
+ */
+class MachineNameWidgetTest extends JavascriptTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * Node is required because the machine name callback checks for the
+   * "access content" permission.
+   *
+   * @var array
+   */
+  public static $modules = ['entity_test', 'field', 'field_ui', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create a web user.
+    $this->drupalLogin($this->drupalCreateUser(['view test entity', 'administer entity_test content', 'administer entity_test_string_id form display']));
+  }
+
+  /**
+   * Tests the machine name field widget.
+   */
+  public function testMachineNameWidget() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // First, make sure that both the ID and the label fields are initially
+    // hidden in the form display.
+    entity_get_form_display('entity_test_string_id', 'entity_test_string_id', 'default')
+      ->removeComponent('id')
+      ->removeComponent('name')
+      ->save();
+
+    // Go to the entity add form and check that the test fields are not
+    // displayed because they are not configured in the form display yet.
+    $this->drupalGet('/entity_test_string_id/add');
+    $assert_session->fieldNotExists('id[0][value]');
+    $assert_session->fieldNotExists('name[0][value]');
+
+    // Configure the test field to use the machine name widget with no initial
+    // settings.
+    entity_get_form_display('entity_test_string_id', 'entity_test_string_id', 'default')
+      ->setComponent('id', [
+        'type' => 'machine_name',
+        'weight' => 5,
+      ])
+      ->save();
+
+    // Check that the widget displays an "error" summary when it has missing or
+    // broken settings.
+    $this->drupalGet('/entity_test_string_id/structure/entity_test_string_id/form-display');
+    $assert_session->pageTextContains('Missing configuration.');
+
+    // Check that test field is configured in the form display while the source
+    // field is not.
+    $this->assertTrue($assert_session->optionExists('fields[id][region]', 'content')->isSelected());
+    $this->assertTrue($assert_session->optionExists('fields[name][region]', 'hidden')->isSelected());
+
+    // Open the widget settings form and check that a field which is not present
+    // in the form display can not be selected as a 'source_field'.
+    $page->pressButton('id_settings_edit');
+    $assert_session->waitForField('fields[id][settings_edit_form][settings][source_field]');
+    $assert_session->optionNotExists('fields[id][settings_edit_form][settings][source_field]', 'name');
+
+    // Go to the entity add form and check that the test field is not displayed,
+    // even when it is enabled in the form display, because of missing settings.
+    $this->drupalGet('/entity_test_string_id/add');
+    $assert_session->fieldNotExists('id[0][value]');
+    $assert_session->fieldNotExists('name[0][value]');
+
+    // Enable the 'source' field in the entity form display and configure the
+    // test field to use it.
+    entity_get_form_display('entity_test_string_id', 'entity_test_string_id', 'default')
+      ->setComponent('name', [
+        'type' => 'string_textfield',
+        'weight' => 10,
+      ])
+      ->save();
+
+    // Go to the form display and check that the machine name widget is only
+    // available for the ID field.
+    $this->drupalGet('/entity_test_string_id/structure/entity_test_string_id/form-display');
+    $assert_session->optionExists('edit-fields-name-type', 'string_textfield');
+    $assert_session->optionNotExists('edit-fields-name-type', 'machine_name');
+
+    $assert_session->optionExists('edit-fields-id-type', 'string_textfield');
+    $assert_session->optionExists('edit-fields-id-type', 'machine_name');
+
+    // Configure the test field to use the newly enabled 'source' field as the
+    // machine name source.
+    $page->pressButton('id_settings_edit');
+    $assert_session->waitForField('fields[id][settings_edit_form][settings][source_field]');
+    $page->selectFieldOption('fields[id][settings_edit_form][settings][source_field]', 'name');
+    $page->pressButton('id_plugin_settings_update');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->pageTextContains('Source field: Name');
+    $assert_session->pageTextContains('Replace pattern: [^a-z0-9_]+');
+    $assert_session->pageTextContains('Replace character: _');
+
+    $this->submitForm([], 'Save');
+    $assert_session->pageTextContains('Your settings have been saved.');
+
+    // Go to the entity add form and check that the test field is still not
+    // displayed, even when it has a source field configured, because the weight
+    // of the source field in the form display is higher than the weight of the
+    // test field.
+    $this->drupalGet('/entity_test_string_id/add');
+    $assert_session->fieldNotExists('id[0][value]');
+    $assert_session->fieldExists('name[0][value]');
+
+    // Configure the source field to have a lower weight than the test field,
+    // which will finally make it appear in the entity form.
+    entity_get_form_display('entity_test_string_id', 'entity_test_string_id', 'default')
+      ->setComponent('name', [
+        'type' => 'string_textfield',
+        'weight' => 4,
+      ])
+      ->save();
+
+    $this->drupalGet('/entity_test_string_id/add');
+    $test_source_field = $page->findField('name[0][value]');
+    $id = $page->findField('id[0][value]');
+    $id_machine_name_value = $page->find('css', '#edit-name-0-value-machine-name-suffix .machine-name-value');
+    $this->assertNotEmpty($test_source_field);
+    $this->assertNotEmpty($id);
+    $this->assertNotEmpty($id_machine_name_value, 'Test field with the machine name widget has been initialized.');
+
+    $test_values = [
+      'input' => 'Test value !0-9@',
+      'expected' => 'test_value_0_9_',
+    ];
+    $test_source_field->setValue($test_values['input']);
+
+    // Wait the set timeout for fetching the machine name.
+    $this->assertJsCondition('jQuery("#edit-name-0-value-machine-name-suffix .machine-name-value").html() == "' . $test_values['expected'] . '"');
+
+    // Validate the generated machine name.
+    $this->assertEquals($test_values['expected'], $id_machine_name_value->getHtml());
+
+    // Submit the entity form.
+    $this->submitForm([], 'Save');
+
+    // Load the entity and check that machine name value that was saved is
+    // correct.
+    $entity = EntityTestStringId::load('test_value_0_9_');
+    $this->assertSame($test_values['input'], $entity->name->value);
+    $this->assertSame($test_values['expected'], $entity->id->value);
+
+    // Try changing the 'replace_pattern' and 'replace' settings of the widget.
+    entity_get_form_display('entity_test_string_id', 'entity_test_string_id', 'default')
+      ->setComponent('id', [
+        'type' => 'machine_name',
+        'weight' => 5,
+        'settings' => [
+          'source_field' => 'name',
+          'replace_pattern' => '[^a-z0-9-]+',
+          'replace' => '-',
+        ],
+      ])
+      ->save();
+
+    $this->drupalGet('/entity_test_string_id/add');
+    $test_source_field = $page->findField('name[0][value]');
+    $id_machine_name_value = $page->find('css', '#edit-name-0-value-machine-name-suffix .machine-name-value');
+
+    $test_values = [
+      'input' => 'Test value2 !0-9@',
+      'expected' => 'test-value2-0-9-',
+    ];
+    $test_source_field->setValue($test_values['input']);
+
+    // Wait the set timeout for fetching the machine name.
+    $this->assertJsCondition('jQuery("#edit-name-0-value-machine-name-suffix .machine-name-value").html() == "' . $test_values['expected'] . '"');
+
+    // Validate the generated machine name.
+    $this->assertEquals($test_values['expected'], $id_machine_name_value->getHtml());
+
+    // Submit the entity form.
+    $this->submitForm([], 'Save');
+
+    // Load the entity and check that machine name value that was saved is
+    // correct.
+    $entity = EntityTestStringId::load('test-value2-0-9-');
+    $this->assertSame($test_values['input'], $entity->name->value);
+    $this->assertSame($test_values['expected'], $entity->id->value);
+
+    // Repeat the steps above in order to check that entering an existing value
+    // in the machine name widget throws an error.
+    $this->drupalGet('/entity_test_string_id/add');
+    $test_source_field = $page->findField('name[0][value]');
+    $id_machine_name_value = $page->find('css', '#edit-name-0-value-machine-name-suffix .machine-name-value');
+
+    $test_values = [
+      'input' => 'Test value2 !0-9@',
+      'expected' => 'test-value2-0-9-',
+    ];
+    $test_source_field->setValue($test_values['input']);
+
+    // Wait the set timeout for fetching the machine name.
+    $this->assertJsCondition('jQuery("#edit-name-0-value-machine-name-suffix .machine-name-value").html() == "' . $test_values['expected'] . '"');
+
+    // Validate the generated machine name.
+    $this->assertEquals($test_values['expected'], $id_machine_name_value->getHtml());
+
+    // Submit the entity form.
+    $this->submitForm([], 'Save');
+
+    // Check that a form-level error has been thrown.
+    $assert_session->pageTextContains('The machine-readable name is already in use. It must be unique.');
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
index bf2643d..bf46bb7 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
@@ -5,9 +5,9 @@
 use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
 
 /**
- * Tests for the machine name field.
+ * Tests for the machine name form element.
  *
- * @group field
+ * @group Form
  */
 class MachineNameTest extends JavascriptTestBase {
 
