diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml
index 0b76811..fe9c3a8 100644
--- a/core/modules/media/config/schema/media.schema.yml
+++ b/core/modules/media/config/schema/media.schema.yml
@@ -66,3 +66,11 @@ media.handler.field_aware:
     source_field:
       type: string
       label: 'Source field'
+
+media.handler.oembed:
+  type: media.handler.field_aware
+  label: 'oEmbed handler configuration'
+  mapping:
+    thumbnails_location:
+      type: string
+      label: 'oEmbed thumbnails location'
diff --git a/core/modules/media/media.services.yml b/core/modules/media/media.services.yml
index 730e2d8..1b4b932 100644
--- a/core/modules/media/media.services.yml
+++ b/core/modules/media/media.services.yml
@@ -2,7 +2,11 @@ services:
   plugin.manager.media.handler:
     class: Drupal\media\MediaHandlerManager
     parent: default_plugin_manager
-
   media.thumbnail_handler:
     class: Drupal\media\MediaThumbnailHandler
     arguments: ['@entity_type.manager', '@string_translation']
+  media.oembed:
+    class: Drupal\media\OEmbed
+    arguments: ['@http_client_factory', '@cache.default']
+
+
diff --git a/core/modules/media/src/OEmbed.php b/core/modules/media/src/OEmbed.php
new file mode 100644
index 0000000..1835166
--- /dev/null
+++ b/core/modules/media/src/OEmbed.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace Drupal\media;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Http\ClientFactory;
+
+/**
+ * OEmbed service.
+ */
+class OEmbed implements OEmbedInterface {
+
+  /**
+   * Cache key to be used to store providers info into cache.
+   *
+   * @var string
+   */
+  protected static $providersCacheKey = 'media:oembed:providers';
+
+  /**
+   * URL of the JSON with providers info.
+   *
+   * @var string
+   */
+  protected static $providersUrl = 'http://oembed.com/providers.json';
+
+  /**
+   * Cache lifetime for the providers info.
+   */
+  protected static $providersCacheLifetime = 60 * 60 * 24 * 7;
+
+  /**
+   * The HTTP client factory service.
+   *
+   * @var \Drupal\Core\Http\ClientFactory
+   */
+  protected $clientFactory;
+
+  /**
+   * OEmbed providers information.
+   *
+   * @var array
+   */
+  protected $providers;
+
+  /**
+   * Cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * Static cache of fetched oEmbed resources.
+   *
+   * @var array
+   */
+  protected $resources;
+
+  /**
+   * Static cache of discovered oEmbed resources.
+   *
+   * @var array
+   */
+  protected $discovered;
+
+  /**
+   * Constructs OEmbed class.
+   *
+   * @param \Drupal\Core\Http\ClientFactory $client_factory
+   *   The HTTP client factory service.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend.
+   */
+  public function __construct(ClientFactory $client_factory, CacheBackendInterface $cache) {
+    $this->clientFactory = $client_factory;
+    $this->cache = $cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProviders() {
+    if ($this->providers) {
+      return $this->providers;
+    }
+    elseif ($data = $this->cache->get(static::$providersCacheKey)) {
+      $this->providers = $data->data;
+      return $this->providers;
+    }
+    else {
+      $response = $this->clientFactory->fromOptions()->get(static::$providersUrl);
+      if ($response->getStatusCode() !== 200) {
+        // TODO Handle errors.
+      }
+
+      $providers = \GuzzleHttp\json_decode($response->getBody()->getContents(), TRUE);
+
+      if (!is_array($providers)) {
+        // TODO Handle errors.
+      }
+
+      $this->cache->set(self::$providersCacheKey, $providers, static::$providersCacheLifetime);
+      $this->providers = $providers;
+      return $this->providers;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function matchUrl($url, $providers_limit = NULL) {
+    $providers = $this->getProviders();
+    if (isset($providers)) {
+      $providers = array_intersect_key($providers, $providers_limit);
+    }
+
+    foreach ($providers as $provider_name => $provider_info) {
+      if (!empty($provider_info['endpoints'])) {
+        foreach ($provider_info['endpoints'] as $endpoint) {
+          if (!empty($endpoint['schemes'])) {
+            foreach ($endpoint['schemes'] as $scheme) {
+              $regexp = str_replace(['.', '*'], ['\.', '.*'], $scheme);
+              if (preg_match("|$regexp|", $url)) {
+                return [
+                  'provider' => $provider_name,
+                  'endpoint' => $endpoint['url'],
+                ];
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function doUrlDiscovery($url) {
+    if (!empty($this->discovered[$url])) {
+      return $this->discovered[$url];
+    }
+
+    $response = $this->clientFactory->fromOptions()->get($url);
+    if ($response->getStatusCode() !== 200) {
+      // TODO
+    }
+
+    $content = $response->getBody()->getContents();
+    $dom = new \DOMDocument();
+
+    // TODO this causes annoying warning with YT videos. Fix it:
+    // Warning: DOMDocument::loadHTML(): htmlParseEntityRef: no name in Entity
+    if (!@$dom->loadHTML($content)) {
+      // TODO
+    }
+
+    $xpath = new \DOMXpath($dom);
+    $result = $xpath->query("//link[@type='application/json+oembed']");
+    if ($result->length > 0) {
+      $this->discovered[$url] = $result->item(0)->getAttribute('href');
+      return $this->discovered[$url];
+    }
+
+    $result = $xpath->query("//link[@type='text/xml+oembed']");
+    if ($result->length > 0) {
+      $this->discovered[$url] = $result->item(0)->getAttribute('href');
+      return $this->discovered[$url];
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchResource($endpoint_url) {
+    if (!empty($this->resources[$endpoint_url])) {
+      return $this->resources[$endpoint_url];
+    }
+
+    $response = $this->clientFactory->fromOptions()->get($endpoint_url);
+    if ($response->getStatusCode() !== 200) {
+      // TODO Handle errors.
+    }
+
+    $format = $response->getHeader('Content-Type');
+    $content = $response->getBody()->getContents();
+    if ($format[0] == 'application/json') {
+      $data = \GuzzleHttp\json_decode($content, TRUE);
+
+      if (!is_array($data)) {
+        // TODO Handle errors.
+      }
+
+      $this->resources[$endpoint_url] = $data;
+      return $data;
+    }
+    elseif ($format[0] == 'text/xml') {
+      $data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($content), TRUE);
+
+      if (!is_array($data)) {
+        // TODO Handle errors.
+      }
+
+      $this->resources[$endpoint_url] = $data;
+      return $data;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isOEmbedResource($url) {
+    if ($this->doUrlDiscovery($url)) {
+      return TRUE;
+    }
+
+    // TODO handle providers that do not support discovery.
+
+    return FALSE;
+  }
+
+}
\ No newline at end of file
diff --git a/core/modules/media/src/OEmbedInterface.php b/core/modules/media/src/OEmbedInterface.php
new file mode 100644
index 0000000..bf64c2b
--- /dev/null
+++ b/core/modules/media/src/OEmbedInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\media;
+
+/**
+ * Interface for oEmbed service.
+ */
+interface OEmbedInterface {
+
+  /**
+   * Gets oEmbed providers information.
+   *
+   * Returns multi-dimensional array where each provider is represented as a
+   * single top-level array element. Each individual element has the following
+   * values:
+   * - provider_name: Human readable name of the provider.
+   * - provider_url: Main URL of the provider.
+   * - endpoints: List of endpoints provider exposes. Each endpoint is an array
+   *   with the following values:
+   *     - url: Endpoint's URL.
+   *     - schemes: List of URL schemes supported by the provider.
+   *     - formats: List of supported formats. Can be "json", "xml" or both.
+   *     - discovery: Whether provider supports oEmbed discovery.
+   *
+   * @return array
+   *   Information about oEmbed providers.
+   */
+  public function getProviders();
+
+  /**
+   * Tries to match the URL against the database of oEmbed providers.
+   *
+   * @param string $url
+   *   URL to be tested
+   * @param array|null $providers_limit
+   *   List of provider machine names to limit too. All providers are considered
+   *   if omitted.
+   *
+   * @return array|false
+   *   Associative array with two values:
+   *   - provider: Matching provider's name.
+   *   - endpoint: URL of the matching endpoint.
+   *   FALSE if a match was not found.
+   */
+  public function matchUrl($url, $providers_limit = NULL);
+
+  /**
+   * Runs oEmbed discovery and returns endpoint URL if successful.
+   *
+   * @param $url
+   *   URL of the resource.
+   *
+   * @return string|false
+   *   Resource specific URL of the oEmbed endpoint or FALSE if the discovery
+   *   was not successful.
+   */
+  public function doUrlDiscovery($url);
+
+
+  /**
+   * Determines whether it is an oEmbed resource.
+   *
+   * @param $url
+   *   URL of the resource.
+   *
+   * @return bool
+   *   TRUE if the URL represents a valid oEmbed resource and FALSE if not.
+   */
+  public function isOEmbedResource($url);
+
+  /**
+   * Fetches information about the oEmbed resource.
+   *
+   * @param $endpoint_url
+   *   Resource specific URL of the oEmbed endpoint.
+   *
+   * @return array|false
+   *   Resource information as returned from the oEmbed endpoint or FALSE if
+   *   the resource could not be fetched.
+   */
+  public function fetchResource($endpoint_url);
+
+}
diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
new file mode 100644
index 0000000..aea168b
--- /dev/null
+++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\media\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FormatterBase;
+
+/**
+ * Plugin implementation of the 'link' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "oembed",
+ *   label = @Translation("oEmbed"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class OEmbedFormatter extends FormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+    $element = [];
+
+    foreach ($items as $delta => $item) {
+      $resource = \Drupal::service('media.oembed')->fetchResource(\Drupal::service('media.oembed')->doUrlDiscovery($item->uri));
+      $element[$delta] = [
+        'data' => [
+          '#markup' => $resource['html'],
+          '#allowed_tags' => ['iframe'],
+        ],
+      ];
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php
new file mode 100644
index 0000000..2d83849
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if a value belongs to an allowed oEmbed provider.
+ *
+ * @Constraint(
+ *   id = "oembed_provider",
+ *   label = @Translation("oEmbed provider", context = "Validation"),
+ *   type = { "link"}
+ * )
+ */
+class OEmbedProviderConstraint extends Constraint {
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'oEmbed provider @provider is not allowed.';
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php
new file mode 100644
index 0000000..de0bb22
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\media\OEmbedInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the OEmbedProvider constraint.
+ */
+class OEmbedProviderConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * The oEmbed service.
+   *
+   * @var \Drupal\media\OEmbed
+   */
+  protected $oEmbed;
+
+  /**
+   * Constructs a new TweetVisibleConstraintValidator.
+   *
+   * @param \Drupal\media\OEmbedInterface $oembed
+   *   The oEmbed service.
+   */
+  public function __construct(OEmbedInterface $oembed) {
+    $this->oEmbed = $oembed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('media.oembed'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    // TODO check if resource belongs to an allowed provider.
+
+
+    //$this->context->addViolation($constraint->message, ['@provider' => 'Not allowed provider']);
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php
new file mode 100644
index 0000000..75475b2
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if a value belongs to an allowed oEmbed provider.
+ *
+ * @Constraint(
+ *   id = "oembed_resource",
+ *   label = @Translation("oEmbed resource", context = "Validation"),
+ *   type = { "link", "string", "string_long"}
+ * )
+ */
+class OEmbedResourceConstraint extends Constraint {
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'Provided URL does not represent a valid oEmbed resource.';
+
+}
diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php
new file mode 100644
index 0000000..7e7d69d
--- /dev/null
+++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\media\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\media\OEmbedInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the OEmbedResource constraint.
+ */
+class OEmbedResourceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * The oEmbed service.
+   *
+   * @var \Drupal\media\OEmbed
+   */
+  protected $oEmbed;
+
+  /**
+   * Constructs a new TweetVisibleConstraintValidator.
+   *
+   * @param \Drupal\media\OEmbedInterface $oembed
+   *   The oEmbed service.
+   */
+  public function __construct(OEmbedInterface $oembed) {
+    $this->oEmbed = $oembed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('media.oembed'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    $main_property = $value->mainPropertyName();
+    if (!$this->oEmbed->isOEmbedResource($value->get($main_property)->getString())) {
+      $this->context->addViolation($constraint->message);
+    }
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/media/Handler/OEmbed.php b/core/modules/media/src/Plugin/media/Handler/OEmbed.php
new file mode 100644
index 0000000..b721b3d
--- /dev/null
+++ b/core/modules/media/src/Plugin/media/Handler/OEmbed.php
@@ -0,0 +1,281 @@
+<?php
+
+namespace Drupal\media\Plugin\media\Handler;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Logger\LoggerChannelInterface;
+use Drupal\link\LinkItemInterface;
+use Drupal\media\MediaHandlerBase;
+use Drupal\media\MediaInterface;
+use Drupal\media\MediaTypeInterface;
+use Drupal\media\OEmbedInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the media handler plugin for oEmbed resources.
+ *
+ * @MediaHandler(
+ *   id = "oembed",
+ *   label = @Translation("OEmbed"),
+ *   description = @Translation("Provides business logic and metadata for oEmbed URLs represented as media."),
+ *   allowed_field_types = {"link", "string", "string_long"}
+ * )
+ */
+class OEmbed extends MediaHandlerBase {
+
+  /**
+   * The logger channel for media.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected $logger;
+
+  /**
+   * oEmbed service.
+   *
+   * @var \Drupal\media\OEmbedInterface
+   */
+  protected $oEmbed;
+
+  /**
+   * Constructs a new OEmbed instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   Entity type manager service.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   Entity field manager service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type plugin manager service.
+   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
+   *   The logger channel for media.
+   * @param \Drupal\media\OEmbedInterface $oembed
+   *   The oEmbed service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, FieldTypePluginManagerInterface $field_type_manager, LoggerChannelInterface $logger, OEmbedInterface $oembed) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $config_factory, $field_type_manager);
+    $this->oEmbed = $oembed;
+    $this->logger = $logger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
+      $container->get('config.factory'),
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('logger.factory')->get('media'),
+      $container->get('media.oembed')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvidedFields() {
+    return [
+      'type' => ['label' => $this->t('Resource type')],
+      'title' => ['label' => $this->t('Resource title')],
+      'author_name' => ['label' => $this->t('The name of the author/owner')],
+      'author_url' => ['label' => $this->t('The URL of the author/owner')],
+      'provider_name' => ['label' => $this->t("The provider's name")],
+      'provider_url' => ['label' => $this->t('The URL of the provider')],
+      'cache_age' => ['label' => $this->t('Suggested cache lifetime')],
+      'thumbnail_url' => ['label' => $this->t('The URL of the thumbnail')],
+      'thumbnail_local_uri' => ['label' => $this->t('The URL of the thumbnail')],
+      'thumbnail_local' => ['label' => $this->t('The URL of the thumbnail')],
+      'thumbnail_width' => ['label' => $this->t('Thumbnail width')],
+      'thumbnail_height' => ['label' => $this->t('Thumbnail height')],
+      'url' => ['label' => $this->t('The source URL of the resource')],
+      'width' => ['label' => $this->t('The width of the resource')],
+      'height' => ['label' => $this->t('The height of the resource')],
+      'html' => ['label' => $this->t('HTML representation of the resource')],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getField(MediaInterface $media, $name) {
+    $main_property = $media->get($this->configuration['source_field'])->first()->mainPropertyName();
+    $resource_url = $media->get($this->configuration['source_field'])->first()->get($main_property)->getString();
+    $oembed_data = $this->oEmbed->fetchResource($this->oEmbed->doUrlDiscovery($resource_url));
+
+    switch ($name) {
+      case 'thumbnail_local':
+        $local_uri = $this->getField($media, 'thumbnail_local_uri');
+
+        if ($local_uri) {
+          if (file_exists($local_uri)) {
+            return $local_uri;
+          }
+          else {
+            $image_data = file_get_contents($this->getField($media, 'thumbnail_url'));
+            if ($image_data) {
+              return file_unmanaged_save_data($image_data, $local_uri, FILE_EXISTS_REPLACE);
+            }
+          }
+        }
+        return FALSE;
+
+      case 'thumbnail_local_uri':
+        $image_url = $this->getField($media, 'thumbnail_url');
+        if ($image_url) {
+          $filename = $this->getField($media, 'provider_name') . '_' . substr(md5($resource_url), 0, 5);
+          return $this->getLocalImageUri($filename, $image_url);
+        }
+        return FALSE;
+
+      default:
+        if (!empty($oembed_data[$name])) {
+          return $oembed_data[$name];
+        }
+        break;
+
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
+    $form['thumbnails_location'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Thumbnails location'),
+      '#default_value' => $this->configuration['thumbnails_location'],
+      '#description' => $this->t('Thumbnails will be fetched from the provider for local usage. This is the location where they will be placed.'),
+    ];
+
+    /*$form['allowed_providers'] = [
+      '#type' => 'select',
+      '#multiple' => TRUE,
+      '#default_value' => $this->configuration['allowed_providers'],
+      '#options' => array_map(function ($provider) { return $provider['provider_name']; }, $this->oEmbed->getProviders()),
+      '#description' => $this->t('Select allowed oEmbed providers for this media type.'),
+      '#attributes' => ['size' => 30],
+    ];*/
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'thumbnails_location' => 'public://oembed_thumbnails',
+      //'allowed_providers' => NULL,
+    ] + parent::defaultConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultThumbnail() {
+    // TODO Use oEmbed specific thumbnail?
+    return $this->configFactory->get('media.settings')
+      ->get('icon_base') . '/no-thumbnail.png';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getThumbnail(MediaInterface $media) {
+    if ($local_image = $this->getField($media, 'thumbnail_local')) {
+      return $local_image;
+    }
+    return $this->getDefaultThumbnail();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createSourceField(MediaTypeInterface $type) {
+    $field = parent::createSourceField($type);
+
+    $settings = $field->getSettings();
+    $settings['title'] = DRUPAL_DISABLED;
+    $settings['link_type'] = LinkItemInterface::LINK_EXTERNAL;
+    $field->set('settings', $settings);
+
+    return $field;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultName(MediaInterface $media) {
+    if ($title = $this->getField($media, 'title')) {
+      return $title;
+    }
+    return parent::getDefaultName($media);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function attachConstraints(MediaInterface $media) {
+    parent::attachConstraints($media);
+
+    if (isset($this->configuration['source_field'])) {
+      if ($media->hasField($this->configuration['source_field'])) {
+        foreach ($media->get($this->configuration['source_field']) as &$url) {
+          /** @var \Drupal\Core\TypedData\DataDefinitionInterface $typed_data */
+          $typed_data = $url->getDataDefinition();
+          $typed_data->addConstraint('oembed_resource');
+          $typed_data->addConstraint('oembed_provider');
+        }
+      }
+    }
+  }
+
+  /**
+   * Computes the destination URI for a thumbnail
+   *
+   * @param string
+   *   Filename without the extension.
+   * @param string $remote_url
+   *   The remote URL of the thumbnail.
+   *
+   * @return string
+   *   The local URI.
+   */
+  protected function getLocalImageUri($filename, $remote_url) {
+    // Ensure that the destination directory is writable. If not, log a warning
+    // and return the default thumbnail.
+    $ready = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    if (!$ready) {
+      $this->logger->warning('Could not prepare thumbnail destination directory @dir for oEmbed media.', [
+        '@dir' => $directory,
+      ]);
+      return $this->getDefaultThumbnail();
+    }
+
+    $local_uri = $this->configuration['thumbnails_location'] . '/' . $filename . '.';
+    $local_uri .= pathinfo($remote_url, PATHINFO_EXTENSION);
+
+    return $local_uri;
+  }
+
+}
\ No newline at end of file
diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.youtube.default.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.youtube.default.yml
new file mode 100644
index 0000000..f107b7d
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_form_display.media.youtube.default.yml
@@ -0,0 +1,38 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.youtube.field_media_oembed
+    - media.type.youtube
+  module:
+    - link
+id: media.youtube.default
+targetEntityType: media
+bundle: youtube
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 1
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_oembed:
+    settings:
+      placeholder_url: ''
+      placeholder_title: ''
+    third_party_settings: {  }
+    type: link_default
+    weight: 2
+    region: content
+  uid:
+    type: entity_reference_autocomplete
+    weight: 0
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden:
+  name: true
diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml
new file mode 100644
index 0000000..301b726
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.youtube.field_media_oembed
+    - image.style.thumbnail
+    - media.type.youtube
+  module:
+    - image
+    - media
+    - user
+id: media.youtube.default
+targetEntityType: media
+bundle: youtube
+mode: default
+content:
+  created:
+    label: hidden
+    type: timestamp
+    weight: 0
+    region: content
+    settings:
+      date_format: medium
+      custom_date_format: ''
+      timezone: ''
+    third_party_settings: {  }
+  field_media_oembed:
+    label: above
+    settings: {  }
+    third_party_settings: {  }
+    type: oembed
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/profiles/standard/config/optional/field.field.media.youtube.field_media_oembed.yml b/core/profiles/standard/config/optional/field.field.media.youtube.field_media_oembed.yml
new file mode 100644
index 0000000..34c6480
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.field.media.youtube.field_media_oembed.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_oembed
+    - media.type.youtube
+  module:
+    - link
+id: media.youtube.field_media_oembed
+field_name: field_media_oembed
+entity_type: media
+bundle: youtube
+label: OEmbed
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  title: 0
+  link_type: 16
+field_type: link
diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_oembed.yml b/core/profiles/standard/config/optional/field.storage.media.field_media_oembed.yml
new file mode 100644
index 0000000..fab31ee
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.storage.media.field_media_oembed.yml
@@ -0,0 +1,18 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - link
+    - media
+id: media.field_media_oembed
+field_name: field_media_oembed
+entity_type: media
+type: link
+settings: {  }
+module: link
+locked: true
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/profiles/standard/config/optional/media.type.youtube.yml b/core/profiles/standard/config/optional/media.type.youtube.yml
new file mode 100644
index 0000000..6bf0836
--- /dev/null
+++ b/core/profiles/standard/config/optional/media.type.youtube.yml
@@ -0,0 +1,13 @@
+langcode: en
+status: true
+dependencies: {  }
+id: youtube
+label: YouTube
+description: 'YouTube videos.'
+handler: oembed
+queue_thumbnail_downloads: false
+new_revision: false
+handler_configuration:
+  thumbnails_location: 'public://oembed_thumbnails'
+  source_field: field_media_oembed
+field_map: {  }
