diff --git a/src/Engine/RulesComponent.php b/src/Engine/RulesComponent.php
index e7c74180..cdd0345a 100644
--- a/src/Engine/RulesComponent.php
+++ b/src/Engine/RulesComponent.php
@@ -29,9 +29,9 @@ class RulesComponent {
   protected $contextDefinitions = [];
 
   /**
-   * List of context names that is provided back to the caller.
+   * List of context definitions (or NULLs for BC) keyed by name.
    *
-   * @var string[]
+   * @var \Drupal\rules\Context\ContextDefinitionInterface|NULL[]
    */
   protected $providedContext = [];
 
@@ -75,7 +75,7 @@ class RulesComponent {
       $component->addContextDefinition($name, ContextDefinition::createFromArray($definition));
     }
     foreach ($configuration['provided_context_definitions'] as $name => $definition) {
-      $component->provideContext($name);
+      $component->provideContext($name, ContextDefinition::createFromArray($definition));
     }
     return $component;
   }
@@ -110,15 +110,21 @@ class RulesComponent {
    *     nested 'id' key.
    *   - context_definitions: Array of context definition arrays, keyed by
    *     context name.
-   *   - provided_context: The names of the context that is provided back.
+   *   - provided_context_definitions: Array of provided context definitions,
+   *     keyed by context name.
    */
   public function getConfiguration() {
+    $provided_context_definitions = $this->providedContext;
+    array_walk($provided_context_definitions, function (ContextDefinitionInterface &$definition, $name, $context_definitions) {
+      $definition = $definition ? $definition->toArray() : ($context_definitions[$name] ? $context_definitions[$name]->toArray() : []);
+    }, $this->contextDefinitions);
+
     return [
       'expression' => $this->expression->getConfiguration(),
       'context_definitions' => array_map(function (ContextDefinitionInterface $definition) {
         return $definition->toArray();
       }, $this->contextDefinitions),
-      'provided_context_definitions' => $this->providedContext,
+      'provided_context_definitions' => $provided_context_definitions,
     ];
   }
 
@@ -183,11 +189,13 @@ class RulesComponent {
    *
    * @param string $name
    *   The name of the context to provide.
+   * @param \Drupal\rules\Context\ContextDefinitionInterface $definition
+   *   The definition of the context provided.
    *
    * @return $this
    */
-  public function provideContext($name) {
-    $this->providedContext[] = $name;
+  public function provideContext($name, ContextDefinitionInterface $definition = NULL) {
+    $this->providedContext[$name] = $definition;
     return $this;
   }
 
@@ -198,7 +206,7 @@ class RulesComponent {
    *   The names of the context that is provided back.
    */
   public function getProvidedContext() {
-    return $this->providedContext;
+    return array_keys($this->providedContext);
   }
 
   /**
@@ -232,10 +240,17 @@ class RulesComponent {
    *   Thrown if the Rules expression triggers errors during execution.
    */
   public function execute() {
+    // Add provided context that are to context to the execution state.
+    foreach ($this->providedContext as $name => $definition) {
+      if (!isset($this->contextDefinitions[$name]) && $definition) {
+        $this->state->setVariable($name, $definition, NULL);
+      }
+    }
+
     $this->expression->executeWithState($this->state);
     $this->state->autoSave();
     $result = [];
-    foreach ($this->providedContext as $name) {
+    foreach ($this->providedContext as $name => $definition) {
       $result[$name] = $this->state->getVariableValue($name);
     }
     return $result;
@@ -289,6 +304,11 @@ class RulesComponent {
     foreach ($this->contextDefinitions as $name => $context_definition) {
       $data_definitions[$name] = $context_definition->getDataDefinition();
     }
+    foreach ($this->providedContext as $name => $context_definition) {
+      if (empty($data_definitions[$name]) && $context_definition) {
+        $data_definitions[$name] = $context_definition->getDataDefinition();
+      }
+    }
 
     return ExecutionMetadataState::create($data_definitions);
   }
diff --git a/src/Form/ReactionRuleAddForm.php b/src/Form/ReactionRuleAddForm.php
index d5730bd4..140b8b1c 100644
--- a/src/Form/ReactionRuleAddForm.php
+++ b/src/Form/ReactionRuleAddForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\rules\Form;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
 use Drupal\rules\Core\RulesEventManager;
 use Drupal\rules\Engine\ExpressionManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -24,11 +25,13 @@ class ReactionRuleAddForm extends RulesComponentFormBase {
    *
    * @param \Drupal\rules\Engine\ExpressionManagerInterface $expression_manager
    *   The expression manager.
+   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
+   *   The typed data manager.
    * @param \Drupal\rules\Core\RulesEventManager $event_manager
    *   The Rules event manager.
    */
-  public function __construct(ExpressionManagerInterface $expression_manager, RulesEventManager $event_manager) {
-    parent::__construct($expression_manager);
+  public function __construct(ExpressionManagerInterface $expression_manager, TypedDataManagerInterface $typed_data_manager, RulesEventManager $event_manager) {
+    parent::__construct($expression_manager, $typed_data_manager);
     $this->eventManager = $event_manager;
   }
 
@@ -36,7 +39,7 @@ class ReactionRuleAddForm extends RulesComponentFormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('plugin.manager.rules_expression'), $container->get('plugin.manager.rules_event'));
+    return new static($container->get('plugin.manager.rules_expression'), $container->get('typed_data_manager'), $container->get('plugin.manager.rules_event'));
   }
 
   /**
diff --git a/src/Form/ReactionRuleEditForm.php b/src/Form/ReactionRuleEditForm.php
index 2afaffcf..66e3436e 100644
--- a/src/Form/ReactionRuleEditForm.php
+++ b/src/Form/ReactionRuleEditForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\rules\Form;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
 use Drupal\rules\Core\RulesEventManager;
 use Drupal\rules\Engine\ExpressionManagerInterface;
 use Drupal\rules\Ui\RulesUiConfigHandler;
@@ -28,15 +29,17 @@ class ReactionRuleEditForm extends RulesComponentFormBase {
   protected $rulesUiHandler;
 
   /**
-   * Constructs a new object of this class.
+   * Constructs a new reaction rule form.
    *
    * @param \Drupal\rules\Engine\ExpressionManagerInterface $expression_manager
    *   The expression manager.
+   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
+   *   The typed data manager.
    * @param \Drupal\rules\Core\RulesEventManager $event_manager
-   *   The event plugin manager.
+   *   The Rules event manager.
    */
-  public function __construct(ExpressionManagerInterface $expression_manager, RulesEventManager $event_manager) {
-    parent::__construct($expression_manager);
+  public function __construct(ExpressionManagerInterface $expression_manager, TypedDataManagerInterface $typed_data_manager, RulesEventManager $event_manager) {
+    parent::__construct($expression_manager, $typed_data_manager);
     $this->eventManager = $event_manager;
   }
 
@@ -44,7 +47,7 @@ class ReactionRuleEditForm extends RulesComponentFormBase {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('plugin.manager.rules_expression'), $container->get('plugin.manager.rules_event'));
+    return new static($container->get('plugin.manager.rules_expression'), $container->get('typed_data_manager'), $container->get('plugin.manager.rules_event'));
   }
 
   /**
diff --git a/src/Form/RulesComponentFormBase.php b/src/Form/RulesComponentFormBase.php
index b58e035d..8d211ef4 100644
--- a/src/Form/RulesComponentFormBase.php
+++ b/src/Form/RulesComponentFormBase.php
@@ -4,7 +4,10 @@ namespace Drupal\rules\Form;
 
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
+use Drupal\rules\Entity\RulesComponentConfig;
 use Drupal\rules\Engine\ExpressionManagerInterface;
+use Drupal\rules\Context\ContextDefinition;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -19,11 +22,21 @@ abstract class RulesComponentFormBase extends EntityForm {
    */
   protected $expressionManager;
 
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
+   */
+  protected $typedDataManager;
+
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('plugin.manager.rules_expression'));
+    return new static(
+      $container->get('plugin.manager.rules_expression'),
+      $container->get('typed_data_manager')
+    );
   }
 
   /**
@@ -31,9 +44,12 @@ abstract class RulesComponentFormBase extends EntityForm {
    *
    * @param \Drupal\rules\Engine\ExpressionManagerInterface $expression_manager
    *   The expression manager.
+   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
+   *   The typed data manager.
    */
-  public function __construct(ExpressionManagerInterface $expression_manager) {
+  public function __construct(ExpressionManagerInterface $expression_manager, TypedDataManagerInterface $typed_data_manager) {
     $this->expressionManager = $expression_manager;
+    $this->typedDataManager = $typed_data_manager;
   }
 
   /**
@@ -83,6 +99,112 @@ abstract class RulesComponentFormBase extends EntityForm {
       '#title' => $this->t('Description'),
     ];
 
+    if (!$this->entity instanceof RulesComponentConfig) {
+      return parent::form($form, $form_state);
+    }
+
+    $form['settings']['variables'] = [
+      '#type' => 'table',
+      '#tree' => TRUE,
+      '#header' => [
+        $this->t('Data Type'),
+        $this->t('Label'),
+        $this->t('Machine Name'),
+        $this->t('Usage'),
+        $this->t('Weight'),
+      ],
+      '#tabledrag' => [
+        [
+          'action' => 'order',
+          'relationship' => 'sibling',
+          'group' => 'settings-variables-order-weight',
+        ],
+      ],
+    ];
+
+    $context_definitions = $this->entity->getContextDefinitions();
+    $provided_context_definitions = $this->entity->getProvidedContextDefinitions();
+
+    // Build one array of usages, this allows us to only loop and build the
+    // table once.
+    $def_usages = [];
+    foreach (array_keys($context_definitions) as $name) {
+      $def_usages[$name] = !empty($provided_context_definitions[$name]) ? 'both' : 'parameter';
+    }
+    foreach (array_keys($provided_context_definitions) as $name) {
+      $def_usages[$name] = !empty($def_usages[$name]) ? $def_usages[$name] : 'provided';
+    }
+
+    // Always add 3 paramater type usages to the end.
+    $def_usages['new_1'] = 'parameter';
+    $def_usages['new_2'] = 'parameter';
+    $def_usages['new_3'] = 'parameter';
+
+    // Prepare some variables for the loop.
+    $weight = 0;
+    $usage_options = [
+      'parameter' => $this->t('Parameter'),
+      'provided' => $this->t('Provided'),
+      'both' => $this->t('Parameter & Provided'),
+    ];
+    $data_types = $this->typedDataManager->getDefinitions();
+    foreach ($data_types as $id => $data_definition) {
+      $category = !empty($data_definition['deriver']) ? $data_types[$data_definition['id']]['label'] : $this->t('Data');
+      if (is_callable([$category, 'render'])) {
+        $category = $category->render();
+      }
+
+      $data_type_options[$category][$id] = $data_definition['label'];
+    }
+
+    foreach ($def_usages as $name => $usage) {
+      $list_var = ($usage != 'provided') ? 'context_definitions' : 'provided_context_definitions';
+      $context_definition = isset(${$list_var}[$name]) ? ${$list_var}[$name] : NULL;
+
+      $row = [];
+      $row['#attributes']['class'][] = 'draggable';
+      $row['#weight'] = $weight;
+      $row['type'] = [
+        '#title' => $this->t('Data Type'),
+        '#title_display' => 'invisible',
+        '#type' => 'select',
+        '#options' => $data_type_options,
+        '#default_value' => $context_definition ? $context_definition->getDataType() : NULL,
+      ];
+      $row['label'] = [
+        '#title' => $this->t('Label'),
+        '#title_display' => 'invisible',
+        '#type' => 'textfield',
+        '#default_value' => $context_definition ? $context_definition->getLabel() : NULL,
+      ];
+      $row['name'] = [
+        '#title' => $this->t('Machine Name'),
+        '#title_display' => 'invisible',
+        '#type' => 'textfield',
+        '#default_value' => $context_definition ? $name : NULL,
+      ];
+      $row['usage'] = [
+        '#title' => $this->t('Usage'),
+        '#title_display' => 'invisible',
+        '#type' => 'select',
+        '#options' => $usage_options,
+        '#default_value' => $usage,
+      ];
+      $row['weight'] = [
+        '#title' => $this->t('Weight'),
+        '#title_display' => 'invisible',
+        '#type' => 'weight',
+        '#default_value' => $weight,
+        '#attributes' => ['class' => ['settings-variables-order-weight']],
+      ];
+
+      // Add the row to the table and include it in included defs.
+      $form['settings']['variables'][$name] = $row;
+
+      // Increment the weight.
+      $weight++;
+    }
+
     return parent::form($form, $form_state);
   }
 
@@ -96,6 +218,36 @@ abstract class RulesComponentFormBase extends EntityForm {
       $tags = array_map('trim', explode(',', $entity->get('tags')));
     }
     $entity->set('tags', $tags);
+    if ($entity instanceof RulesComponentConfig) {
+      // Set the context definitions.
+      $variables = $form_state->getValue('variables');
+      uasort($variables, ['\Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
+
+      // Create the relevant context definitions.
+      $context_definitions = $provided_context_definitions = [];
+      foreach ($variables as $variable) {
+        // Skip any rows where the context name has not been set.
+        if (empty($variable['name'])) {
+          continue;
+        }
+
+        // Build a context definition.
+        $context_definition = ContextDefinition::create($variable['type']);
+        $context_definition->setLabel($variable['label']);
+
+        // Add the cotnext definition to the correct array(s).
+        if (in_array($variable['usage'], ['parameter', 'both'])) {
+          $context_definitions[$variable['name']] = $context_definition;
+        }
+        if (in_array($variable['usage'], ['provided', 'both'])) {
+          $provided_context_definitions[$variable['name']] = $context_definition;
+        }
+      }
+
+      // Set the context definitions.
+      $entity->setContextDefinitions($context_definitions);
+      $entity->setProvidedContextDefinitions($provided_context_definitions);
+    }
     return $entity;
   }
 
diff --git a/tests/src/Functional/UiPageTest.php b/tests/src/Functional/UiPageTest.php
index 6c808bee..a5b100be 100644
--- a/tests/src/Functional/UiPageTest.php
+++ b/tests/src/Functional/UiPageTest.php
@@ -23,6 +23,82 @@ class UiPageTest extends RulesBrowserTestBase {
    */
   protected $profile = 'minimal';
 
+  /**
+   * Tests that the rules component listing page works.
+   */
+  public function testRulesComponentPage() {
+    $account = $this->drupalCreateUser(['administer rules']);
+    $this->drupalLogin($account);
+
+    $this->drupalGet('admin/config/workflow/rules/components');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Test that there is an empty rules component listing.
+    $this->assertSession()->pageTextContains('There are no rules components yet.');
+  }
+
+  /**
+   * Test create a rules component.
+   */
+  public function testCreateRulesComponent() {
+    $account = $this->drupalCreateUser(['administer rules']);
+    $this->drupalLogin($account);
+
+    $this->drupalGet('admin/config/workflow/rules/components');
+    $this->clickLink('Add component');
+
+    $this->fillField('Label', 'Test Component');
+    $this->fillField('Machine-readable name', 'test_component');
+    $this->fillField('variables[new_1][type]', 'string');
+    $this->fillField('variables[new_1][label]', 'String');
+    $this->fillField('variables[new_1][name]', 'string');
+    $this->fillField('variables[new_1][usage]', 'parameter');
+    $this->fillField('variables[new_2][type]', 'integer');
+    $this->fillField('variables[new_2][label]', 'Integer');
+    $this->fillField('variables[new_2][name]', 'integer');
+    $this->fillField('variables[new_2][usage]', 'provided');
+    $this->fillField('variables[new_3][type]', 'entity:user');
+    $this->fillField('variables[new_3][label]', 'User');
+    $this->fillField('variables[new_3][name]', 'user');
+    $this->fillField('variables[new_3][usage]', 'both');
+
+    $this->pressButton('Save');
+
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Component Test Component has been created.');
+
+    $this->assertSession()->fieldValueEquals('variables[string][type]', 'string');
+    $this->assertSession()->fieldValueEquals('variables[string][label]', 'String');
+    $this->assertSession()->fieldValueEquals('variables[string][name]', 'string');
+    $this->assertSession()->fieldValueEquals('variables[string][usage]', 'parameter');
+    $this->assertSession()->fieldValueEquals('variables[integer][usage]', 'provided');
+    $this->assertSession()->fieldValueEquals('variables[user][usage]', 'both');
+
+    $this->clickLink('Add condition');
+    $this->fillField('Condition', 'rules_user_is_blocked');
+    $this->pressButton('Continue');
+
+    $this->pressButton('Switch to data selection');
+
+    $this->fillField('context_definitions[user][setting]', 'user');
+    $this->pressButton('Save');
+    $this->assertSession()->pageTextContains('You have unsaved changes.');
+
+    $this->clickLink('Add action');
+    $this->fillField('Action', 'rules_data_set');
+    $this->pressButton('Continue');
+
+    $this->fillField('context_definitions[data][setting]', 'integer');
+    $this->fillField('context_definitions[value][setting]', '4');
+    $this->pressButton('Save');
+
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('You have unsaved changes.');
+
+    $this->pressButton('Save');
+    $this->assertSession()->pageTextContains('Rule component Test Component has been updated.');
+  }
+
   /**
    * Tests that the reaction rule listing page works.
    */
