diff --git a/core/modules/workflows/src/Entity/Workflow.php b/core/modules/workflows/src/Entity/Workflow.php
index 51bdda2b24..00876528df 100644
--- a/core/modules/workflows/src/Entity/Workflow.php
+++ b/core/modules/workflows/src/Entity/Workflow.php
@@ -154,7 +154,7 @@ public function addState($state_id, $label) {
     if (isset($this->states[$state_id])) {
       throw new \InvalidArgumentException("The state '$state_id' already exists in workflow '{$this->id()}'");
     }
-    if (preg_match('/[^a-z0-9_]+/', $state_id)) {
+    if (preg_match('/[^a-z0-9_]+|^[0-9]+$/', $state_id)) {
       throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores");
     }
     $this->states[$state_id] = [
@@ -271,7 +271,7 @@ public function addTransition($transition_id, $label, array $from_state_ids, $to
     if (isset($this->transitions[$transition_id])) {
       throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow '{$this->id()}'");
     }
-    if (preg_match('/[^a-z0-9_]+/', $transition_id)) {
+    if (preg_match('/[^a-z0-9_]+|^[0-9]+$/', $transition_id)) {
       throw new \InvalidArgumentException("The transition ID '$transition_id' must contain only lowercase letters, numbers, and underscores");
     }
 
diff --git a/core/modules/workflows/src/Form/WorkflowStateAddForm.php b/core/modules/workflows/src/Form/WorkflowStateAddForm.php
index 0b368585b7..59300ee9ac 100644
--- a/core/modules/workflows/src/Form/WorkflowStateAddForm.php
+++ b/core/modules/workflows/src/Form/WorkflowStateAddForm.php
@@ -39,7 +39,9 @@ public function form(array $form, FormStateInterface $form_state) {
       '#type' => 'machine_name',
       '#machine_name' => [
         'exists' => [$this, 'exists'],
+        'replace_pattern' => '[^a-z0-9_]+|^[0-9]+$',
       ],
+      '#required' => TRUE,
     ];
 
     // Add additional form fields from the workflow type plugin.
@@ -82,16 +84,22 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
     /** @var \Drupal\workflows\WorkflowInterface $entity */
     $values = $form_state->getValues();
 
-    // This is fired twice so we have to check that the entity does not already
-    // have the state.
-    if (!$entity->hasState($values['id'])) {
-      $entity->addState($values['id'], $values['label']);
-      if (isset($values['type_settings'])) {
-        $configuration = $entity->getTypePlugin()->getConfiguration();
-        $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-        $entity->set('type_settings', $configuration);
+    try {
+      // This is fired twice so we have to check that the entity does not already
+      // have the state.
+      if (!$entity->hasState($values['id'])) {
+        $entity->addState($values['id'], $values['label']);
+        if (isset($values['type_settings'])) {
+          $configuration = $entity->getTypePlugin()->getConfiguration();
+          $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()
+            ->getPluginId()];
+          $entity->set('type_settings', $configuration);
+        }
       }
     }
+    catch (\Exception $e) {
+      // Do nothing, if there is an exception we want validation to handle it.
+    }
   }
 
   /**
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
index fe3a40636d..59772dea65 100644
--- a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
+++ b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
@@ -40,7 +40,9 @@ public function form(array $form, FormStateInterface $form_state) {
       '#type' => 'machine_name',
       '#machine_name' => [
         'exists' => [$this, 'exists'],
+        'replace_pattern' => '[^a-z0-9_]+|^[0-9]+$',
       ],
+      '#required' => TRUE,
     ];
 
     // @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that
@@ -102,13 +104,19 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
       // Only do something once form validation is complete.
       return;
     }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
+    try {
+      /** @var \Drupal\workflows\WorkflowInterface $entity */
+      $values = $form_state->getValues();
+      $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
+      if (isset($values['type_settings'])) {
+        $configuration = $entity->getTypePlugin()->getConfiguration();
+        $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()
+          ->getPluginId()];
+        $entity->set('type_settings', $configuration);
+      }
+    }
+    catch (\Exception $e) {
+      // Do nothing, if there is an exception we want validation to handle it.
     }
   }
 
diff --git a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
index 0411915716..e3aa57023a 100644
--- a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
+++ b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
@@ -111,6 +111,12 @@ public function testWorkflowCreation() {
     $workflow = $workflow_storage->loadUnchanged('test');
     $this->assertFalse($workflow->getState('draft')->canTransitionTo('draft'), 'Can not transition from draft to draft');
 
+    $this->clickLink('Add a new state');
+    // Don't allow numeric only machine names.
+    $this->submitForm(['label' => 'foo', 'id' => '123'], 'Save');
+    $this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
+
+    $this->drupalGet('/admin/config/workflow/workflows/manage/test');
     $this->clickLink('Add a new transition');
     $this->submitForm(['id' => 'publish', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published'], 'Save');
     $this->assertSession()->pageTextContains('Created Publish transition.');
@@ -118,6 +124,11 @@ public function testWorkflowCreation() {
     $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'), 'Can transition from draft to published');
 
     $this->clickLink('Add a new transition');
+    $this->submitForm(['id' => '123', 'label' => 'Foo', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save');
+    $this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
+
+    $this->drupalGet('/admin/config/workflow/workflows/manage/test');
+    $this->clickLink('Add a new transition');
     $this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save');
     $this->assertSession()->pageTextContains('Created Create new draft transition.');
     $workflow = $workflow_storage->loadUnchanged('test');
