diff --git a/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml b/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7889cd0fc174168642408c76d26cb73b69f493f2
--- /dev/null
+++ b/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: media.media_library
+label: 'Media library'
+targetEntityType: media
+cache: true
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56df385a2e1586b1cc44ca7d6480aa66800afe89
--- /dev/null
+++ b/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml
@@ -0,0 +1,26 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.audio.field_media_audio_file
+    - media.type.audio
+id: media.audio.media_library
+targetEntityType: media
+bundle: audio
+mode: media_library
+content:
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  field_media_audio_file: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a0a5d49d903e59927d5ca1862c69658f48b690fc
--- /dev/null
+++ b/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml
@@ -0,0 +1,26 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.file.field_media_file
+    - media.type.file
+id: media.file.media_library
+targetEntityType: media
+bundle: file
+mode: media_library
+content:
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  field_media_file: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..68ca01ae64486ea441a1f5caf96fe80762c09f96
--- /dev/null
+++ b/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml
@@ -0,0 +1,36 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.image.field_media_image
+    - image.style.thumbnail
+    - media.type.image
+  module:
+    - image
+id: media.image.media_library
+targetEntityType: media
+bundle: image
+mode: media_library
+content:
+  field_media_image:
+    type: image_image
+    weight: 1
+    region: content
+    settings:
+      progress_indicator: throbber
+      preview_image_style: thumbnail
+    third_party_settings: {  }
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b7caf5dbfeb9f1b0d6a50a2314be0f4362d805e1
--- /dev/null
+++ b/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml
@@ -0,0 +1,26 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.video.field_media_video_file
+    - media.type.video
+id: media.video.media_library
+targetEntityType: media
+bundle: video
+mode: media_library
+content:
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  field_media_video_file: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/css/media_library.theme.css b/core/modules/media_library/css/media_library.theme.css
index 5aab3c5bbd17118340a3d677868267aabfb94505..4f04445dd5c75e35340a5f4c9e6452ddcaf6cceb 100644
--- a/core/modules/media_library/css/media_library.theme.css
+++ b/core/modules/media_library/css/media_library.theme.css
@@ -198,6 +198,44 @@
   border-color: #40b6ff;
 }
 
+/* Style the wrappers around new media and files */
+.media-library-upload__media,
+.media-library-upload__file {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 20px 0 20px 0;
+}
+
+.media-library-upload__file {
+  align-items: center;
+}
+
+.media-library-upload__file-label {
+  margin-right: 10px;
+}
+
+/* @todo Remove in https://www.drupal.org/project/drupal/issues/2987921 */
+.media-library-upload__source-field .file,
+.media-library-upload__source-field .button,
+.media-library-upload__source-field .image-preview,
+.media-library-upload__source-field .form-type-managed-file > label,
+.media-library-upload__source-field .file-size {
+  display: none;
+}
+
+.media-library-upload__media-preview {
+  margin-right: 20px;
+  width: 220px;
+  background: #ebebeb;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-library-upload__media-preview img {
+  display: block;
+}
+
 /* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
 .media-library-widget .media-library-item__name a,
 .media-library-view.view-display-id-widget .media-library-item__name a {
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index b72a4d47ce7c145669176863a4cdb695ec6be862..977da24ae5d7c8fa390faa2adca3b6b0dbd8e757 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -6,11 +6,13 @@
  */
 
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Component\Serialization\Json;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
 use Drupal\views\Form\ViewsForm;
 use Drupal\views\Plugin\views\cache\CachePluginBase;
 use Drupal\views\Plugin\views\query\QueryPluginBase;
@@ -42,6 +44,36 @@ function media_library_theme() {
   ];
 }
 
