diff --git a/core/modules/media/src/MediaTypeResolverBase.php b/core/modules/media/src/MediaTypeResolverBase.php
new file mode 100644
index 0000000000..7eb1111e82
--- /dev/null
+++ b/core/modules/media/src/MediaTypeResolverBase.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\media_library;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\media\MediaTypeResolverInterface;
+
+abstract class MediaTypeResolverBase implements MediaTypeResolverInterface {
+
+  /**
+   * The media type storage handler.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $entityTypeManager;
+
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll(array $allowed_types = NULL) {
+    $media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($allowed_types);
+
+    // Filter out any media types which the current user does not have
+    // permission to create.
+    $access_handler = $this->entityTypeManager->getAccessControlHandler('media');
+
+    return array_filter($media_types, [$access_handler, 'createAccess']);
+  }
+
+}
diff --git a/core/modules/media/src/MediaTypeResolverInterface.php b/core/modules/media/src/MediaTypeResolverInterface.php
new file mode 100644
index 0000000000..e3001b9d4d
--- /dev/null
+++ b/core/modules/media/src/MediaTypeResolverInterface.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\media;
+
+use Drupal\Core\Form\FormStateInterface;
+
+interface MediaTypeResolverInterface {
+
+  /**
+   * Returns all media types which can use a given value in their source field.
+   *
+   * @param mixed $value
+   *   The input value to use for the source field.
+   * @param string[] $allowed_types
+   *   (optional) A set of media type IDs.
+   *
+   * @return \Drupal\media\MediaTypeInterface[]
+   *   The media types which can use the input value in their source field. If
+   *   $allowed_types was passed, only media types which appear in that array
+   *   will be returned.
+   */
+  public function getAllByValue($value, array $allowed_types = NULL);
+
+  /**
+   * Returns all media types which can be handled by this resolver.
+   *
+   * @param string[] $allowed_types
+   *   (optional) A set of media type IDs.
+   *
+   * @return \Drupal\media\MediaTypeInterface[]
+   *   The media types which can be handled by this resolver. If $allowed_types
+   *   was passed, only media types which appear in that array will be returned.
+   */
+  public function getAll(array $allowed_types = NULL);
+
+  /**
+   * Alters a form element which will provide a potential source field value.
+   *
+   * @param array $element
+   *   The form element.
+   * @param array $complete_form
+   *   The complete form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current form state.
+   */
+  public function alterFormElement(array &$element, array &$complete_form, FormStateInterface $form_state);
+
+}
diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml
new file mode 100644
index 0000000000..ac745ddbc2
--- /dev/null
+++ b/core/modules/media_library/media_library.services.yml
@@ -0,0 +1,4 @@
+services:
+  media.type_resolver.upload:
+    class: '\Drupal\media_library\UploadMediaTypeResolver'
+    arguments: ['@entity_type.manager']
diff --git a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
index 3ed96b4c4e..94031ec6f1 100644
--- a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
+++ b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
@@ -8,15 +8,12 @@
 use Drupal\Core\Ajax\InvokeCommand;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\ElementInfoManagerInterface;
 use Drupal\file\FileInterface;
-use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
-use Drupal\file\Plugin\Field\FieldType\FileItem;
 use Drupal\media\MediaInterface;
