diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml
index 9fbadeff29..3a8651e333 100644
--- a/core/modules/media/media.routing.yml
+++ b/core/modules/media/media.routing.yml
@@ -5,6 +5,14 @@ entity.media.multiple_delete_confirm:
   requirements:
     _permission: 'administer media+delete any media'
 
+media.bulk_upload:
+  path: '/admin/content/media/upload'
+  defaults:
+    _form: '\Drupal\media\Form\MediaBulkUploadForm'
+    _title: 'Bulk Upload Media'
+  requirements:
+    _custom_access: '\Drupal\media\Form\MediaBulkUploadForm::access'
+
 entity.media.revision:
   path: '/media/{media}/revisions/{media_revision}/view'
   defaults:
diff --git a/core/modules/media/src/Form/MediaBulkUploadForm.php b/core/modules/media/src/Form/MediaBulkUploadForm.php
new file mode 100644
index 0000000000..a6bbc76120
--- /dev/null
+++ b/core/modules/media/src/Form/MediaBulkUploadForm.php
@@ -0,0 +1,537 @@
+<?php
+
+namespace Drupal\media\Form;
+
+use Drupal\Core\Access\AccessResultAllowed;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Url;
+use Drupal\file\FileInterface;
+use Drupal\media\MediaTypeInterface;
+use Drupal\media\MediaUploadTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * An AJAX multi-step form to bulk create media entities.
+ */
+class MediaBulkUploadForm extends FormBase {
+
+  use MediaUploadTrait;
+
+  /**
+   * The element info manager.
+   *
+   * @var \Drupal\Core\Render\ElementInfoManagerInterface
+   */
+  protected $elementInfo;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Media types the current user has access to.
+   *
+   * @var \Drupal\media\MediaTypeInterface[]
+   */
+  protected $types;
+
+  /**
+   * The current step of the form.
+   *
+   * @var string
+   */
+  protected $step;
+
+  /**
+   * The original number of uploaded files.
+   *
+   * @var int
+   */
+  protected $originalFileCount;
+
+  /**
+   * The uploaded files left to be processed.
+   *
+   * @var \Drupal\file\FileInterface[]
+   */
+  protected $files;
+
+  /**
+   * The current file being processed.
+   *
+   * @var \Drupal\file\FileInterface
+   */
+  protected $currentFile;
+
+  /**
+   * The currently displayed media form.
+   *
+   * @var \Drupal\Core\Entity\EntityFormInterface
+   */
+  protected $mediaForm;
+
+  /**
+   * The media items created by this form.
+   *
+   * @var \Drupal\media\MediaInterface[]
+   */
+  protected $newMedia;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, ElementInfoManagerInterface $element_info) {
+    $this->entityTypeManager = $entityTypeManager;
+    $this->elementInfo = $element_info;
+
+    // Store media types the current user can create as early as possible.
+    $media_type_storage = $this->entityTypeManager->getStorage('media_type');
+    $types = $media_type_storage->loadMultiple();
+    $types = $this->filterTypesWithFileSource($types);
+    $types = $this->filterTypesWithCreateAccess($types);
+    $this->types = $types;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('element_info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEntityTypeManager() {
+    return $this->entityTypeManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'media_bulk_upload_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Every AJAX callback updates the entire form, so we need to wrap it.
+    $form['#prefix'] = '<div id="media-build-upload-form-wrapper">';
+    $form['#suffix'] = '</div>';
+
+    $form['progress'] = $this->getFormProgress($form_state);
+    // Determine what step of the form we're on.
+    switch ($this->step) {
+      case NULL:
+        $form = $this->buildUploadForm($form, $form_state);
+        break;
+
+      case 'select_media_type':
+        $form = $this->buildMediaTypeSelectionForm($form, $form_state);
+        break;
+
+      case 'show_media_form':
+        $form = $this->buildMediaForm($form, $form_state);
+        break;
+
+      case 'finished':
+        $form = $this->buildFinishedForm($form, $form_state);
+        break;
+    }
+    return $form;
+  }
+
+  /**
+   * Returns a render array representing the form process.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   A render array representing the form progress.
+   */
+  protected function getFormProgress(FormStateInterface $form_state) {
+    $element = [];
+    if ($this->originalFileCount > 1 && $this->step !== 'finished') {
+      $remaining = ($this->originalFileCount - count($this->files));
+      $element = [
+        '#theme' => 'progress_bar',
+        '#label' => $this->t('Processing @remaining of @total files', [
+          '@remaining' => $remaining,
+          '@total' => $this->originalFileCount,
+        ]),
+        '#percent' => floor((($remaining - 1) / $this->originalFileCount) * 100),
+      ];
+    }
+    return $element;
+  }
+
+  /**
+   * Builds a form for the initial file upload.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The form render array.
+   */
+  protected function buildUploadForm(array $form, FormStateInterface $form_state) {
+    $element_info = $this->elementInfo->getInfo('managed_file');
+    $upload_validators = $this->mergeUploadValidators($this->types);
+    $form['upload'] = [
+      '#type' => 'managed_file',
+      '#process' => array_merge($element_info['#process'], ['::processUpload']),
+      '#upload_validators' => $upload_validators,
+      '#multiple' => TRUE,
+    ];
+    $form['upload_help'] = [
+      '#theme' => 'file_upload_help',
+      '#description' => $this->t('Upload files here to start the bulk media creation process.'),
+      '#upload_validators' => $upload_validators,
+    ];
+    return $form;
+  }
+
+  /**
+   * Processes an upload (managed_file) element.
+   *
+   * @param array $element
+   *   The upload element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The processed upload element.
+   */
+  public function processUpload(array $element, FormStateInterface $form_state) {
+    $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
+    $element['upload_button']['#ajax'] = [
+      'callback' => '::updateFormCallback',
+      'wrapper' => 'media-build-upload-form-wrapper',
+    ];
+    // Hide the Managed File element's table display - we don't use it.
+    $element['remove_button']['#access'] = FALSE;
+    foreach ($element['#value']['fids'] as $fid) {
+      if (isset($element['file_' . $fid])) {
+        $element['file_' . $fid]['#access'] = FALSE;
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * Submit handler for the upload button, inside the managed_file element.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
+    $fids = $form_state->getValue('upload', []);
+    // Prepare form state storage to be used in future steps.
+    $this->files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids);
+    $this->originalFileCount = count($this->files);
+    $this->prepareForNextFile($form_state);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Builds a form for selecting a media type for the next uploaded file.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The form render array.
+   */
+  protected function buildMediaTypeSelectionForm(array $form, FormStateInterface $form_state) {
+    /** @var \Drupal\file\FileInterface $file */
+    $valid_types = $this->filterTypesThatAcceptFile($this->currentFile, $this->types);
+    $form['title'] = [
+      '#markup' => t('<p>Select media type to create with <strong>@file</strong></p>', [
+        '@file' => $this->currentFile->label(),
+      ]),
+    ];
+    foreach ($valid_types as $type) {
+      $form[] = [
+        '#type' => 'submit',
+        '#name' => $type->id(),
+        '#value' => $type->label(),
+        '#submit' => ['::mediaTypeSelectSubmit'],
+        '#ajax' => [
+          'callback' => '::updateFormCallback',
+          'wrapper' => 'media-build-upload-form-wrapper',
+        ],
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * Submit handler for the media type select button.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public function mediaTypeSelectSubmit(array &$form, FormStateInterface $form_state) {
+    $media_type_id = $form_state->getTriggeringElement()['#name'];
+    /** @var \Drupal\media\MediaTypeInterface $media_type */
+    $media_type = $this->entityTypeManager
+      ->getStorage('media_type')
+      ->load($media_type_id);
+    $this->prepareForMediaForm($form_state, $media_type);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Builds a form for creating media.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The form render array.
+   */
+  protected function buildMediaForm(array $form, FormStateInterface $form_state) {
+    if (!empty($this->files)) {
+      $save_value = $this->t('Save and continue');
+    }
+    else {
+      $save_value = $this->t('Save and finish');
+    }
+    $form['subform'] = $this->mediaForm->buildForm([], $form_state);
+    $form['subform']['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $save_value,
+      '#submit' => ['::mediaFormSubmit'],
+      '#ajax' => [
+        'callback' => '::updateFormCallback',
+        'wrapper' => 'media-build-upload-form-wrapper',
+      ],
+      '#weight' => $form['subform']['actions']['submit']['#weight'],
+    ];
+    // Replace relative (::*) callbacks in the media form.
+    foreach (['#process', '#entity_builders'] as $key) {
+      foreach ($form['subform'][$key] as &$callback) {
+        if (is_string($callback) && substr($callback, 0, 2) == '::') {
+          $callback = [$this->mediaForm, substr($callback, 2)];
+        }
+      }
+    }
+    return $form;
+  }
+
+  /**
+   * Gets the form entity.
+   *
+   * We implement this method to support some of the sub form's callbacks that
+   * make direct references to $form_state->getFormObject()->getEntity().
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The current form entity.
+   */
+  public function getEntity() {
+    return $this->mediaForm->getEntity();
+  }
+
+  /**
+   * Submit handler for the media form.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public function mediaFormSubmit(array &$form, FormStateInterface $form_state) {
+    // Submit the media form instance and save the new media.
+    $this->mediaForm->submitForm($form, $form_state);
+    $media = $this->mediaForm->getEntity();
+    $media->save();
+    $this->newMedia[] = $media;
+    $this->prepareForNextFile($form_state);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Creates a media form object for a newly uploaded file.
+   *
+   * @param \Drupal\file\FileInterface $file
+   *   A newly uploaded file.
+   * @param \Drupal\media\MediaTypeInterface $type
+   *   A media Type.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface|false
+   *   A media form object, or FALSE if there was an error.
+   */
+  public function createMediaForm(FileInterface $file, MediaTypeInterface $type) {
+    // Move the temporary file to the correct destination.
+    $location = $this->getUploadLocationForType($type);
+    file_prepare_directory($location, FILE_CREATE_DIRECTORY);
+    file_move($file, $location);
+    $media_form = $this->entityTypeManager->getFormObject('media', 'add');
+    /** @var \Drupal\media\MediaInterface $media */
+    $media = $this->entityTypeManager->getStorage('media')->create([
+      'bundle' => $type->id(),
+    ]);
+    $source_field = $type->getSource()->getSourceFieldDefinition($type);
+    $media->set($source_field->getName(), $file->id());
+    $media_form->setEntity($media);
+    return $media_form;
+  }
+
+  /**
+   * Prepares the form for processing the next file.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  protected function prepareForNextFile(FormStateInterface $form_state) {
+    $form_state->setRebuild();
+    // If there are no files left to process, go to the finished step.
+    if (empty($this->files)) {
+      $this->step = 'finished';
+      return;
+    }
+    // Get the next file and update the form state.
+    $this->currentFile = array_shift($this->files);
+    // Determine if we can skip the media type selection page.
+    $valid_types = $this->filterTypesThatAcceptFile($this->currentFile, $this->types);
+    if (count($valid_types) === 1) {
+      $this->prepareForMediaForm($form_state, reset($valid_types));
+    }
+    else {
+      $this->step = 'select_media_type';
+    }
+  }
+
+  /**
+   * Prepares the form to present a media form to the user.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   * @param \Drupal\media\MediaTypeInterface $media_type
+   *   A pre-selected media type.
+   */
+  protected function prepareForMediaForm(FormStateInterface $form_state, MediaTypeInterface $media_type) {
+    $this->clearFormState($form_state);
+    $this->mediaForm = $this->createMediaForm($this->currentFile, $media_type);
+    $this->step = 'show_media_form';
+  }
+
+  /**
+   * Clears all unknown values from the form state object.
+   *
+   * This is necessary as each media form we present needs completely isolated
+   * data, otherwise values get mixed up during the multi step process.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  protected function clearFormState(FormStateInterface $form_state) {
+    $form_state->setStorage([]);
+    $input = $form_state->getUserInput();
+    $input = array_intersect_key($input, array_flip([
+      'form_build_id',
+      'form_token',
+      'form_id',
+    ]));
+    $form_state->setUserInput($input);
+  }
+
+  /**
+   * Builds the confirmation page of the form.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The form render array.
+   */
+  protected function buildFinishedForm(array $form, FormStateInterface $form_state) {
+    $form['header'] = [
+      '#markup' => $this->formatPlural(count($this->newMedia),
+        '<h3>One media item created:</h3>',
+        '<h3>@count media items created:</h3>'),
+    ];
+    $form['list'] = [
+      '#theme' => 'item_list',
+      '#items' => [],
+    ];
+    foreach ($this->newMedia as $item) {
+      $form['list']['#items'][] = $item->toLink()->toRenderable();
+    }
+    $form['actions']['#type'] = 'actions';
+    $form['actions']['return'] = [
+      '#type' => 'link',
+      '#attributes' => ['class' => ['button']],
+      '#url' => Url::fromRoute('entity.media.collection'),
+      '#title' => $this->t('Return to media listing'),
+    ];
+    $form['actions']['upload'] = [
+      '#type' => 'link',
+      '#attributes' => ['class' => ['button', 'button--primary']],
+      '#url' => Url::fromRoute('media.bulk_upload'),
+      '#title' => $this->t('Upload more'),
+    ];
+    return $form;
+  }
+
+  /**
+   * AJAX callback for refreshing the entire form.
+   *
+   * @param array $form
+   *   The form render array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The form render array.
+   */
+  public function updateFormCallback(array &$form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if ($this->step === 'show_media_form') {
+      $this->mediaForm->validateForm($form['subform'], $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {}
+
+  /**
+   * Access callback to check that the user can create file based media.
+   */
+  public function access() {
+    return AccessResultAllowed::allowedIf(count($this->types))->mergeCacheMaxAge(0);
+  }
+
+}
diff --git a/core/modules/media/src/MediaUploadTrait.php b/core/modules/media/src/MediaUploadTrait.php
new file mode 100644
index 0000000000..8e907fb242
--- /dev/null
+++ b/core/modules/media/src/MediaUploadTrait.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace Drupal\media;
+
+use Drupal\file\FileInterface;
+use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\file\Plugin\Field\FieldType\FileItem;
+
+/**
+ * Provides generic methods to support media file uploads.
+ */
+trait MediaUploadTrait {
+
+  /**
+   * Returns the entity type manager.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+   *   The entity type manager.
+   */
+  abstract protected function getEntityTypeManager();
+
+  /**
+   * Filters media types that accept a given file.
+   *
+   * @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) {
+    $valid_types = [];
+    $types = $this->filterTypesWithFileSource($types);
+    foreach ($types as $type) {
+      $validators = $this->getUploadValidatorsForType($type);
+      $errors = file_validate($file, $validators);
+      if (empty($errors)) {
+        $valid_types[] = $type;
+      }
+    }
+    return $valid_types;
+  }
+
+  /**
+   * Filters an array of media types that accept file sources.
+   *
+   * @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) {
+    $valid_types = [];
+    foreach ($types as $type) {
+      $source = $type->getSource();
+      if (is_a($source, 'Drupal\media\Plugin\media\Source\File')) {
+        $valid_types[] = $type;
+      }
+    }
+    return $valid_types;
+  }
+
+  /**
+   * Merges file upload validators for an array of media types.
+   *
+   * @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);
+      $max_size = max($max_size, $validators['file_validate_size'][0]);
+      $extensions = array_unique(array_merge($extensions, explode(' ', $validators['file_validate_extensions'][0])));
+    }
+    return [
+      'file_validate_extensions' => [implode(' ', $extensions)],
+      'file_validate_size' => [$max_size],
+    ];
+  }
+
+  /**
+   * Gets upload validators for a given Media Type.
+   *
+   * @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) {
+    $file_item = $this->getFileItemForType($type);
+    return $file_item->getUploadValidators();
+  }
+
+  /**
+   * Gets upload destination for a given Media Type.
+   *
+   * @param \Drupal\media\MediaTypeInterface $type
+   *   A Media Type.
+   *
+   * @return string
+   *   An unsanitized file directory URI with tokens replaced.
+   */
+  protected function getUploadLocationForType(MediaTypeInterface $type) {
+    $file_item = $this->getFileItemForType($type);
+    return $file_item->getUploadLocation();
+  }
+
+  /**
+   * Creates a file item for a given Media Type.
+   *
+   * @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.
+   *
+   * @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) {
+    $valid_types = [];
+    $access_handler = $this->getEntityTypeManager()->getAccessControlHandler('media');
+    foreach ($types as $type) {
+      if ($access_handler->createAccess($type->id())) {
+        $valid_types[] = $type;
+      }
+    }
+    return $valid_types;
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaBulkUploadFormTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaBulkUploadFormTest.php
new file mode 100644
index 0000000000..8e08090878
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaBulkUploadFormTest.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\Tests\TestFileCreationTrait;
+
+/**
+ * Tests related to media bulk upload form.
+ *
+ * @group media
+ */
+class MediaBulkUploadFormTest extends MediaJavascriptTestBase {
+
+  use TestFileCreationTrait;
+
+  /**
+   * Test the media bulk upload form.
+   *
+   * @see \Drupal\media\Form\MediaBulkUploadForm
+   */
+  public function testBulkUploadForm() {
+    $assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // We have no media types that accept files, so the page should 403.
+    $this->drupalGet('/admin/content/media/upload');
+    $assert->statusCodeEquals(403);
+
+    // Create three media types - two of which share a file extension (.gif).
+    $this->createMediaType(['bundle' => 'file'], 'file');
+    $this->createMediaType(['bundle' => 'image'], 'image');
+    $type = $this->createMediaType(['bundle' => 'animated_image'], 'image');
+    /** @var \Drupal\field\FieldConfigInterface $field_definition */
+    $field_definition = $type->getSource()->getSourceFieldDefinition($type);
+    $settings = $field_definition->get('settings');
+    $settings['file_extensions'] = 'gif foobar';
+    $field_definition->set('settings', $settings);
+    $field_definition->save();
+
+    // The page should load normally.
+    $this->drupalGet('/admin/content/media/upload');
+    $assert->statusCodeEquals(200);
+    // The upload help text should show a combined list of allowed extensions.
+    $assert->pageTextContains('Allowed types: gif foobar txt doc docx pdf png jpg jpeg.');
+
+    // Switch to a low-user who can't create the "file" media type.
+    $user = $this->drupalCreateUser([
+      'create image media',
+      'create animated_image media',
+    ]);
+    $this->drupalLogin($user);
+    $this->drupalGet('/admin/content/media/upload');
+    $assert->statusCodeEquals(200);
+    // The file media type's extensions should be missing.
+    $assert->pageTextContains('Allowed types: gif foobar png jpg jpeg.');
+
+    // Sort test files by extension.
+    $test_files = [];
+    foreach ($this->getTestFiles('image') as $image) {
+      $extension = pathinfo($image->filename, PATHINFO_EXTENSION);
+      $test_files[$extension] = isset($test_files[$extension]) ? $test_files[$extension] : [];
+      $test_files[$extension][] = $image;
+    }
+
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = \Drupal::service('file_system');
+
+    // Try a JPG, which should not prompt for a media type.
+    $file = reset($test_files['jpg']);
+    $page->attachFileToField('files[upload][]', $file_system->realpath($file->uri));
+    $assert->assertWaitOnAjaxRequest();
+    $assert->pageTextNotContains('Select media type to create');
+
+    // Try a GIF, which should prompt for a media type to create.
+    $this->drupalGet('/admin/content/media/upload');
+    $file = reset($test_files['gif']);
+    $page->attachFileToField('files[upload][]', $file_system->realpath($file->uri));
+    $assert->assertWaitOnAjaxRequest();
+    $assert->pageTextContains('Select media type to create');
+    $assert->elementExists('css', '[value="animated_image"]');
+    $assert->elementExists('css', '[value="image"]');
+    $page->pressButton('animated_image');
+    $assert->assertWaitOnAjaxRequest();
+    $page->fillField('name[0][value]', 'My animated media');
+    $page->fillField('field_media_image_1[0][alt]', 'A great GIF');
+    $page->pressButton('Save and finish');
+    $assert->assertWaitOnAjaxRequest();
+    $assert->pageTextContains('One media item created');
+    $assert->pageTextContains('My animated media');
+  }
+
+}
