diff --git a/core/core.services.yml b/core/core.services.yml
index 459503e44c..b7755a0fb4 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -997,6 +997,10 @@ services:
     class: Drupal\Core\Routing\Enhancer\FormRouteEnhancer
     tags:
       - { name: route_enhancer }
+  route_enhancer.form.json_schema:
+    class: Drupal\Core\Routing\Enhancer\JsonSchemaFormRouteEnhancer
+    tags:
+      - { name: route_enhancer }
   route_enhancer.entity:
     class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
     tags:
@@ -1061,6 +1065,12 @@ services:
   controller.entity_form:
     class: Drupal\Core\Entity\HtmlEntityFormController
     arguments: ['@controller_resolver', '@form_builder', '@entity.manager']
+  controller.form.json_schema:
+    class: Drupal\Core\Form\JsonSchemaFormController
+    arguments: ['@controller_resolver', '@form_builder', '@class_resolver']
+  controller.entity_form.json_schema:
+    class: Drupal\Core\Form\JsonSchemaEntityFormController
+    arguments: ['@controller_resolver', '@form_builder', '@entity.manager']
   form_ajax_response_builder:
     class: Drupal\Core\Form\FormAjaxResponseBuilder
     arguments: ['@main_content_renderer.ajax', '@current_route_match']