-use Drupal\media\MediaTypeInterface;
+use Drupal\media_library\UploadMediaTypeResolver;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 
@@ -69,6 +66,13 @@ class MediaLibraryUploadForm extends FormBase {
    */
   protected $mediumStyleExists = FALSE;
 
+  /**
+   * The media type resolver service.
+   *
+   * @var \Drupal\media_library\UploadMediaTypeResolver
+   */
+  protected $typeResolver;
+
   /**
    * Constructs a new MediaLibraryUploadForm.
    *
@@ -76,11 +80,14 @@ class MediaLibraryUploadForm extends FormBase {
    *   The entity type manager.
    * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
    *   The element info manager.
+   * @param \Drupal\media_library\UploadMediaTypeResolver $type_resolver
+   *   The media type resolver service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info, UploadMediaTypeResolver $type_resolver) {
     $this->entityTypeManager = $entity_type_manager;
     $this->elementInfo = $element_info;
     $this->mediumStyleExists = !empty($entity_type_manager->getStorage('image_style')->load('medium'));
+    $this->typeResolver = $type_resolver;
   }
 
   /**
@@ -89,7 +96,8 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ele
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity_type.manager'),
-      $container->get('element_info')
+      $container->get('element_info'),
+      $container->get('media.type_resolver.upload')
     );
   }
 
@@ -113,18 +121,18 @@ public function buildForm(array $form, FormStateInterface $form_state) {
 
     if (empty($this->media) && empty($this->files)) {
       $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
-      $upload_validators = $this->mergeUploadValidators($this->getTypes());
       $form['upload'] = [
         '#type' => 'managed_file',
         '#title' => $this->t('Upload'),
         // @todo Move validation in https://www.drupal.org/node/2988215
         '#process' => array_merge(['::validateUploadElement'], $process, ['::processUploadElement']),
-        '#upload_validators' => $upload_validators,
+        '#allowed_types' => $this->getTypes(),
       ];
+      $this->typeResolver->alterFormElement($element, $form, $form_state);
       $form['upload_help'] = [
         '#theme' => 'file_upload_help',
         '#description' => $this->t('Upload files here to add new media.'),
-        '#upload_validators' => $upload_validators,
+        '#upload_validators' => $form['upload']['#upload_validators'],
       ];
       $remaining = $this->getRequest()->query->get('media_library_remaining');
       if ($remaining) {
@@ -191,7 +199,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#type' => 'container',
       ];
       foreach ($this->files as $i => $file) {
-        $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
+        $types = $this->typeResolver->getAllByValue($file, $this->getTypes());
         $form['files'][$i] = [
           '#type' => 'container',
           '#attributes' => [
@@ -285,7 +293,7 @@ public function selectType(array &$form, FormStateInterface $form_state) {
     }
     $i = $element['#media_library_index'];
     $type = $element['#media_library_type'];
-    $this->media[] = $this->createMediaEntity($this->files[$i], $this->getTypes()[$type]);
+    $this->media[] = $this->createMediaEntity($this->files[$i], $type);
     unset($this->files[$i]);
     $form_state->setRebuild();
   }
@@ -381,10 +389,10 @@ public function uploadButtonSubmit(array $form, FormStateInterface $form_state)
     $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids);
     /** @var \Drupal\file\FileInterface $file */
     foreach ($files as $file) {
-      $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
+      $types = $this->typeResolver->getAllByValue($file, $this->getTypes());
       if (!empty($types)) {
         if (count($types) === 1) {
-          $this->media[] = $this->createMediaEntity($file, reset($types));
+          $this->media[] = $this->createMediaEntity($file, key($types));
         }
         else {
           $this->files[] = $file;
@@ -399,8 +407,8 @@ public function uploadButtonSubmit(array $form, FormStateInterface $form_state)
    *
    * @param \Drupal\file\FileInterface $file
    *   A file for the media source field.
-   * @param \Drupal\media\MediaTypeInterface $type
-   *   A media type.
+   * @param string $type
+   *   A media type ID.
    *
    * @return \Drupal\media\MediaInterface
    *   An unsaved media entity.
@@ -408,13 +416,20 @@ public function uploadButtonSubmit(array $form, FormStateInterface $form_state)
    * @throws \Exception
    *   If a file operation failed when moving the upload.
    */
-  protected function createMediaEntity(FileInterface $file, MediaTypeInterface $type) {
+  protected function createMediaEntity(FileInterface $file, $type) {
+    /** @var \Drupal\media\MediaInterface $media */
     $media = $this->entityTypeManager->getStorage('media')->create([
-      'bundle' => $type->id(),
+      'bundle' => $type,
       'name' => $file->getFilename(),
     ]);
-    $source_field = $type->getSource()->getSourceFieldDefinition($type)->getName();
-    $location = $this->getUploadLocationForType($media->bundle->entity);
+    $source_field = $media->bundle->entity->getSource()->getSourceFieldDefinition($type)->getName();
+    $media->set($source_field, $file->id());
+    $location = $media
+      ->set($source_field, $file->id())
+      ->get($source_field)
+      ->first()
+      ->getUploadLocation();
+
     if (!file_prepare_directory($location, FILE_CREATE_DIRECTORY)) {
       throw new \Exception("The destination directory '$location' is not writable");
     }
@@ -422,8 +437,7 @@ protected function createMediaEntity(FileInterface $file, MediaTypeInterface $ty
     if (!$file) {
       throw new \Exception("Unable to move file to '$location'");
     }
-    $media->set($source_field, $file->id());
-    return $media;
+    return $media->set($source_field, $file->id());
   }
 
   /**
@@ -456,160 +470,16 @@ public function access() {
    *
    * @todo Move in https://www.drupal.org/node/2987924
    *
-   * @return \Drupal\media\MediaTypeInterface[]
-   *   A list of media types that are valid for this form.
+   * @return string[]
+   *   A list of media type IDs that are valid for this form.
    */
   protected function getTypes() {
     // Cache results if possible.
     if (!isset($this->types)) {
-      $media_type_storage = $this->entityTypeManager->getStorage('media_type');
       $allowed_types = _media_library_get_allowed_types() ?: NULL;
-      $types = $media_type_storage->loadMultiple($allowed_types);
-      $types = $this->filterTypesWithFileSource($types);
-      $types = $this->filterTypesWithCreateAccess($types);
-      $this->types = $types;
+      $this->types = array_keys($this->typeResolver->getAll($allowed_types));
     }
     return $this->types;
   }
 
-  /**
-   * Filters media types that accept a given file.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\file\FileInterface $file
-   *   A file entity.
-   * @param \Drupal\media\MediaTypeInterface[] $types
-   *   An array of available media types.
-   *
-   * @return \Drupal\media\MediaTypeInterface[]
-   *   An array of media types that accept the file.
-   */
-  protected function filterTypesThatAcceptFile(FileInterface $file, array $types) {
-    $types = $this->filterTypesWithFileSource($types);
-    return array_filter($types, function (MediaTypeInterface $type) use ($file) {
-      $validators = $this->getUploadValidatorsForType($type);
-      $errors = file_validate($file, $validators);
-      return empty($errors);
-    });
-  }
-
-  /**
-   * Filters an array of media types that accept file sources.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface[] $types
-   *   An array of media types.
-   *
-   * @return \Drupal\media\MediaTypeInterface[]
-   *   An array of media types that accept file sources.
-   */
-  protected function filterTypesWithFileSource(array $types) {
-    return array_filter($types, function (MediaTypeInterface $type) {
-      return is_a($type->getSource()->getSourceFieldDefinition($type)->getClass(), FileFieldItemList::class, TRUE);
-    });
-  }
-
-  /**
-   * Merges file upload validators for an array of media types.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface[] $types
-   *   An array of media types.
-   *
-   * @return array
-   *   An array suitable for passing to file_save_upload() or the file field
-   *   element's '#upload_validators' property.
-   */
-  protected function mergeUploadValidators(array $types) {
-    $max_size = 0;
-    $extensions = [];
-    $types = $this->filterTypesWithFileSource($types);
-    foreach ($types as $type) {
-      $validators = $this->getUploadValidatorsForType($type);
-      if (isset($validators['file_validate_size'])) {
-        $max_size = max($max_size, $validators['file_validate_size'][0]);
-      }
-      if (isset($validators['file_validate_extensions'])) {
-        $extensions = array_unique(array_merge($extensions, explode(' ', $validators['file_validate_extensions'][0])));
-      }
-    }
-    // If no field defines a max size, default to the system wide setting.
-    if ($max_size === 0) {
-      $max_size = file_upload_max_size();
-    }
-    return [
-      'file_validate_extensions' => [implode(' ', $extensions)],
-      'file_validate_size' => [$max_size],
-    ];
-  }
-
-  /**
-   * Gets upload validators for a given media type.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface $type
-   *   A media type.
-   *
-   * @return array
-   *   An array suitable for passing to file_save_upload() or the file field
-   *   element's '#upload_validators' property.
-   */
-  protected function getUploadValidatorsForType(MediaTypeInterface $type) {
-    return $this->getFileItemForType($type)->getUploadValidators();
-  }
-
-  /**
-   * Gets upload destination for a given media type.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface $type
-   *   A media type.
-   *
-   * @return string
-   *   An unsanitized file directory URI with tokens replaced.
-   */
-  protected function getUploadLocationForType(MediaTypeInterface $type) {
-    return $this->getFileItemForType($type)->getUploadLocation();
-  }
-
-  /**
-   * Creates a file item for a given media type.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface $type
-   *   A media type.
-   *
-   * @return \Drupal\file\Plugin\Field\FieldType\FileItem
-   *   The file item.
-   */
-  protected function getFileItemForType(MediaTypeInterface $type) {
-    $source = $type->getSource();
-    $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($type));
-    return new FileItem($source_data_definition);
-  }
-
-  /**
-   * Filters an array of media types that can be created by the current user.
-   *
-   * @todo Move in https://www.drupal.org/node/2987924
-   *
-   * @param \Drupal\media\MediaTypeInterface[] $types
-   *   An array of media types.
-   *
-   * @return \Drupal\media\MediaTypeInterface[]
-   *   An array of media types that accept file sources.
-   */
-  protected function filterTypesWithCreateAccess(array $types) {
-    $access_handler = $this->entityTypeManager->getAccessControlHandler('media');
-    return array_filter($types, function (MediaTypeInterface $type) use ($access_handler) {
-      return $access_handler->createAccess($type->id());
-    });
-  }
-
 }
diff --git a/core/modules/media_library/src/UploadMediaTypeResolver.php b/core/modules/media_library/src/UploadMediaTypeResolver.php
new file mode 100644
index 0000000000..f49dedfd6e
--- /dev/null
+++ b/core/modules/media_library/src/UploadMediaTypeResolver.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\media_library;
+
+use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\FileInterface;
+use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
+use Drupal\file\Plugin\Field\FieldType\FileItem;
+use Drupal\media\MediaTypeInterface;
+
+class UploadMediaTypeResolver extends MediaTypeResolverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll(array $allowed_types = NULL) {
+    $media_types = parent::getAll($allowed_types);
+
+    // Filter out media types which do not use a file field as a source.
+    return array_filter($media_types, [$this, 'isFileSource']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAllByValue($value, array $allowed_types = NULL) {
+    if ($value instanceof FileInterface) {
+      $filter = function (MediaTypeInterface $media_type) use ($value) {
+        $errors = file_validate($value, $this->getItem($media_type)->getUploadValidators());
+        return empty($errors);
+      };
+      return array_filter($this->getAll($allowed_types), $filter);
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterFormElement(array &$element, array &$complete_form, FormStateInterface $form_state) {
+    if ($element['#type'] === 'managed_file') {
+      $allowed_types = isset($element['#allowed_types']) ? $element['#allowed_types'] : NULL;
+      $media_types = $this->getAll($allowed_types);
+      $element['#upload_validators'] = $this->mergeUploadValidators($media_types);
+    }
+  }
+
+  protected function mergeUploadValidators(array $media_types) {
+    $max_size = 0;
+    $extensions = [];
+
+    foreach ($media_types as $media_type) {
+      $validators = $this->getItem($media_type)->getUploadValidators();
+
+      if (isset($validators['file_validate_size'])) {
+        $max_size = max($max_size, $validators['file_validate_size'][0]);
+      }
+      if (isset($validators['file_validate_extensions'])) {
+        $extensions = array_merge($extensions, explode(' ', $validators['file_validate_extensions'][0]));
+      }
+    }
+
+    return [
+      'file_validate_extensions' => [
+        implode(' ', array_unique($extensions)),
+      ],
+      'file_validate_size' => [
+        // If no field defines a max size, default to the system-wide setting.
+        $max_size ?: file_upload_max_size(),
+      ],
+    ];
+  }
+
+  protected function getItem(MediaTypeInterface $media_type) {
+    $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
+    $data_definition = FieldItemDataDefinition::create($field_definition);
+    return new FileItem($data_definition);
+  }
+
+  protected function isFileSource(MediaTypeInterface $media_type) {
+    $class = $media_type->getSource()->getSourceFieldDefinition($media_type);
+    return is_a($class, FileFieldItemList::class, TRUE);
+  }
+
+}
