diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index ee8a448..be94707 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -7,18 +7,23 @@
 
 namespace Drupal\Core\Field;
 
+use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\UnchangingCacheableDependencyTrait;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\Entity\BaseFieldOverride;
+use Drupal\Core\Field\TypedData\FieldDefinitionOptionsProviderTrait;
 use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
 use Drupal\Core\TypedData\ListDataDefinition;
-use Drupal\Core\TypedData\OptionsProviderInterface;
 
 /**
  * A class for defining entity fields.
  */
 class BaseFieldDefinition extends ListDataDefinition implements FieldDefinitionInterface, FieldStorageDefinitionInterface {
 
+  use FieldDefinitionOptionsProviderTrait {
+    getOptionsProvider as getOptionsProviderTrait;
+    getOptionsProviderDefinition as getOptionsProviderDefinitionTrait;
+  }
   use UnchangingCacheableDependencyTrait;
 
   /**
@@ -512,16 +517,37 @@ public function setDefaultValue($value) {
   /**
    * {@inheritdoc}
    */
-  public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
-    // If the field item class implements the interface, create an orphaned
-    // runtime item object, so that it can be used as the options provider
-    // without modifying the entity being worked on.
-    if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) {
-      $items = $entity->get($this->getName());
-      return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
-    }
-    // @todo: Allow setting custom options provider, see
-    // https://www.drupal.org/node/2002138.
+  public function getOptionsProvider($property_name = NULL, FieldableEntityInterface $entity = NULL, $delta = 0) {
+    return $this->getOptionsProviderTrait($property_name, $entity, $delta);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptionsProviderDefinition($property_name = NULL) {
+    return $this->getOptionsProviderDefinition($property_name);
+  }
+
+  /**
+   * Sets options providers for field item properties.
+   *
+   * @param string $property_name
+   *   The property for which to specify the options provider.
+   * @param string|null $provider_definition
+   *   The options provider definition; e.g. the class name. See
+   *   \Drupal\Core\TypedData\TypedDataManager::getOptionsProvider() for
+   *   supported notations. If NULL is given, the set options provider
+   *   definition is unset.
+   *
+   * @return $this
+   *
+   * @see ::getPropertyDefinitions()
+   */
+  public function setOptionsProviderDefinition($property_name, $provider_definition = NULL) {
+    $this->checkOptionsProviderDefinition($property_name, $provider_definition);
+    // Set the options provider and let getPropertyDefinitions() apply it.
+    $this->definition['options_provider'][$property_name] = $provider_definition;
+    return $this;
   }
 
   /**
@@ -538,11 +564,23 @@ public function getPropertyDefinition($name) {
 
   /**
    * {@inheritdoc}
+   *
+   * @see ::setOptionsProviderDefinition()
    */
   public function getPropertyDefinitions() {
     if (!isset($this->propertyDefinitions)) {
       $class = $this->getFieldItemClass();
       $this->propertyDefinitions = $class::propertyDefinitions($this);
+      $this->addLegacyOptionsProvider($this->propertyDefinitions);
+      $this->addFieldStorageDefinitionContext($this->propertyDefinitions);
+
+      // Incorporate any options providers that have been specified.
+      if (!empty($this->definition['options_provider'])) {
+        $provider_definitions = array_intersect_key($this->definition['options_provider'], $this->propertyDefinitions);
+        foreach ($provider_definitions as $name => $provider_definition) {
+          $this->propertyDefinitions[$name]->setOptionsProviderDefinition($provider_definition);
+        }
+      }
     }
     return $this->propertyDefinitions;
   }
diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php
index 3f13e1d..011e8ca 100644
--- a/core/lib/Drupal/Core/Field/FieldConfigBase.php
+++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php
@@ -7,10 +7,10 @@
 
 namespace Drupal\Core\Field;
 
-use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\TypedData\FieldDefinitionOptionsProviderTrait;
 use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
 
 /**
@@ -18,6 +18,8 @@
  */
 abstract class FieldConfigBase extends ConfigEntityBase implements FieldConfigInterface {
 
+  use FieldDefinitionOptionsProviderTrait;
+
   /**
    * The field ID.
    *
@@ -194,6 +196,13 @@
   protected $constraints = [];
 
   /**
+   * The set options provider context.
+   *
+   * @var array[]
+   */
+  protected $optionsProviderContext;
+
+  /**
    * {@inheritdoc}
    */
   public function id() {
@@ -514,6 +523,29 @@ public function setDefaultValue($value) {
   /**
    * {@inheritdoc}
    */
+  public function getOptionsProviderContext() {
+    return $this->optionsProviderContext;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOptionsProviderContext($interface, $method, $arguments) {
+    $this->optionsProviderContext[$interface][$method] = $arguments;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeOptionsProviderContext($interface) {
+    unset($this->optionsProviderContext[$interface]);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function allowBundleRename() {
     $this->bundleRenameAllowed = TRUE;
   }
diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
index e4f47b2..e399634 100644
--- a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
@@ -222,4 +222,30 @@ public function getFieldStorageDefinition();
    */
   public function getConfig($bundle);
 
+  /**
+   * {@inheritdoc}
+   *
+   * @param string|null $property_name
+   *   (optional) The name of the property to get options for; e.g., 'value'.
+   *   If omitted, options for the field item's main property are provided.
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   (optional) The entity for which the options should be provided.
+   * @param int $delta
+   *   (optional) If the entity is passed, the delta of the item for which to
+   *   return the options provider. Defaults to the first item.
+   *
+   * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
+   *   An options provider, or NULL if no options are defined.
+   */
+  public function getOptionsProvider($property_name = NULL, FieldableEntityInterface $entity = NULL, $delta = 0);
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param string|null $property_name
+   *   (optional) The name of the property to get options for; e.g., 'value'.
+   *   If omitted, options for the field item's main property are provided.
+   */
+  public function getOptionsProviderDefinition($property_name = NULL);
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
index 3d15bca..ccc85d2 100644
--- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
@@ -136,15 +136,27 @@ public function getDescription();
   /**
    * Gets an options provider for the given field item property.
    *
-   * @param string $property_name
-   *   The name of the property to get options for; e.g., 'value'.
+   * @param string|null $property_name
+   *   (optional) The name of the property to get options for; e.g., 'value'.
+   *   If omitted, options for the field item's main property are provided.
    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
-   *   The entity for which the options should be provided.
+   *   (optional) The entity for which the options should be provided.
    *
    * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
    *   An options provider, or NULL if no options are defined.
+   *
+   * @see ::getOptionsProviderDefinition()
+   */
+  public function getOptionsProvider($property_name = NULL, FieldableEntityInterface $entity = NULL);
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param string|null $property_name
+   *   (optional) The name of the property to get options for; e.g., 'value'.
+   *   If omitted, options for the field item's main property are provided.
    */
-  public function getOptionsProvider($property_name, FieldableEntityInterface $entity);
+  public function getOptionsProviderDefinition($property_name = NULL);
 
   /**
    * Returns whether the field can contain multiple items.
diff --git a/core/lib/Drupal/Core/Field/TypedData/FieldDefinitionOptionsProviderTrait.php b/core/lib/Drupal/Core/Field/TypedData/FieldDefinitionOptionsProviderTrait.php
new file mode 100644
index 0000000..9d4a86b
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/TypedData/FieldDefinitionOptionsProviderTrait.php
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\TypedData\FieldDefinitionOptionsProviderTrait.
+ */
+
+namespace Drupal\Core\Field\TypedData;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\TypedData\OptionsProviderInterface;
+
+/**
+ * Implements handling of options provider for field definitions.
+ *
+ * The trait expects the object to implement
+ * \Drupal\Core\Field\FieldDefinitionInterface.
+ */
+trait FieldDefinitionOptionsProviderTrait {
+
+ /**
+  * Checks options providers can be defined for the given property.
+  *
+  * @param string $property_name
+  *   The property for which to specify the options provider.
+  * @param string|null $provider_definition
+  *   The options provider definition; e.g. the class name. See
+  *   \Drupal\Core\TypedData\TypedDataManager::getOptionsProvider() for
+  *   supported notations.
+  *
+  * @throws \InvalidArgumentException
+  *   Thrown if options providers cannot be defined for the given property.
+  */
+  protected function checkOptionsProviderDefinition($property_name, $provider_definition) {
+    // Make sure the specified property supports defining option providers:
+    $property_definition = $this
+      ->getRelatedFieldStorageDefinition()
+      ->getPropertyDefinition($property_name);
+
+    if (!$property_definition) {
+      throw new \InvalidArgumentException(String::format('Options provider for the unknown property @name of field @field given.', array(
+        '@name' => $property_name,
+        '@field' => $this->getName(),
+      )));
+    }
+    if (!method_exists($property_definition, 'setOptionsProviderDefinition')) {
+      throw new \InvalidArgumentException(String::format('Unable to set an options provider for the property @name of field @field as the property definition class does not support it.', array(
+        '@name' => $property_name,
+        '@field' => $this->getName(),
+      )));
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Field\FieldDefinitionInterface::getOptionsProvider().
+   * Implements \Drupal\Core\Field\FieldStorageDefinitionInterface::getOptionsProvider().
+   */
+  public function getOptionsProvider($property_name = NULL, FieldableEntityInterface $entity = NULL, $delta = 0) {
+    // In order to be compatible with
+    // \Drupal\Core\TypedData\DataDefinitionInterface::getOptionsProvider() we
+    // must support an optional $data argument instead of the $property_name.
+    // @todo: Fix by resolving https://www.drupal.org/node/2268049.
+    if (isset($property_name) && $property_name instanceof TypedDataInterface) {
+      $entity = $property_name->getRoot();
+      $property_name = NULL;
+    }
+    $field_storage_definition = $this->getRelatedFieldStorageDefinition();
+    $property_name = $property_name ?: $field_storage_definition->getMainPropertyName();
+    $property_definition = $field_storage_definition->getPropertyDefinition($property_name);
+
+    if (!isset($property_definition)) {
+      throw new \InvalidArgumentException(SafeMarkup::format('Invalid property name %property given.', array('%property' => $property_name)));
+    }
+
+    if ($entity) {
+      $field_item_list = $entity->get($this->getName());
+      // Pass on an empty property object even if no data is set at the given
+      // delta as this is required by the legacy API support.
+      // @see \Drupal\Core\Field\TypedData\LegacyOptionsProvider
+      $item = isset($field_item_list[$delta]) ? $field_item_list[$delta] :
+        \Drupal::service('plugin.manager.field.field_type')->createFieldItem($field_item_list, 0);
+      $property = $item->get($property_name);
+    }
+    else {
+      $property = NULL;
+    }
+    return $property_definition->getOptionsProvider($property);
+  }
+
+  /**
+   * Implements \Drupal\Core\Field\FieldDefinitionInterface::getOptionsProviderDefinition().
+   * Implements \Drupal\Core\Field\FieldStorageDefinitionInterface::getOptionsProviderDefinition().
+   */
+  public function getOptionsProviderDefinition($property_name = NULL) {
+    $property_name = $property_name ?: $this->getRelatedFieldStorageDefinition()->getMainPropertyName();
+    return $this
+      ->getRelatedFieldStorageDefinition()
+      ->getPropertyDefinition($property_name)
+      ->getOptionsProviderDefinition();
+  }
+
+  /**
+   * Gets the field storage definition related to the object.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   *   The field storage definition.
+   */
+  private function getRelatedFieldStorageDefinition() {
+    if ($this instanceof FieldStorageDefinitionInterface) {
+      return $this;
+    }
+    elseif ($this instanceof FieldDefinitionInterface) {
+      // On field definition interface, there is already a getter we can re-use.
+      return $this->getFieldStorageDefinition();
+    }
+    throw new \LogicException('The object must either implement FieldStorageDefinitionInterface of FieldDefinitionInterace.');
+  }
+
+  /**
+   * Returns the field type plugin manager.
+   *
+   * @return \Drupal\Core\Field\FieldTypePluginManagerInterface
+   *   The field type plugin manager.
+   */
+  protected function getFieldTypePluginManager() {
+    return \Drupal::service('plugin.manager.field.field_type');
+  }
+
+  /**
+   * Adds a legacy options provider definition if necessary.
+   *
+   * For BC we support field item classes implementing the options provider
+   * interface.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $property_definitions
+   *   The field item property definitions.
+   */
+  protected function addLegacyOptionsProvider(array $property_definitions) {
+    $main_property = $this->getRelatedFieldStorageDefinition()->getMainPropertyName();
+    if (!isset($property_definitions[$main_property])) {
+      return;
+    }
+    $property = $property_definitions[$main_property];
+    $type_definition = $this->getFieldTypePluginManager()
+      ->getDefinition($this->getType());
+
+    // For BC, if the field item class implements the options provider
+    // interface, specify a class that proxies it through.
+    if (is_subclass_of($type_definition['class'], OptionsProviderInterface::class) && !$property->getOptionsProviderDefinition()) {
+      $property->setOptionsProviderDefinition(LegacyOptionsProvider::class);
+    }
+  }
+
+  /**
+   * Adds the field storage definition as available options provider context.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $property_definitions
+   *   The properties to which to add the context.
+   */
+  protected function addFieldStorageDefinitionContext(array $property_definitions) {
+    $field_storage_definition = $this->getRelatedFieldStorageDefinition();
+    foreach ($property_definitions as $name => $property_definition) {
+      $property_definition->setOptionsProviderContext(
+        FieldStorageDefinitionAwareOptionsProviderInterface::class,
+        'setFieldStorageDefinition',
+        array($field_storage_definition)
+      );
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderInterface.php b/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderInterface.php
new file mode 100644
index 0000000..bed4b9d
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\TypedData\FieldStorageDefinitionAwareOptionsProviderInterface.
+ */
+
+namespace Drupal\Core\Field\TypedData;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\TypedData\OptionsProviderInterface;
+
+/**
+ * Interface for options providers requiring field definitions.
+ */
+interface FieldStorageDefinitionAwareOptionsProviderInterface extends OptionsProviderInterface {
+
+  /**
+   * Sets the field storage definition.
+   *
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
+   *   The field definition where the options provider is defined.
+   *
+   * @return $this
+   */
+  public function setFieldStorageDefinition(FieldStorageDefinitionInterface $definition);
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderTrait.php b/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderTrait.php
new file mode 100644
index 0000000..e607dc7
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/TypedData/FieldStorageDefinitionAwareOptionsProviderTrait.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\TypedData\FieldStorageDefinitionAwareOptionsProviderTrait.
+ */
+
+namespace Drupal\Core\Field\TypedData;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Trait implementing FieldStorageDefinitionAwareOptionsProviderInterface.
+ */
+trait FieldStorageDefinitionAwareOptionsProviderTrait {
+
+  /**
+   * The field storage definition.
+   *
+   * @var \Drupal\Core\Field\FieldStorageDefinitionInterface
+   */
+  protected $fieldStorageDefinition;
+
+  /**
+   * Sets the field storage definition.
+   *
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
+   *   The field definition where the options provider is defined.
+   *
+   * @return $this
+   */
+  public function setFieldStorageDefinition(FieldStorageDefinitionInterface $definition) {
+    $this->fieldStorageDefinition = $definition;
+    return $this;
+  }
+
+  /**
+   * Gets the field storage definition.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   *   The field storage definition.
+   *
+   * @throws \LogicException
+   *   Thrown when the field storage definition is not available.
+   */
+  public function getFieldStorageDefinition() {
+    if (!$this->fieldStorageDefinition) {
+      throw new \LogicException("No field storage definition available. This options provider must be used on field definitions.");
+    }
+    return $this->fieldStorageDefinition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/TypedData/LegacyOptionsProvider.php b/core/lib/Drupal/Core/Field/TypedData/LegacyOptionsProvider.php
new file mode 100644
index 0000000..daf85ce
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/TypedData/LegacyOptionsProvider.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\TypedData\LegacyOptionsProvider.
+ */
+
+namespace Drupal\Core\Field\TypedData;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TypedData\Options\DependentOptionsProviderInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Provides legacy support for field types implementing the provider interface.
+ */
+class LegacyOptionsProvider implements DependentOptionsProviderInterface, FieldStorageDefinitionAwareOptionsProviderInterface {
+
+  use FieldStorageDefinitionAwareOptionsProviderTrait;
+
+  /**
+   * The field item.
+   *
+   * @var \Drupal\Core\Field\FieldItemInterface|null
+   */
+  protected $fieldItem;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setData(TypedDataInterface $data = NULL) {
+    // We get some field item property passed, so fetch the parent.
+    $this->fieldItem = isset($data) ? $data->getParent() : NULL;
+    return $this;
+  }
+
+  /**
+   * Returns a field item of the field, possibly being a dummy item.
+   *
+   * @return \Drupal\Core\Field\FieldItemInterface|\Drupal\Core\TypedData\OptionsProviderInterface
+   *   The field item.
+   */
+  protected function getFieldItem() {
+    if (!isset($this->fieldItem)) {
+      $field_definition = $this->getFieldStorageDefinition();
+      // We support two legacy cases here:
+      // - Base field definitions, in which case the object is the field
+      //   definition also.
+      // - Field storage config, for which we cannot know the bundle if no data
+      //   is passed what previously was not required. If the legacy options
+      //   provider is used in this non-legacy way, throw an exception.
+      if ($field_definition instanceof BaseFieldDefinition) {
+        $items = \Drupal::typedDataManager()
+          ->create($field_definition);
+        $this->fieldItem = $items[0];
+      }
+      elseif ($field_definition instanceof FieldStorageConfig) {
+        throw new \LogicException(SafeMarkup::format('The options provider defined for field %field implements the Legacy API and does not support non-legacy usage without data being passed. Convert the options provider to the latest API in order to allow this usage.',
+          array('%field' => $field_definition->id())));
+      }
+      else {
+        throw new \LogicException("Legacy options provider is used in some non-legacy scenario.");
+      }
+    }
+    return $this->fieldItem;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPossibleValues(AccountInterface $account = NULL) {
+    return $this->getFieldItem()->getPossibleValues($account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPossibleOptions(AccountInterface $account = NULL) {
+    return $this->getFieldItem()->getPossibleOptions($account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettableValues(AccountInterface $account = NULL) {
+    return $this->getFieldItem()->getSettableValues($account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettableOptions(AccountInterface $account = NULL) {
+    return $this->getFieldItem()->getSettableOptions($account);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextAwareOptionsProviderInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextAwareOptionsProviderInterface.php
new file mode 100644
index 0000000..a08985a
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextAwareOptionsProviderInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Context\DependentOptionsProviderInterface.
+ */
+
+namespace Drupal\Core\Plugin\Context;
+
+use Drupal\Core\TypedData\OptionsProviderInterface;
+
+/**
+ * Interface for option providers depending on the available contexts.
+ *
+ * Option providers that want to differentiate based on available contexts may
+ * implement this interface. However, note that the available contexts are
+ * provided optionally and the provider must be able to provide options if no
+ * data value has been set.
+ */
+interface ContextAwareOptionsProviderInterface extends OptionsProviderInterface {
+
+  /**
+   * Sets the available contexts for which to provide options.
+   *
+   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
+   *   (optional) The array of available contexts.
+   *
+   * @return $this
+   */
+  public function setContexts(array $contexts = NULL);
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
index 40fd662..63efe76 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
@@ -63,6 +63,13 @@ class ContextDefinition implements ContextDefinitionInterface {
   protected $defaultValue;
 
   /**
+   * The options provider definition.
+   *
+   * @var string|null
+   */
+  protected $optionsProviderDefinition;
+
+  /**
    * An array of constraints.
    *
    * @var array[]
@@ -188,6 +195,47 @@ public function setRequired($required = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function getOptionsProvider(array $contexts = NULL) {
+    if (isset($this->optionsProviderDefinition)) {
+      $data_definition = $this->getDataDefinition()
+        ->setOptionsProviderContext(ContextAwareOptionsProviderInterface::class, 'setContexts', array($contexts));
+
+      $provider = \Drupal::typedDataManager()
+        ->getOptionsProvider($data_definition);
+      return $provider;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptionsProviderDefinition() {
+    return $this->optionsProviderDefinition;
+  }
+
+  /**
+   * Defines the options provider to be used.
+   *
+   * See \Drupal\Core\TypedData\TypedDataManager::getOptionsProvider() for
+   * supported definitions. In addition, specified option provider classes may
+   * implement \Drupal\Core\Plugin\Context\ContextAwareOptionsProviderInterface
+   * in order to provide options depending on the available contexts.
+   *
+   * @param string|null $provider_definition
+   *   The options provider definition; e.g. the class name.
+   *
+   * @return $this
+   *
+   * @see ::getOptionsProviderDefinition()
+   */
+  public function setOptionsProviderDefinition($provider_definition) {
+    $this->optionsProviderDefinition = $provider_definition;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getDefaultValue() {
     return $this->defaultValue;
   }
@@ -248,7 +296,8 @@ public function getDataDefinition() {
     }
     $definition->setLabel($this->getLabel())
       ->setDescription($this->getDescription())
-      ->setRequired($this->isRequired());
+      ->setRequired($this->isRequired())
+      ->setOptionsProviderDefinition($this->optionsProviderDefinition);
     $constraints = $definition->getConstraints() + $this->getConstraints();
       $definition->setConstraints($constraints);
     return $definition;
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
index 3472c94..076d0cf 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinitionInterface.php
@@ -15,6 +15,30 @@
 interface ContextDefinitionInterface extends ComponentContextDefinitionInterface {
 
   /**
+   * Returns an options provider if there are defined options.
+   *
+   * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
+   *   (optional) The array of contexts to which the defined context belongs.
+   *
+   * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
+   *   The options provider, or NULL if no options are defined.
+   *
+   * @see ::getOptionsProviderDefinition()
+   */
+  public function getOptionsProvider(array $contexts = NULL);
+
+  /**
+   * Returns the set options provider definition.
+   *
+   * @return string|null
+   *   The options provider definition, or NULL if no options provider has been
+   *   defined.
+   *
+   * @see ::getOptionsProvider()
+   */
+  public function getOptionsProviderDefinition();
+
+  /**
    * Returns the data definition of the defined context.
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinition.php b/core/lib/Drupal/Core/TypedData/DataDefinition.php
index 1ef7c49..fa47a0b 100644
--- a/core/lib/Drupal/Core/TypedData/DataDefinition.php
+++ b/core/lib/Drupal/Core/TypedData/DataDefinition.php
@@ -261,6 +261,62 @@ public function setSetting($setting_name, $value) {
   /**
    * {@inheritdoc}
    */
+  public function getOptionsProvider($data = NULL) {
+    return \Drupal::typedDataManager()
+      ->getOptionsProvider($this, $data);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptionsProviderDefinition() {
+    return isset($this->definition['options_provider']) ? $this->definition['options_provider'] : NULL;
+  }
+
+  /**
+   * Defines the options provider to be used.
+   *
+   * See \Drupal\Core\TypedData\TypedDataManager::getOptionsProvider() for
+   * supported definition notations.
+   *
+   * @param string|null $provider_definition
+   *   The options provider definition; e.g. the class name.
+   *
+   * @return $this
+   *
+   * @see ::getOptionsProviderDefinition()
+   */
+  public function setOptionsProviderDefinition($provider_definition) {
+    $this->definition['options_provider'] = $provider_definition;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptionsProviderContext() {
+    return isset($this->definition['options_provider_context']) ? $this->definition['options_provider_context'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOptionsProviderContext($interface, $method, $arguments) {
+    $this->definition['options_provider_context'][$interface][$method] = $arguments;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeOptionsProviderContext($interface) {
+    unset($this->definition['options_provider_context'][$interface]);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getConstraints() {
     $constraints = isset($this->definition['constraints']) ? $this->definition['constraints'] : array();
     $constraints += \Drupal::typedDataManager()->getDefaultConstraints($this);
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php
index 33f9db3..7b072bb 100644
--- a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php
+++ b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php
@@ -149,6 +149,76 @@ public function getSettings();
   public function getSetting($setting_name);
 
   /**
+   * Returns the options provider definition.
+   *
+   * If not specified, the default defined by the data type will be returned.
+   *
+   * @return string|null
+   *   The options provider definition, or NULL if no options provider has been
+   *   defined.
+   *
+   * @see ::getOptionsProvider()
+   */
+  public function getOptionsProviderDefinition();
+
+  /**
+   * Returns an options provider if there are defined options.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $data
+   *   (optional) The data object for which to get the option provider.
+   *
+   * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
+   *   The options provider, or NULL if no options are defined.
+   *
+   * @see ::getOptionsProviderDefinition()
+   */
+  public function getOptionsProvider($data = NULL);
+
+  /**
+   * Gets all options provider context.
+   *
+   * @return array
+   *   A nested array of options provider context. The array is keyed by
+   *   interface and method names and contains the provided arguments.
+   *
+   * @see ::setOptionsProviderContext()
+   */
+  public function getOptionsProviderContext();
+
+  /**
+   * Allows providing contextual information to options providers.
+   *
+   * If the defined options provider implements the specified interface, the
+   * given method is invoked with the provided arguments in order to provide the
+   * additional context to the options provider object.
+   *
+   * @param string $interface
+   *   The interface the options provider needs to implement.
+   * @param string $method
+   *   The method to call on the options provider.
+   * @param mixed[] $arguments
+   *   The arguments to call the method with.
+   *
+   * @return $this
+   *
+   * @see ::getOptionsProviderContext()
+   * @see ::removeOptionsProviderContext()
+   */
+  public function setOptionsProviderContext($interface, $method, $arguments);
+
+  /**
+   * Removes any options provider context associated with the given interface.
+   *
+   * @param string $interface
+   *   The interface with which the context is associated with.
+   *
+   * @return $this
+   *
+   * @see ::setOptionsProviderContext()
+   */
+  public function removeOptionsProviderContext($interface);
+
+  /**
    * Returns an array of validation constraints.
    *
    * The validation constraints of a definition consist of any for it defined
diff --git a/core/lib/Drupal/Core/TypedData/Options/CallableOptionsProvider.php b/core/lib/Drupal/Core/TypedData/Options/CallableOptionsProvider.php
new file mode 100644
index 0000000..1acd4d7
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/CallableOptionsProvider.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\CallableOptionsProvider.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * A simple options provider based upon a callable.
+ */
+class CallableOptionsProvider extends SimpleOptionsProviderBase {
+
+  /**
+   * The callable providing the options.
+   *
+   * @var mixed
+   */
+  protected $callable;
+
+  /**
+   * Constructs the object.
+   *
+   * @param mixed $callable
+   *   The callable providing the options.
+   */
+  public function __construct($callable) {
+    $this->callable = $callable;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPossibleOptions(AccountInterface $account = NULL) {
+    return call_user_func($this->callable, $account);
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderInterface.php b/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderInterface.php
new file mode 100644
index 0000000..a3f22fd
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\DefinitionAwareOptionsProviderInterface.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\TypedData\DataDefinitionInterface;
+use Drupal\Core\TypedData\OptionsProviderInterface;
+
+/**
+ * Interface for option providers that are aware of their definition objects.
+ *
+ * Being aware of the data definition allows option providers to return
+ * different options based on definition settings or data type.
+ */
+interface DefinitionAwareOptionsProviderInterface extends OptionsProviderInterface {
+
+  /**
+   * Sets the data definition.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
+   *   The data definition where the options provider is defined.
+   *
+   * @return $this
+   */
+  public function setDataDefinition(DataDefinitionInterface $definition);
+}
diff --git a/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderTrait.php b/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderTrait.php
new file mode 100644
index 0000000..cb3092a
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/DefinitionAwareOptionsProviderTrait.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\DefinitionAwareOptionsProviderTrait.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\TypedData\DataDefinitionInterface;
+
+/**
+ * Trait for implementing the DefinitionAwareOptionsProviderInterface.
+ */
+trait DefinitionAwareOptionsProviderTrait {
+
+  /**
+   * The data definition.
+   *
+   * @var \Drupal\Core\TypedData\DataDefinitionInterface
+   */
+  protected $definition;
+
+  /**
+   * @see \Drupal\Core\TypedData\Options\DefinitionAwareOptionsProviderInterface::setDataDefinition()
+   */
+  public function setDataDefinition(DataDefinitionInterface $definition) {
+    $this->definition = $definition;
+    return $this;
+  }
+
+  /**
+   * Gets the data definition where the options provider is defined.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface
+   *   The data definition.
+   */
+  public function getDataDefinition() {
+    return $this->definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/Options/DependentOptionsProviderInterface.php b/core/lib/Drupal/Core/TypedData/Options/DependentOptionsProviderInterface.php
new file mode 100644
index 0000000..26ddbf3
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/DependentOptionsProviderInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\DependentOptionsProviderInterface.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\TypedData\OptionsProviderInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+
+/**
+ * Interface for option providers that vary depending on current data values.
+ *
+ * Option providers that want to differentiate based on current data values may
+ * implement this interface in order to receive optionally provided data
+ * objects. However, note that current data value is provided optionally and
+ * implementing classes must be able to provide options if no data value has
+ * been set.
+ */
+interface DependentOptionsProviderInterface extends OptionsProviderInterface {
+
+  /**
+   * Sets the data object to provide options for.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface|null $data
+   *   (optional) The data object to set, or NULL if no data value is available.
+   *
+   * @return $this
+   */
+  public function setData(TypedDataInterface $data = NULL);
+}
diff --git a/core/lib/Drupal/Core/TypedData/Options/OptionsProviderResolver.php b/core/lib/Drupal/Core/TypedData/Options/OptionsProviderResolver.php
new file mode 100644
index 0000000..3790f0e
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/OptionsProviderResolver.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\OptionsProviderResolver.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+
+/**
+ * Resolves options provider definitions.
+ */
+class OptionsProviderResolver {
+
+  /**
+   * The class resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
+   */
+  protected $classResolver;
+
+  /**
+   * Constructs the object.
+   *
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver to use.
+   */
+  public function __construct(ClassResolverInterface $class_resolver) {
+    $this->classResolver = $class_resolver;
+  }
+
+  /**
+   * Returns an options provider for the given definition and arguments.
+   *
+   * @param string|null $definition
+   *   The options provider definition; e.g., a class name, or NULL if
+   *   undefined.
+   *
+   * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
+   *   The options provider, or NULL if none is defined.
+   *
+   * @see \Drupal\Core\TypedData\TypedDataManager::getOptionsProvider()
+   */
+  public function getOptionsProvider($definition) {
+    if (!isset($definition)) {
+      return;
+    }
+    // If the definition is a callable, wrap it in a suiting options provider.
+    if ($callable = $this->getCallableFromDefinition($definition)) {
+      $provider = new CallableOptionsProvider($callable);
+    }
+    else {
+      $provider = $this->classResolver->getInstanceFromDefinition($definition);
+    }
+    return $provider;
+  }
+
+  /**
+   * Returns a callable for the given definition.
+   *
+   * @param string $definition
+   *   The options provider definition.
+   *
+   * @return mixed|null
+   *   The callable, or NULL if no callable was defined.
+   */
+  protected function getCallableFromDefinition($definition) {
+    if (strpos($definition, ':') === FALSE && function_exists($definition)) {
+      return $definition;
+    }
+    // Definition is in the service:method notation.
+    elseif (substr_count($definition, ':') == 1) {
+      list($service, $method) = explode(':', $definition, 2);
+      $instance = $this->classResolver->getInstanceFromDefinition($service);
+      return array($instance, $method);
+    }
+    // Definition is in the class::method notation for static methods.
+    elseif (strpos($definition, '::') !== FALSE) {
+      return explode('::', $definition, 2);
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/Options/SimpleOptionsProviderBase.php b/core/lib/Drupal/Core/TypedData/Options/SimpleOptionsProviderBase.php
new file mode 100644
index 0000000..7cafab4
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Options/SimpleOptionsProviderBase.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Options\SimpleOptionsProviderBase.
+ */
+
+namespace Drupal\Core\TypedData\Options;
+
+use Drupal\Core\Form\OptGroup;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TypedData\OptionsProviderInterface;
+
+/**
+ * Base class for providers with possible and settable options being the same.
+ */
+abstract class SimpleOptionsProviderBase implements OptionsProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPossibleValues(AccountInterface $account = NULL) {
+    // Flatten options firstly, because Possible Options may contain group
+    // arrays.
+    $flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
+    return array_keys($flatten_options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettableValues(AccountInterface $account = NULL) {
+    return $this->getPossibleValues($account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettableOptions(AccountInterface $account = NULL) {
+    return $this->getPossibleOptions($account);
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 20910f6..f86fa5e 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -13,6 +13,10 @@
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\TypedData\Options\DefinitionAwareOptionsProviderInterface;
+use Drupal\Core\TypedData\Options\DependentOptionsProviderInterface;
+use Drupal\Core\TypedData\Options\OptionsProviderResolver;
+use Drupal\Core\TypedData\Validation\MetadataFactory;
 use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
 use Drupal\Core\TypedData\Validation\RecursiveValidator;
 use Drupal\Core\Validation\ConstraintManager;
@@ -55,6 +59,13 @@ class TypedDataManager extends DefaultPluginManager {
   protected $classResolver;
 
   /**
+   * The options provider resolver.
+   *
+   * @var \Drupal\Core\TypedData\Options\OptionsProviderResolver
+   */
+  protected $optionsProviderResolver;
+
+  /**
    * Constructs a new TypedDataManager.
    *
    * @param \Traversable $namespaces
@@ -71,7 +82,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
     $this->alterInfo('data_type_info');
     $this->setCacheBackend($cache_backend, 'typed_data_types_plugins');
     $this->classResolver = $class_resolver;
-
+    $this->optionsProviderResolver = new OptionsProviderResolver($this->classResolver);
     parent::__construct('Plugin/DataType', $namespaces, $module_handler, NULL, 'Drupal\Core\TypedData\Annotation\DataType');
   }
 
@@ -319,6 +330,63 @@ public function getPropertyInstance(TypedDataInterface $object, $property_name,
   }
 
   /**
+   * Returns an options provider for the given definition and arguments.
+   *
+   * Option providers are usually defined by a class implementing the
+   * \Drupal\Core\TypedData\OptionsProviderInterface, while in simple cases
+   * options may be defined by a callable, which returns a single set of options
+   * for both settable and possible options.
+   *
+   * Supported definitions are:
+   *  - The name of a class implementing the OptionsProviderInterface.
+   *  - A callable, either as
+   *    - static method via the class::method notation or as
+   *    - method on a service via the service:method notation or as
+   *    - function callback.
+   *
+   * If a class name is given, the class may implement the
+   *   - \Drupal\Core\DependencyInjection\ContainerInjectionInterface, or it is
+   *     instantiated without constructor arguments.
+   *   - \Drupal\Core\TypedData\Options\DefinitionAwareOptionsProviderInterface
+   *     in order to get the data definition passed, or
+   *   - \Drupal\Core\TypedData\Options\DependentOptionsProviderInterface
+   *     in order to provide options in dependence of the current data value.
+   * Depending on the definition object for which options are provided, some
+   * additional interfaces may be supported. E.g., Drupal core also supports
+   *   - \Drupal\Core\Field\TypedData\FieldStorageDefinitionAwareOptionsProviderInterface
+   *     on field storage definitions in order to pass the field storage
+   *     definition or
+   *   - \Drupal\Core\Plugin\Context\DependentOptionsProviderInterface
+   *     on context definitions in order to pass the available context objects.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
+   *   The definition where the options provider is defined.
+   * @param \Drupal\Core\TypedData\TypedDataInterface|null $data
+   *   (optional) The data object holding the current value.
+   *
+   * @return \Drupal\Core\TypedData\OptionsProviderInterface|null
+   *   The options provider, or NULL if none is defined.
+   */
+  public function getOptionsProvider(DataDefinitionInterface $definition, TypedDataInterface $data = NULL) {
+    $provider_definition = $definition->getOptionsProviderDefinition();
+    $provider = $this->optionsProviderResolver->getOptionsProvider($provider_definition);
+
+    // Forward all options provider context and add default context.
+    $context = $definition->getOptionsProviderContext();
+    $context[DefinitionAwareOptionsProviderInterface::class]['setDataDefinition'] = array($definition);
+    $context[DependentOptionsProviderInterface::class]['setData'] = array($data);
+
+    foreach ($context as $interface => $methods) {
+      if ($provider instanceof $interface) {
+        foreach ($methods as $method => $arguments) {
+          call_user_func_array(array($provider, $method), $arguments);
+        }
+      }
+    }
+    return $provider;
+  }
+
+  /**
    * Sets the validator for validating typed data.
    *
    * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php
index 5fff1aa..6cd0e1ab 100644
--- a/core/modules/field/src/Entity/FieldStorageConfig.php
+++ b/core/modules/field/src/Entity/FieldStorageConfig.php
@@ -10,10 +10,9 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
-use Drupal\Core\TypedData\OptionsProviderInterface;
+use Drupal\Core\Field\TypedData\FieldDefinitionOptionsProviderTrait;
 use Drupal\field\FieldStorageConfigInterface;
 
 /**
@@ -47,6 +46,8 @@
  */
 class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
 
+  use FieldDefinitionOptionsProviderTrait;
+
   /**
    * The maximum length of the field name, in characters.
    *
@@ -631,21 +632,6 @@ public function setCardinality($cardinality) {
   /**
    * {@inheritdoc}
    */
-  public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
-    // If the field item class implements the interface, create an orphaned
-    // runtime item object, so that it can be used as the options provider
-    // without modifying the entity being worked on.
-    if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) {
-      $items = $entity->get($this->getName());
-      return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
-    }
-    // @todo: Allow setting custom options provider, see
-    // https://www.drupal.org/node/2002138.
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function isMultiple() {
     $cardinality = $this->getCardinality();
     return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
@@ -738,6 +724,10 @@ public function getPropertyDefinitions() {
     if (!isset($this->propertyDefinitions)) {
       $class = $this->getFieldItemClass();
       $this->propertyDefinitions = $class::propertyDefinitions($this);
+      $this->addLegacyOptionsProvider($this->propertyDefinitions);
+      $this->addFieldStorageDefinitionContext($this->propertyDefinitions);
+
+      // @todo: Allow configurable fields to customize option providers.
     }
     return $this->propertyDefinitions;
   }
diff --git a/core/modules/system/src/Tests/TypedData/OptionsProviderTest.php b/core/modules/system/src/Tests/TypedData/OptionsProviderTest.php
new file mode 100644
index 0000000..aed8d72
--- /dev/null
+++ b/core/modules/system/src/Tests/TypedData/OptionsProviderTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\TypedData\Tests\OptionsProviderTest.
+ */
+
+namespace Drupal\system\Tests\TypedData;
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\Options\SimpleOptionsProviderBase;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests using option providers.
+ *
+ * @group TypedData
+ */
+class OptionsProviderTest extends KernelTestBase {
+
+  /**
+   * Returns a sample options array.
+   *
+   * @return int[]
+   *   A sample options array.
+   */
+  public static function getOptions() {
+    return [
+      1 => 1,
+      2 => 2,
+      3 => 3,
+    ];
+  }
+
+  /**
+   * Tests using a callable options provider.
+   */
+  public function testCallableOptionsProvider() {
+    $definition = DataDefinition::create('int')
+      ->setOptionsProviderDefinition(self::class . '::getOptions');
+
+    $provider = $definition->getOptionsProvider();
+    $this->assertNotNull($provider);
+    $this->assertEquals($provider->getPossibleOptions(), self::getOptions());
+    $this->assertEquals($provider->getPossibleValues(), array_keys(self::getOptions()));
+    $this->assertEquals($provider->getSettableOptions(), self::getOptions());
+    $this->assertEquals($provider->getSettableValues(), array_keys(self::getOptions()));
+  }
+
+  /**
+   * Tests using a simple options provider.
+   */
+  public function testSimpleOptionsProviderBase() {
+    $definition = DataDefinition::create('int')
+      ->setOptionsProviderDefinition(SimpleOptionsProvider::class);
+    $expected = [ 1 => 1 ];
+
+    $provider = $definition->getOptionsProvider();
+    $this->assertNotNull($provider);
+    $this->assertEquals($provider->getPossibleOptions(), $expected);
+    $this->assertEquals($provider->getPossibleValues(), array_keys($expected));
+    $this->assertEquals($provider->getSettableOptions(), $expected);
+    $this->assertEquals($provider->getSettableValues(), array_keys($expected));
+  }
+
+  // @todo: Test definition aware and dependent options providers.
+
+}
+
+/**
+ * Helper class for testing simple option providers.
+ */
+class SimpleOptionsProvider extends SimpleOptionsProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPossibleOptions(AccountInterface $account = NULL) {
+    return [ 1 => 1 ];
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php
index f685edd..394e76b 100644
--- a/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php
+++ b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php
@@ -12,6 +12,10 @@
 
 /**
  * Defines a test interface to mock entity base field definitions.
+ *
+ * @todo This is not possible anymore. fix by converting to abstract class
+ * which implements both FDI and FSDI ?
  */
-interface TestBaseFieldDefinitionInterface extends FieldDefinitionInterface, FieldStorageDefinitionInterface {
+interface TestBaseFieldDefinitionInterface extends FieldStorageDefinitionInterface {
+
 }