diff --git a/core/lib/Drupal/Core/Form/JsonSchemaEntityFormController.php b/core/lib/Drupal/Core/Form/JsonSchemaEntityFormController.php
new file mode 100644
index 0000000000..175d6b155b
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/JsonSchemaEntityFormController.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Entity\HtmlEntityFormController;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a wrapping controller for JSON Schema entity forms.
+ */
+class JsonSchemaEntityFormController extends HtmlEntityFormController {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContentResult(Request $request, RouteMatchInterface $route_match) {
+    $form = parent::getContentResult($request, $route_match);
+    $json_schema_form_builder = new JsonSchemaFormBuilder();
+    return new JsonResponse($json_schema_form_builder->build($form));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/JsonSchemaFormBuilder.php b/core/lib/Drupal/Core/Form/JsonSchemaFormBuilder.php
new file mode 100644
index 0000000000..a534b12cce
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/JsonSchemaFormBuilder.php
@@ -0,0 +1,358 @@
+<?php
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Builds a JSON Schema representation of a form.
+ */
+class JsonSchemaFormBuilder {
+
+  /**
+   * Builds the JSON Schema for a given form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   *
+   * @return array
+   *   The array of data expected by JSON Schema.
+   */
+  public function build(array $form) {
+    return [
+      'schema' => $this->buildSchema($form),
+      'uiSchema' => $this->buildUiSchema($form),
+      'formData' => $this->buildFormData($form),
+    ];
+  }
+
+  /**
+   * Recursively builds the schema.
+   *
+   * @param array $parent
+   *   The parent element.
+   * @param array $result
+   *   The array passed through for gathering the recursive data.
+   *
+   * @return array
+   *   The schema.
+   */
+  protected function buildSchema(array $parent, array $result = []) {
+    $keys = Element::children($parent);
+    foreach ($keys as $key) {
+      $recurse = TRUE;
+
+      $element = $parent[$key];
+      $row = [];
+      if (isset($element['#type'])) {
+        $type = $this->deriveSchemaType($element);
+        if ($type === FALSE) {
+          continue;
+        }
+        if ($type) {
+          $row['type'] = $type;
+        }
+
+        if (in_array($element['#type'], ['checkboxes', 'radios', 'select'], TRUE)) {
+          // If no options are defined, skip this element.
+          if (empty($element['#options'])) {
+            continue;
+          }
+
+          $row['enum'] = array_keys($element['#options']);
+          $values = array_values($element['#options']);
+          if ($values !== $row['enum']) {
+            $row['enumNames'] = $values;
+          }
+
+          // @todo Does this apply to radios too?
+          if ($element['#type'] === 'checkboxes') {
+            $row = ['items' => $row];
+            $row['uniqueItems'] = TRUE;
+            $row['type'] = 'array';
+          }
+
+          if (in_array($element['#type'], ['checkboxes', 'radios'], TRUE)) {
+            $recurse = FALSE;
+          }
+        }
+      }
+
+      if ((!isset($element['#type']) || $element['#type'] === 'markup') && isset($element['#markup'])) {
+        $row['type'] = 'string';
+      }
+
+      if ($recurse) {
+        $row = $this->buildSchema($element, $row);
+      }
+
+      if ($row) {
+        $result += [
+          'type' => 'object',
+          // Explicitly set the title to an empty string if none exists.
+          'title' => '',
+        ];
+
+        $result['properties'][$key] = $row;
+
+        if (!empty($element['#required'])) {
+          $result['required'][] = $key;
+        }
+      }
+    }
+
+    if (($result || $keys) && isset($parent['#title'])) {
+      $result['title'] = (string) $parent['#title'];
+    }
+
+    return $result;
+  }
+
+  /**
+   * Derives the schema type for a given element.
+   *
+   * @param array $element
+   *   The form element.
+   *
+   * @return string
+   *   The schema type.
+   */
+  protected function deriveSchemaType(array $element) {
+    $type = $element['#type'];
+    switch ($type) {
+      case 'textarea':
+      case 'textfield':
+      case 'token':
+      case 'item':
+      case 'date':
+      case 'datelist':
+      case 'datetime':
+      case 'color':
+      case 'email':
+      case 'path':
+      case 'url':
+      case 'search':
+      case 'tel':
+      case 'machine_name':
+      case 'markup':
+      case 'password':
+      case 'link':
+      case 'submit':
+        return 'string';
+
+      case 'checkbox':
+        return 'boolean';
+
+      case 'number':
+      case 'range':
+        return 'number';
+
+      case 'radios':
+      case 'checkboxes':
+      case 'select':
+        return gettype(key($element['#options']));
+
+      // Do not print these to the page.
+      case 'value':
+        return FALSE;
+
+      // @todo Should probably print some wrapper stuff.
+      case 'container':
+      case 'details':
+      case 'fieldgroup':
+      case 'fieldset':
+      case 'vertical_tabs':
+      case 'actions':
+        return NULL;
+
+      // @todo Should be dealt with.
+      case 'dropbutton':
+      case 'operations':
+      case 'html_tag':
+      case 'inline_template':
+      case 'label':
+      case 'more_link':
+      case 'system_compact_link':
+      case 'page_title':
+      case 'status_messages':
+      case 'managed_file':
+      case 'file':
+      case 'entity_autocomplete':
+      case 'language_select':
+        return NULL;
+
+      // @todo What do we do with this?
+      case 'pager':
+      case 'ajax':
+        return NULL;
+
+      // @todo WTF?
+      case 'status_report':
+      case 'status_report_page':
+        return NULL;
+
+      // Pass through to the value checking below.
+      case 'hidden':
+        break;
+
+      default:
+        throw new \Exception(sprintf('Unknown type: "%s"', $type));
+    }
+
+    $value = isset($element['#input']) ? gettype($element['#value']) : NULL;
+    switch ($value) {
+      case 'boolean':
+      case 'string':
+      case 'integer':
+        return $value;
+
+      // @todo.
+      case 'array':
+        return NULL;
+
+      default:
+        throw new \Exception(sprintf('Unknown value "%s" for type: "%s"', $type, $value));
+    }
+  }
+
+  /**
+   * Recursively builds the UI schema.
+   *
+   * @param array $parent
+   *   The parent element.
+   * @param array $result
+   *   The array passed through for gathering the recursive data.
+   *
+   * @return array
+   *   The UI schema.
+   */
+  protected function buildUiSchema(array $parent, array $result = []) {
+    foreach (Element::children($parent) as $key) {
+      $element = $parent[$key];
+
+      // @todo Move this to FormBuilder.
+      if (!isset($element['#type']) && (isset($element['#markup']) || isset($element['#plain_text']))) {
+        $element['#type'] = 'markup';
+      }
+
+      $row = $this->handleWidgetType($element);
+
+      if (!isset($element['#type']) || !in_array($element['#type'], ['checkboxes', 'radios'], TRUE)) {
+        $row = $this->buildUiSchema($element, $row);
+      }
+
+      if ($row) {
+        $result[$key] = $row;
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * Allows individual widget types to control the UI schema.
+   *
+   * @param array $element
+   *   The form element.
+   *
+   * @return array
+   *   UI schema information specific to the widget type.
+   */
+  protected function handleWidgetType(array $element) {
+    if (!isset($element['#type'])) {
+      return [];
+    }
+
+    $type = $element['#type'];
+    switch ($type) {
+      case 'hidden':
+      case 'value':
+      case 'token':
+        $row['ui:widget'] = 'hidden';
+        break;
+
+      case 'link':
+        $row['ui:widget'] = 'link';
+        $row['ui:options']['label'] = FALSE;
+        if (!empty($element['#url'])) {
+          $row['ui:options']['title'] = (string) $element['#title'];
+          $row['ui:options']['url'] = $element['#url']->toString();
+        }
+        break;
+
+      case 'checkboxes':
+        $row['ui:widget'] = 'checkboxes';
+        break;
+
+      case 'radios':
+        $row['ui:widget'] = 'radio';
+        break;
+
+      case 'radio':
+      case 'checkbox':
+        $row['ui:widget'] = $type;
+        $row['ui:options']['label'] = FALSE;
+        break;
+
+      case 'markup':
+        $row['ui:options']['label'] = FALSE;
+      case 'item':
+        $row['ui:widget'] = $type;
+        if (isset($element['#plain_text']) && $element['#plain_text'] !== '') {
+          $row['ui:options']['markup'] = (string) $element['#plain_text'];
+        }
+        elseif (isset($element['#markup']) && $element['#markup'] !== '') {
+          $row['ui:options']['markup'] = (string) $element['#markup'];
+        }
+        break;
+
+      case 'submit':
+        $row['ui:widget'] = 'submit';
+        $row['ui:options']['label'] = FALSE;
+        if (isset($element['#name'])) {
+          $row['ui:options']['name'] = $element['#name'];
+        }
+        break;
+
+      // No custom widget needed.
+      default:
+        $row = [];
+    }
+
+    if (isset($element['#description'])) {
+      $row['ui:description'] = $element['#description'];
+    }
+
+    return $row;
+  }
+
+  /**
+   * Recursively builds the form data.
+   *
+   * @param array $parent
+   *   The parent element.
+   * @param array $result
+   *   The array passed through for gathering the recursive data.
+   *
+   * @return array
+   *   The form data.
+   */
+  protected function buildFormData(array $parent, array $result = []) {
+    foreach (Element::children($parent) as $key) {
+      $element = $parent[$key];
+
+      $value = isset($element['#value']) ? $element['#value'] : [];
+
+      if (!isset($element['#type']) || !in_array($element['#type'], ['checkboxes', 'radios'], TRUE)) {
+        $value = $this->buildFormData($element, $value);
+      }
+
+      // Retain 0 and FALSE, but ignore other empty values.
+      if (!in_array($value, [NULL, [], ''], TRUE)) {
+        $result[$key] = $value;
+      }
+    }
+
+    return $result;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/JsonSchemaFormController.php b/core/lib/Drupal/Core/Form/JsonSchemaFormController.php
new file mode 100644
index 0000000000..84cd65d770
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/JsonSchemaFormController.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Controller\HtmlFormController;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a wrapping controller for JSON Schema forms.
+ */
+class JsonSchemaFormController extends HtmlFormController {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContentResult(Request $request, RouteMatchInterface $route_match) {
+    $form = parent::getContentResult($request, $route_match);
+    $json_schema_form_builder = new JsonSchemaFormBuilder();
+    return new JsonResponse($json_schema_form_builder->build($form));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/JsonSchemaFormRouteEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/JsonSchemaFormRouteEnhancer.php
new file mode 100644
index 0000000000..da373a0c56
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/Enhancer/JsonSchemaFormRouteEnhancer.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Core\Routing\Enhancer;
+
+use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @todo.
+ */
+class JsonSchemaFormRouteEnhancer implements RouteEnhancerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Route $route) {
+    return ($route->hasDefault('_form') || $route->hasDefault('_entity_form')) && !$route->hasDefault('_controller') && $route->getRequirement('_format') === 'json_schema';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enhance(array $defaults, Request $request) {
+    if (isset($defaults['_entity_form'])) {
+      $defaults['_controller'] = 'controller.entity_form.json_schema:getContentResult';
+    }
+    else {
+      $defaults['_controller'] = 'controller.form.json_schema:getContentResult';
+    }
+    return $defaults;
+  }
+
+}
diff --git a/core/modules/filter/src/Element/TextFormat.php b/core/modules/filter/src/Element/TextFormat.php
index 16b4cf5207..547d0296a8 100644
--- a/core/modules/filter/src/Element/TextFormat.php
+++ b/core/modules/filter/src/Element/TextFormat.php
@@ -106,7 +106,7 @@ public static function processFormat(&$element, FormStateInterface $form_state,
       '#theme_wrappers',
     ];
     // Move this element into sub-element 'value'.
-    unset($element['value']);
+    unset($element['value'], $element['#type']);
     foreach (Element::properties($element) as $key) {
       if (!in_array($key, $blacklist)) {
         $element['value'][$key] = $element[$key];
diff --git a/core/tests/Drupal/KernelTests/Core/Form/JsonSchemaFormBuilderTest.php b/core/tests/Drupal/KernelTests/Core/Form/JsonSchemaFormBuilderTest.php
new file mode 100644
index 0000000000..71f2524e21
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Form/JsonSchemaFormBuilderTest.php
@@ -0,0 +1,417 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Form;
+
+use Drupal\Core\Form\JsonSchemaFormBuilder;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Form\JsonSchemaFormBuilder
+ * @group Form
+ */
+class JsonSchemaFormBuilderTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system'];
+
+  /**
+   * The JSON Schema form builder.
+   *
+   * @var \Drupal\Core\Form\JsonSchemaFormBuilder
+   */
+  protected $formBuilder;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installConfig('system');
+
+    $this->formBuilder = new JsonSchemaFormBuilder();
+  }
+
+  /**
+   * @dataProvider providerTestJsonSchemaForms
+   */
+  public function testJsonSchemaForms($form_arg, $expected) {
+    $form = \Drupal::formBuilder()->getForm($form_arg);
+    $actual = $this->formBuilder->build($form);
+
+    $this->assertArrayHasKey('formData', $actual);
+    $this->assertArrayHasKey('form_build_id', $actual['formData']);
+    // Copy the generated form build ID into the expected data.
+    $expected['formData']['form_build_id'] = $actual['formData']['form_build_id'];
+
+    $this->assertEquals($expected, $actual);
+    $this->assertSame($expected, $actual);
+  }
+
+  /**
+   * Provides test data for ::testJsonSchemaForms().
+   */
+  public function providerTestJsonSchemaForms() {
+    $data = [];
+    $data[] = [
+      TestForm::class,
+      [
+        'schema' => [
+          'type' => 'object',
+          'title' => 'The form title',
+          'properties' => [
+            'an_item' => [
+              'type' => 'string',
+              'title' => 'An item',
+            ],
+            'just_some_markup' => [
+              'type' => 'string',
+            ],
+            'section' => [
+              'type' => 'object',
+              'title' => '',
+              'properties' => [
+                'test1' => [
+                  'type' => 'string',
+                  'title' => 'Test 1',
+                ],
+                'test2' => [
+                  'type' => 'boolean',
+                  'title' => 'Test 2',
+                ],
+              ],
+              'required' => [
+                'test1',
+                'test2',
+              ],
+            ],
+            'all_the_things' => [
+              'type' => 'object',
+              'title' => '',
+              'properties' => [
+                'checkbox' => [
+                  'type' => 'boolean',
+                ],
+                'checkboxes' => [
+                  'items' => [
+                    'type' => 'string',
+                    'enum' => [
+                      'foo',
+                      'bar',
+                    ],
+                    'enumNames' => [
+                      'Foo',
+                      'Bar',
+                    ],
+                  ],
+                  'uniqueItems' => TRUE,
+                  'type' => 'array',
+                ],
+                'color' => [
+                  'type' => 'string',
+                ],
+                'date' => [
+                  'type' => 'string',
+                ],
+                'email' => [
+                  'type' => 'string',
+                ],
+                'hidden' => [
+                  'type' => 'string',
+                ],
+                'item' => [
+                  'type' => 'string',
+                ],
+                'link' => [
+                  'type' => 'string',
+                ],
+                'number' => [
+                  'type' => 'number',
+                ],
+                'password' => [
+                  'type' => 'string',
+                ],
+                'path' => [
+                  'type' => 'string',
+                ],
+                'radios' => [
+                  'type' => 'string',
+                  'enum' => [
+                    'foo',
+                    'bar',
+                  ],
+                  'enumNames' => [
+                    'Foo',
+                    'Bar',
+                  ],
+                ],
+                'range' => [
+                  'type' => 'number',
+                ],
+                'search' => [
+                  'type' => 'string',
+                ],
+                'select' => [
+                  'type' => 'string',
+                  'enum' => [
+                    'foo',
+                    'bar',
+                  ],
+                  'enumNames' => [
+                    'Foo',
+                    'Bar',
+                  ],
+                ],
+                'submit' => [
+                  'type' => 'string',
+                ],
+                'tel' => [
+                  'type' => 'string',
+                ],
+                'textarea' => [
+                  'type' => 'string',
+                ],
+                'textfield' => [
+                  'type' => 'string',
+                ],
+                'token' => [
+                  'type' => 'string',
+                ],
+                'url' => [
+                  'type' => 'string',
+                ],
+                'vertical_tabs' => [
+                  'type' => 'object',
+                  'title' => 'Vertical Tabs',
+                  'properties' => [
+                    'vertical_tabs__active_tab' => [
+                      'type' => 'string',
+                    ],
+                  ],
+                ],
+                'weight' => [
+                  'type' => 'integer',
+                  'enum' => range(-10, 10),
+                ],
+              ],
+            ],
+            'actions' => [
+              'type' => 'object',
+              'title' => '',
+              'properties' => [
+                'submit' => [
+                  'type' => 'string',
+                ],
+              ],
+            ],
+            'form_build_id' => [
+              'type' => 'string',
+            ],
+            'form_id' => [
+              'type' => 'string',
+            ],
+          ],
+        ],
+        'uiSchema' => [
+          'an_item' => [
+            'ui:widget' => 'item',
+            'ui:options' => ['markup' => 'This is the item'],
+          ],
+          'just_some_markup' => [
+            'ui:options' => [
+              'label' => FALSE,
+              'markup' => '<p>Test paragraph</p>',
+            ],
+            'ui:widget' => 'markup',
+          ],
+          'section' => [
+            'test1' => [
+              'ui:description' => 'This is the description of Test 1',
+            ],
+            'test2' => [
+              'ui:widget' => 'checkbox',
+              'ui:options' => [
+                'label' => FALSE,
+              ],
+            ],
+          ],
+          'all_the_things' => [
+            'checkbox' => [
+              'ui:widget' => 'checkbox',
+              'ui:options' => [
+                'label' => FALSE,
+              ],
+            ],
+            'checkboxes' => [
+              'ui:widget' => 'checkboxes',
+            ],
+            'hidden' => [
+              'ui:widget' => 'hidden',
+            ],
+            'item' => [
+              'ui:widget' => 'item',
+            ],
+            'link' => [
+              'ui:widget' => 'link',
+              'ui:options' => [
+                'label' => FALSE,
+              ],
+            ],
+            'radios' => [
+              'ui:widget' => 'radio',
+            ],
+            'submit' => [
+              'ui:widget' => 'submit',
+              'ui:options' => [
+                'label' => FALSE,
+                'name' => 'op',
+              ],
+            ],
+            'token' => [
+              'ui:widget' => 'hidden',
+            ],
+            'value' => [
+              'ui:widget' => 'hidden',
+            ],
+            'vertical_tabs' => [
+              'vertical_tabs__active_tab' => [
+                'ui:widget' => 'hidden',
+              ],
+            ],
+          ],
+          'actions' => [
+            'submit' => [
+              'ui:widget' => 'submit',
+              'ui:options' => [
+                'label' => FALSE,
+                'name' => 'op',
+              ],
+            ],
+          ],
+          'form_build_id' => [
+            'ui:widget' => 'hidden',
+          ],
+          'form_id' => [
+            'ui:widget' => 'hidden',
+          ],
+        ],
+        'formData' => [
+          'section' => [
+            'test2' => FALSE,
+          ],
+          'all_the_things' => [
+            'checkbox' => 0,
+            'weight' => 0,
+          ],
+          'actions' => [
+            'submit' => 'Save configuration',
+          ],
+          'form_build_id' => '',
+          'form_id' => 'test_form',
+        ],
+      ],
+    ];
+    return $data;
+  }
+
+}
+
+class TestForm implements FormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'test_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['#title'] = 'The form title';
+
+    $form['an_item'] = [
+      '#type' => 'item',
+      '#title' => 'An item',
+      '#plain_text' => 'This is the item',
+    ];
+
+    $form['just_some_markup'] = [
+      '#type' => 'markup',
+      '#markup' => '<p>Test paragraph</p>',
+    ];
+
+    // Test nested fields.
+    $form['section']['test1'] = [
+      '#type' => 'textfield',
+      '#title' => 'Test 1',
+      '#required' => TRUE,
+      '#description' => 'This is the description of Test 1',
+    ];
+    $form['section']['test2'] = [
+      '#type' => 'checkbox',
+      '#title' => 'Test 2',
+      '#required' => TRUE,
+      '#default_value' => FALSE,
+    ];
+
+    // Test every basic element type, skipping ones with known problems.
+    $types_to_skip = [
+      'form',
+      'html',
+      'radio',
+      'page',
+      'entity_autocomplete',
+      'datelist',
+      'datetime',
+      'button',
+      'image_button',
+      'file',
+      'language_select',
+      'machine_name',
+      'password_confirm',
+      'table',
+      'tableselect',
+    ];
+    $types = array_diff(array_keys(\Drupal::service('plugin.manager.element_info')->getDefinitions()), $types_to_skip);
+    sort($types);
+    foreach ($types as $type) {
+      $form['all_the_things'][$type] = [
+        '#type' => $type,
+      ];
+      // @todo.
+      if (in_array($type, ['checkboxes', 'radios', 'select'], TRUE)) {
+        $form['all_the_things'][$type]['#options'] = [
+          'foo' => 'Foo',
+          'bar' => 'Bar',
+        ];
+      }
+    }
+
+    $form['actions']['#type'] = 'actions';
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Save configuration',
+      '#button_type' => 'primary',
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+}