+/**
+ * Implements hook_preprocess_view().
+ *
+ * Adds a link to add media above the view.
+ */
+function media_library_preprocess_views_view(&$variables) {
+  $view = $variables['view'];
+  if ($view->id() === 'media_library' && $view->current_display === 'widget') {
+    $url = Url::fromRoute('media_library.upload');
+    if ($url->access()) {
+      $url->setOption('query', \Drupal::request()->query->all());
+      $variables['header']['add_media'] = [
+        '#type' => 'link',
+        '#title' => t('Add media'),
+        '#url' => $url,
+        '#attributes' => [
+          'class' => ['button', 'button-action', 'button--primary', 'use-ajax'],
+          'data-dialog-type' => 'modal',
+          'data-dialog-options' => Json::encode([
+            'dialogClass' => 'media-library-widget-modal',
+            'height' => '75%',
+            'width' => '75%',
+            'title' => t('Add media'),
+          ]),
+        ],
+      ];
+    }
+  }
+}
+
 /**
  * Implements hook_views_post_render().
  */
diff --git a/core/modules/media_library/media_library.routing.yml b/core/modules/media_library/media_library.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1724760acb1e0820bbd077c366dd70dc7999a8ad
--- /dev/null
+++ b/core/modules/media_library/media_library.routing.yml
@@ -0,0 +1,6 @@
+media_library.upload:
+  path: '/admin/content/media-widget-upload'
+  defaults:
+    _form: '\Drupal\media_library\Form\MediaLibraryUploadForm'
+  requirements:
+    _custom_access: '\Drupal\media_library\Form\MediaLibraryUploadForm::access'
diff --git a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ed96b4c4e2c1cb7a99897d3b08be1e369e05d39
--- /dev/null
+++ b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
@@ -0,0 +1,615 @@
+<?php
+
+namespace Drupal\media_library\Form;
+
+use Drupal\Core\Access\AccessResultAllowed;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseDialogCommand;
+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 Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Creates a form to create media entities from uploaded files.
+ *
+ * @internal
+ */
+class MediaLibraryUploadForm extends FormBase {
+
+  /**
+   * 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 media being processed.
+   *
+   * @var \Drupal\media\MediaInterface[]
+   */
+  protected $media = [];
+
+  /**
+   * The files waiting for type selection.
+   *
+   * @var \Drupal\file\FileInterface[]
+   */
+  protected $files = [];
+
+  /**
+   * Indicates whether the 'medium' image style exists.
+   *
+   * @var bool
+   */
+  protected $mediumStyleExists = FALSE;
+
+  /**
+   * Constructs a new MediaLibraryUploadForm.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
+   *   The element info manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->elementInfo = $element_info;
+    $this->mediumStyleExists = !empty($entity_type_manager->getStorage('image_style')->load('medium'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('element_info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'media_library_upload_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['#prefix'] = '<div id="media-library-upload-wrapper">';
+    $form['#suffix'] = '</div>';
+
+    $form['#attached']['library'][] = 'media_library/style';
+
+    $form['#attributes']['class'][] = 'media-library-upload';
+
+    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,
+      ];
+      $form['upload_help'] = [
+        '#theme' => 'file_upload_help',
+        '#description' => $this->t('Upload files here to add new media.'),
+        '#upload_validators' => $upload_validators,
+      ];
+      $remaining = $this->getRequest()->query->get('media_library_remaining');
+      if ($remaining) {
+        $form['upload']['#multiple'] = $remaining > 1;
+        $form['upload']['#cardinality'] = $form['upload_help']['#cardinality'] = (int) $remaining;
+      }
+    }
+    else {
+      $form['media'] = [
+        '#type' => 'container',
+      ];
+      foreach ($this->media as $i => $media) {
+        $source_field = $media->getSource()
+          ->getSourceFieldDefinition($media->bundle->entity)
+          ->getName();
+
+        $element = [
+          '#type' => 'container',
+          '#attributes' => [
+            'class' => [
+              'media-library-upload__media',
+            ],
+          ],
+          'preview' => [
+            '#type' => 'container',
+            '#attributes' => [
+              'class' => [
+                'media-library-upload__media-preview',
+              ],
+            ],
+          ],
+          'fields' => [
+            '#type' => 'container',
+            '#attributes' => [
+              'class' => [
+                'media-library-upload__media-fields',
+              ],
+            ],
+            // Parents is set here as it is used in the form display.
+            '#parents' => ['media', $i, 'fields'],
+          ],
+        ];
+        // @todo Make this configurable in https://www.drupal.org/node/2988223
+        if ($this->mediumStyleExists && $thumbnail_uri = $media->getSource()->getMetadata($media, 'thumbnail_uri')) {
+          $element['preview']['thumbnail'] = [
+            '#theme' => 'image_style',
+            '#style_name' => 'medium',
+            '#uri' => $thumbnail_uri,
+          ];
+        }
+        EntityFormDisplay::collectRenderDisplay($media, 'media_library')
+          ->buildForm($media, $element['fields'], $form_state);
+        // We hide certain elements in the image widget with CSS.
+        if (isset($element['fields'][$source_field])) {
+          $element['fields'][$source_field]['#attributes']['class'][] = 'media-library-upload__source-field';
+        }
+        if (isset($element['fields']['revision_log_message'])) {
+          $element['fields']['revision_log_message']['#access'] = FALSE;
+        }
+        $form['media'][$i] = $element;
+      }
+
+      $form['files'] = [
+        '#type' => 'container',
+      ];
+      foreach ($this->files as $i => $file) {
+        $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
+        $form['files'][$i] = [
+          '#type' => 'container',
+          '#attributes' => [
+            'class' => [
+              'media-library-upload__file',
+            ],
+          ],
+          'help' => [
+            '#markup' => '<strong class="media-library-upload__file-label">' . $this->t('Select a media type for %filename:', [
+              '%filename' => $file->getFilename(),
+            ]) . '</strong>',
+          ],
+        ];
+        foreach ($types as $type) {
+          $form['files'][$i][$type->id()] = [
+            '#type' => 'submit',
+            '#media_library_index' => $i,
+            '#media_library_type' => $type->id(),
+            '#value' => $type->label(),
+            '#submit' => ['::selectType'],
+            '#ajax' => [
+              'callback' => '::updateFormCallback',
+              'wrapper' => 'media-library-upload-wrapper',
+            ],
+            '#limit_validation_errors' => [['files', $i, $type->id()]],
+          ];
+        }
+      }
+
+      $form['actions'] = [
+        '#type' => 'actions',
+      ];
+      $form['actions']['submit'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Save'),
+        '#ajax' => [
+          'callback' => '::updateWidget',
+          'wrapper' => 'media-library-upload-wrapper',
+        ],
+      ];
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if (count($this->files)) {
+      $form_state->setError($form['files'], $this->t('Please select a media type for all files.'));
+    }
+    foreach ($this->media as $i => $media) {
+      $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
+      $form_display->extractFormValues($media, $form['media'][$i]['fields'], $form_state);
+      $form_display->validateFormValues($media, $form['media'][$i]['fields'], $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    foreach ($this->media as $i => $media) {
+      EntityFormDisplay::collectRenderDisplay($media, 'media_library')
+        ->extractFormValues($media, $form['media'][$i]['fields'], $form_state);
+      $source_field = $media->getSource()->getSourceFieldDefinition($media->bundle->entity)->getName();
+      /** @var \Drupal\file\FileInterface $file */
+      $file = $media->get($source_field)->entity;
+      $file->setPermanent();
+      $file->save();
+      $media->save();
+    }
+  }
+
+  /**
+   * AJAX callback to select a media type for a file.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   If the triggering element is missing required properties.
+   */
+  public function selectType(array &$form, FormStateInterface $form_state) {
+    $element = $form_state->getTriggeringElement();
+    if (!isset($element['#media_library_index']) || !isset($element['#media_library_type'])) {
+      throw new BadRequestHttpException('The "#media_library_index" and "#media_library_type" properties on the triggering element are required for type selection.');
+    }
+    $i = $element['#media_library_index'];
+    $type = $element['#media_library_type'];
+    $this->media[] = $this->createMediaEntity($this->files[$i], $this->getTypes()[$type]);
+    unset($this->files[$i]);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * AJAX callback to update the field widget.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   A command to send the selection to the current field widget.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   If the "media_library_widget_id" query parameter is not present.
+   */
+  public function updateWidget(array &$form, FormStateInterface $form_state) {
+    if ($form_state->getErrors()) {
+      return $form;
+    }
+    $widget_id = $this->getRequest()->query->get('media_library_widget_id');
+    if (!$widget_id || !is_string($widget_id)) {
+      throw new BadRequestHttpException('The "media_library_widget_id" query parameter is required and must be a string.');
+    }
+    $mids = array_map(function (MediaInterface $media) {
+      return $media->id();
+    }, $this->media);
+    // Pass the selection to the field widget based on the current widget ID.
+    return (new AjaxResponse())
+      ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$widget_id\"]", 'val', [implode(',', $mids)]))
+      ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_id\"]", 'trigger', ['mousedown']))
+      ->addCommand(new CloseDialogCommand());
+  }
+
+  /**
+   * 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 processUploadElement(array $element, FormStateInterface $form_state) {
+    $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
+    $element['upload_button']['#ajax'] = [
+      'callback' => '::updateFormCallback',
+      'wrapper' => 'media-library-upload-wrapper',
+    ];
+    return $element;
+  }
+
+  /**
+   * Validates the upload 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 validateUploadElement(array $element, FormStateInterface $form_state) {
+    if ($form_state->getErrors()) {
+      $element['#value'] = [];
+    }
+    $values = $form_state->getValue('upload', []);
+    if (count($values['fids']) > $element['#cardinality']) {
+      $form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
+        '@count' => $element['#cardinality'],
+      ]));
+      $form_state->setValue('upload', []);
+      $element['#value'] = [];
+    }
+    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', []);
+    $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids);
+    /** @var \Drupal\file\FileInterface $file */
+    foreach ($files as $file) {
+      $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
+      if (!empty($types)) {
+        if (count($types) === 1) {
+          $this->media[] = $this->createMediaEntity($file, reset($types));
+        }
+        else {
+          $this->files[] = $file;
+        }
+      }
+    }
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Creates a new, unsaved media entity.
+   *
+   * @param \Drupal\file\FileInterface $file
+   *   A file for the media source field.
+   * @param \Drupal\media\MediaTypeInterface $type
+   *   A media type.
+   *
+   * @return \Drupal\media\MediaInterface
+   *   An unsaved media entity.
+   *
+   * @throws \Exception
+   *   If a file operation failed when moving the upload.
+   */
+  protected function createMediaEntity(FileInterface $file, MediaTypeInterface $type) {
+    $media = $this->entityTypeManager->getStorage('media')->create([
+      'bundle' => $type->id(),
+      'name' => $file->getFilename(),
+    ]);
+    $source_field = $type->getSource()->getSourceFieldDefinition($type)->getName();
+    $location = $this->getUploadLocationForType($media->bundle->entity);
+    if (!file_prepare_directory($location, FILE_CREATE_DIRECTORY)) {
+      throw new \Exception("The destination directory '$location' is not writable");
+    }
+    $file = file_move($file, $location);
+    if (!$file) {
+      throw new \Exception("Unable to move file to '$location'");
+    }
+    $media->set($source_field, $file->id());
+    return $media;
+  }
+
+  /**
+   * 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;
+  }
+
+  /**
+   * Access callback to check that the user can create file based media.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access() {
+    return AccessResultAllowed::allowedIf(count($this->getTypes()))->mergeCacheMaxAge(0);
+  }
+
+  /**
+   * Returns media types which use files that the current user can create.
+   *
+   * @todo Move in https://www.drupal.org/node/2987924
+   *
+   * @return \Drupal\media\MediaTypeInterface[]
+   *   A list of media types 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;
+    }
+    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/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index 86aa2e979539707d5db4543caff052156604b585..290800241f785cebc54cfa22c8abe56232d1aa46 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -124,31 +124,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       ],
     ];
 
-    // @todo Remove in https://www.drupal.org/project/drupal/issues/2938116
-    $allowed_bundles = !empty($element['#target_bundles']) ? $element['#target_bundles'] : [];
-    $add_url = _media_get_add_url($allowed_bundles);
-    if ($add_url) {
-      $element['create_help'] = [
-        '#type' => 'container',
-      ];
-      $element['create_help']['label'] = [
-        '#type' => 'html_tag',
-        '#tag' => 'h4',
-        '#attributes' => [
-          'class' => ['label'],
-        ],
-        '#value' => $this->t('Create new media'),
-      ];
-      $element['create_help']['description'] = [
-        '#type' => 'html_tag',
-        '#tag' => 'div',
-        '#attributes' => [
-          'class' => ['description'],
-        ],
-        '#value' => $this->t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then select it in the library.', [':add_page' => $add_url]),
-      ];
-    }
-
     $element['selection'] = [
       '#type' => 'container',
       '#attributes' => [
@@ -242,6 +217,18 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       $element['#description'] .= '<br />' . $cardinality_message;
     }
 
+    $query = [
+      'media_library_widget_id' => $field_name . $id_suffix,
+      'media_library_allowed_types' => $element['#target_bundles'],
+      'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining,
+    ];
+    $dialog_options = Json::encode([
+      'dialogClass' => 'media-library-widget-modal',
+      'height' => '75%',
+      'width' => '75%',
+      'title' => $this->t('Media library'),
+    ]);
+
     // Add a button that will load the Media library in a modal using AJAX.
     $element['media_library_open_button'] = [
       '#type' => 'link',
@@ -249,27 +236,36 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#name' => $field_name . '-media-library-open-button' . $id_suffix,
       // @todo Make the view configurable in https://www.drupal.org/project/drupal/issues/2971209
       '#url' => Url::fromRoute('view.media_library.widget', [], [
-        'query' => [
-          'media_library_widget_id' => $field_name . $id_suffix,
-          'media_library_allowed_types' => $element['#target_bundles'],
-          'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining,
-        ],
+        'query' => $query,
       ]),
       '#attributes' => [
         'class' => ['button', 'use-ajax', 'media-library-open-button'],
         'data-dialog-type' => 'modal',
-        'data-dialog-options' => Json::encode([
-          'dialogClass' => 'media-library-widget-modal',
-          'height' => '75%',
-          'width' => '75%',
-          'title' => $this->t('Media library'),
-        ]),
+        'data-dialog-options' => $dialog_options,
       ],
       // Prevent errors in other widgets from preventing addition.
       '#limit_validation_errors' => $limit_validation_errors,
       '#access' => $cardinality_unlimited || $remaining > 0,
     ];
 
+    $add_url = Url::fromRoute('media_library.upload', [], [
+      'query' => $query,
+    ]);
+    $element['media_library_add_button'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Add media'),
+      '#name' => $field_name . '-media-library-add-button' . $id_suffix,
+      '#url' => $add_url,
+      '#attributes' => [
+        'class' => ['button', 'use-ajax', 'media-library-add-button'],
+        'data-dialog-type' => 'modal',
+        'data-dialog-options' => $dialog_options,
+      ],
+      // Prevent errors in other widgets from preventing addition.
+      '#limit_validation_errors' => $limit_validation_errors,
+      '#access' => $add_url->access() && ($cardinality_unlimited || $remaining > 0),
+    ];
+
     // This hidden field and button are used to add new items to the widget.
     $element['media_library_selection'] = [
       '#type' => 'hidden',
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..40d4d1bd31c01f5f161dc60d1ab197b998dd37d7
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml
@@ -0,0 +1,69 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_four.field_media_test_image
+    - field.field.media.type_four.field_media_extra_image
+    - image.style.medium
+    - media.type.type_four
+  module:
+    - image
+    - path
+id: media.type_four.default
+targetEntityType: media
+bundle: type_four
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test_image:
+    weight: 0
+    settings:
+      progress_indicator: throbber
+      preview_image_style: medium
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  field_media_extra_image:
+    weight: 1
+    settings:
+      progress_indicator: throbber
+      preview_image_style: medium
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  path:
+    type: path
+    weight: 30
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 100
+    region: content
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0abef4849c1705516bad4aab95046edab29aa242
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml
@@ -0,0 +1,44 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.type_four.field_media_test_image
+    - image.style.thumbnail
+    - media.type.type_four
+  module:
+    - image
+id: media.type_four.media_library
+targetEntityType: media
+bundle: type_four
+mode: media_library
+content:
+  field_media_test_image:
+    weight: 2
+    settings:
+      progress_indicator: throbber
+      preview_image_style: thumbnail
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  field_media_extra_image:
+    weight: 1
+    settings:
+      progress_indicator: throbber
+      preview_image_style: medium
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ea7248e2aa3e180f97708e6873ccc6989f57a8f8
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml
@@ -0,0 +1,60 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_three.field_media_test_image
+    - image.style.medium
+    - media.type.type_three
+  module:
+    - image
+    - path
+id: media.type_three.default
+targetEntityType: media
+bundle: type_three
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_test_image:
+    weight: 0
+    settings:
+      progress_indicator: throbber
+      preview_image_style: medium
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  path:
+    type: path
+    weight: 30
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 100
+    region: content
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    region: content
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7fdfc74f9b6d82bfc084d25656aec60a5a6f741
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml
@@ -0,0 +1,36 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_form_mode.media.media_library
+    - field.field.media.type_three.field_media_test_image
+    - image.style.thumbnail
+    - media.type.type_three
+  module:
+    - image
+id: media.type_three.media_library
+targetEntityType: media
+bundle: type_three
+mode: media_library
+content:
+  field_media_test_image:
+    weight: 1
+    settings:
+      progress_indicator: throbber
+      preview_image_style: thumbnail
+    third_party_settings: {  }
+    type: image_image
+    region: content
+  name:
+    type: string_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden:
+  created: true
+  path: true
+  status: true
+  uid: true
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de6650e2b49d2dffd3fa7b20bc25977363e60dfb
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml
@@ -0,0 +1,52 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.type_three.field_media_test_image
+    - image.style.thumbnail
+    - media.type.type_three
+  module:
+    - image
+    - user
+id: media.type_three.default
+targetEntityType: media
+bundle: type_three
+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_test_image:
+    weight: 6
+    label: above
+    settings:
+      image_style: ''
+      image_link: ''
+    third_party_settings: {  }
+    type: image
+    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:
+  name: true
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3bd38cb4d20161fe8d362c05cb7cf8f5b075f02d
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_extra_image
+    - media.type.type_four
+  module:
+    - image
+id: media.type_three.field_media_extra_image
+field_name: field_media_extra_image
+entity_type: media
+bundle: type_four
+label: Extra Image
+description: ''
+required: false
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_extensions: 'jpg'
+  alt_field: false
+  alt_field_required: false
+  title_field: false
+  title_field_required: false
+  max_resolution: ''
+  min_resolution: ''
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  file_directory: 'type-four-extra-dir'
+  max_filesize: ''
+  handler: 'default:file'
+  handler_settings: {  }
+field_type: image
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e1d4e5194de4d4bd222e47b91a975d25fec31872
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test_image
+    - media.type.type_four
+  module:
+    - image
+id: media.type_three.field_media_test_image
+field_name: field_media_test_image
+entity_type: media
+bundle: type_four
+label: Image
+description: ''
+required: true
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_extensions: 'jpg'
+  alt_field: true
+  alt_field_required: true
+  title_field: false
+  title_field_required: false
+  max_resolution: ''
+  min_resolution: ''
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  file_directory: 'type-four-dir'
+  max_filesize: ''
+  handler: 'default:file'
+  handler_settings: {  }
+field_type: image
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..27bce7e4780655ba088cb29b3409de529a8a0468
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml
@@ -0,0 +1,37 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_test_image
+    - media.type.type_three
+  module:
+    - image
+id: media.type_three.field_media_test_image
+field_name: field_media_test_image
+entity_type: media
+bundle: type_three
+label: Image
+description: ''
+required: true
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_extensions: 'png gif jpg jpeg'
+  alt_field: true
+  alt_field_required: true
+  title_field: false
+  title_field_required: false
+  max_resolution: ''
+  min_resolution: ''
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  file_directory: 'type-three-dir'
+  max_filesize: ''
+  handler: 'default:file'
+  handler_settings: {  }
+field_type: image
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml
index 7d4d8cdc0a01ed74c83abb8bfda043759b2eec36..5c874b736f63dd75614be42fcbccf01c14175ad5 100644
--- a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml
@@ -22,6 +22,8 @@ settings:
     target_bundles:
       type_one: type_one
       type_two: type_two
+      type_three: type_three
+      type_four: type_four
     sort:
       field: _none
     auto_create: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a81f94ab23c8bd5a45eca95a33cf629e889c00f0
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - file
+    - image
+    - media
+id: media.field_media_extra_image
+field_name: field_media_extra_image
+entity_type: media
+type: image
+settings:
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  target_type: file
+  display_field: false
+  display_default: false
+  uri_scheme: public
+module: image
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml
new file mode 100644
index 0000000000000000000000000000000000000000..db94e9312ab1255465a3c5a24914f0c26315b33b
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - file
+    - image
+    - media
+id: media.field_media_test_image
+field_name: field_media_test_image
+entity_type: media
+type: image
+settings:
+  default_image:
+    uuid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  target_type: file
+  display_field: false
+  display_default: false
+  uri_scheme: public
+module: image
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml
new file mode 100644
index 0000000000000000000000000000000000000000..087c34ed063b882c11d21d6b2be1080b14bba4d2
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml
@@ -0,0 +1,12 @@
+langcode: en
+status: true
+dependencies: {  }
+id: type_four
+label: 'Type Four'
+description: ''
+source: image
+queue_thumbnail_downloads: false
+new_revision: false
+source_configuration:
+  source_field: field_media_test_image
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml
new file mode 100644
index 0000000000000000000000000000000000000000..536de34492a24f547962520e908ac0913d9cfa5a
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml
@@ -0,0 +1,12 @@
+langcode: en
+status: true
+dependencies: {  }
+id: type_three
+label: 'Type Three'
+description: ''
+source: image
+queue_thumbnail_downloads: false
+new_revision: false
+source_configuration:
+  source_field: field_media_test_image
+field_map: {  }
diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
index 41979c8eed148d2a7ccaf1a6b201b88e23534263..78582f204fa173cb6288bac79682eef7ddd78b71 100644
--- a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
+++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml
@@ -4,6 +4,7 @@ description: 'Test module for Media library.'
 package: Testing
 core: 8.x
 dependencies:
+  - drupal:image
   - drupal:media_library
   - drupal:media_test_source
   - drupal:menu_ui
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
index ab6e8a55f8a14faed001fad6d146d0efbca8f326..b92a42e3d4015347f7d80dbc269dba4eb201c9a2 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\media\Entity\Media;
+use Drupal\Tests\TestFileCreationTrait;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 
@@ -14,6 +15,8 @@
  */
 class MediaLibraryTest extends WebDriverTestBase {
 
+  use TestFileCreationTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -286,4 +289,110 @@ public function testWidgetAnonymous() {
     $assert_session->pageTextContains('Dog');
   }
 
+  /**
+   * Tests that uploads in the Media library's widget works as expected.
+   */
+  public function testWidgetUpload() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    foreach ($this->getTestFiles('image') as $image) {
+      $extension = pathinfo($image->filename, PATHINFO_EXTENSION);
+      if ($extension === 'png') {
+        $png_image = $image;
+      }
+      elseif ($extension === 'jpg') {
+        $jpg_image = $image;
+      }
+    }
+
+    if (!isset($png_image) || !isset($jpg_image)) {
+      $this->fail('Expected test files not present.');
+    }
+
+    // Visit a node create page.
+    $this->drupalGet('node/add/basic_page');
+
+    $file_storage = $this->container->get('entity_type.manager')->getStorage('file');
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = $this->container->get('file_system');
+
+    // Add to the twin media field using the add button directly on the widget.
+    $unlimited_button = $assert_session->elementExists('css', '.media-library-add-button[href*="field_twin_media"]');
+    $unlimited_button->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $page->attachFileToField('Upload', $this->container->get('file_system')->realpath($png_image->uri));
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Files are temporary until the form is saved.
+    $files = $file_storage->loadMultiple();
+    $file = array_pop($files);
+    $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri()));
+    $this->assertTrue($file->isTemporary());
+
+    $this->assertSame($assert_session->fieldExists('Name')->getValue(), $png_image->filename);
+    $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('Alternative text field is required');
+    $page->fillField('Alternative text', $this->randomString());
+    $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // The file should be permanent now.
+    $files = $file_storage->loadMultiple();
+    $file = array_pop($files);
+    $this->assertFalse($file->isTemporary());
+
+    // Ensure the media item was added.
+    $assert_session->pageTextNotContains('Media library');
+    $assert_session->pageTextContains($png_image->filename);
+
+    // Open the browser again to test type resolution.
+    $unlimited_button = $assert_session->elementExists('css', '.media-library-open-button[href*="field_twin_media"]');
+    $unlimited_button->click();
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('Media library');
+    $assert_session->elementExists('css', '#drupal-modal')->clickLink('Add media');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $page->attachFileToField('Upload', $file_system->realpath($jpg_image->uri));
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->pageTextContains('Select a media type for ' . $jpg_image->filename);
+
+    // Before the type is determined, the file lives in the default upload
+    // location (temporary://).
+    $files = $file_storage->loadMultiple();
+    $file = array_pop($files);
+    $this->assertSame('temporary', $file_system->uriScheme($file->getFileUri()));
+
+    // Both the type_three and type_four media types accept jpg images.
+    $assert_session->buttonExists('Type Three');
+    $assert_session->buttonExists('Type Four')->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // The file should have been moved when the type was selected.
+    $files = $file_storage->loadMultiple();
+    $file = array_pop($files);
+    $this->assertSame('public://type-four-dir', $file_system->dirname($file->getFileUri()));
+    $this->assertSame($assert_session->fieldExists('Name')->getValue(), $jpg_image->filename);
+    $page->fillField('Alternative text', $this->randomString());
+
+    // The type_four media type has another optional image field.
+    $assert_session->pageTextContains('Extra Image');
+    $page->attachFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_image->uri));
+    $assert_session->assertWaitOnAjaxRequest();
+    // Ensure that the extra image was uploaded to the correct directory.
+    $files = $file_storage->loadMultiple();
+    $file = array_pop($files);
+    $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri()));
+
+    $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->pageTextNotContains('Media library');
+    $assert_session->pageTextContains($jpg_image->filename);
+  }
+
 }