diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
similarity index 60%
copy from core/modules/media/config/install/core.entity_view_mode.media.full.yml
copy to core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
index dfdbb3a..e082019 100644
--- a/core/modules/media/config/install/core.entity_view_mode.media.full.yml
+++ b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
@@ -1,9 +1,9 @@
 langcode: en
-status: false
+status: true
 dependencies:
   module:
     - media
-id: media.full
-label: 'Full content'
+id: media.add_inline
+label: 'File Upload'
 targetEntityType: media
 cache: true
diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_view_mode.media.full.yml
similarity index 100%
rename from core/modules/media/config/install/core.entity_view_mode.media.full.yml
rename to core/modules/media/config/optional/core.entity_view_mode.media.full.yml
diff --git a/core/modules/media/src/Element/MediaManagedFile.php b/core/modules/media/src/Element/MediaManagedFile.php
new file mode 100644
index 0000000..74a5e93
--- /dev/null
+++ b/core/modules/media/src/Element/MediaManagedFile.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\media\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\Element\ManagedFile;
+use Drupal\media\Entity\Media;
+
+/**
+ * Extends the managed_file element to include the associated Media information.
+ *
+ * @FormElement("media_managed_file")
+ *
+ * @internal
+ */
+class MediaManagedFile extends ManagedFile {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $element = parent::getInfo();
+
+    $class = get_class($this);
+    $element['#element_validate'] = [
+      [$class, 'validateManagedFile'],
+      [$class, 'validateEntityForm'],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (!empty($element['#value']['target_id'])) {
+      // We're editing an entity with a previously-saved Media entity
+      // reference(s). In order not to confuse ManagedFile, load those entities'
+      // source fids and store them where it expects them.
+      $media_entity = Media::load($element['#value']['target_id']);
+      $media_source_field = $media_entity->getSource()->getConfiguration()['source_field'];
+      $file_value = $media_entity->get($media_source_field)->getValue();
+      foreach ($file_value as $value) {
+        $element['#value']['fids'][] = $value['target_id'];
+      }
+    }
+    $element = parent::processManagedFile($element, $form_state, $complete_form);
+
+    // We don't want to show the file-upload field for values that already have
+    // a file.
+    if (!empty($element['#files'])) {
+      $element['upload']['#access'] = FALSE;
+    }
+
+    $mids = isset($element['#value']['mids']) ? $element['#value']['mids'] : [];
+    $element['mids'] = [
+      '#type' => 'hidden',
+      '#value' => $mids,
+    ];
+
+    return $element;
+  }
+
+  /**
+   * Validates the media form using our custom form handler.
+   *
+   * @param array $entity_form
+   *   The entity form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public static function validateEntityForm(array &$entity_form, FormStateInterface $form_state) {
+    \Drupal::entityTypeManager()->getHandler('media', 'add_inline')
+      ->entityFormValidate($entity_form, $form_state);
+  }
+
+}
diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php
index dc8e0dc..a94f4a6 100644
--- a/core/modules/media/src/Entity/Media.php
+++ b/core/modules/media/src/Entity/Media.php
@@ -39,6 +39,7 @@
  *       "edit" = "Drupal\media\MediaForm",
  *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  *     },
+ *     "add_inline" = "Drupal\media\Form\MediaInlineForm",
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\media\MediaViewsData",
  *     "route_provider" = {
diff --git a/core/modules/media/src/Form/MediaInlineForm.php b/core/modules/media/src/Form/MediaInlineForm.php
new file mode 100644
index 0000000..18ff096
--- /dev/null
+++ b/core/modules/media/src/Form/MediaInlineForm.php
@@ -0,0 +1,238 @@
+<?php
+
+namespace Drupal\media\Form;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityHandlerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Media entity inline form handler.
+ *
+ * @internal
+ */
+class MediaInlineForm implements EntityHandlerInterface {
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity type managed by this handler.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * Module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs the inline entity form controller.
+   *
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   */
+  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityTypeInterface $entity_type) {
+    $this->entityFieldManager = $entity_field_manager;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->moduleHandler = $module_handler;
+    $this->entityType = $entity_type;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $container->get('entity_field.manager'),
+      $container->get('entity_type.manager'),
+      $container->get('module_handler'),
+      $entity_type
+    );
+  }
+
+  /**
+   * Builds the entity form.
+   *
+   * @param array $entity_form
+   *   The entity form, containing the following basic properties:
+   *   - #entity: The entity for the current entity form.
+   *   - #form_mode: The form mode used to display the entity form.
+   *   - #parents: Identifies the position of the entity form in the overall
+   *     parent form, and identifies the location where the field values are
+   *     placed within $form_state->getValues().
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   *
+   * @return array
+   *   The entity form array.
+   */
+  public function entityForm(array $entity_form, FormStateInterface $form_state) {
+    // Build the media entity form.
+    /** @var \Drupal\media\MediaInterface $entity */
+    $entity = $entity_form['#entity'];
+    $form_display = EntityFormDisplay::collectRenderDisplay($entity, $entity_form['#form_mode']);
+    $form_display->buildForm($entity, $entity_form, $form_state);
+    $entity_form['#weight'] = 100;
+
+    // In the service of keeping this form as user-friendly as possible in the
+    // context of a parent entity form, only show required fields.
+    /** @var \Drupal\Core\Field\BaseFieldDefinition $field_definition */
+    foreach ($entity->getFieldDefinitions() as $field_definition) {
+      $field_name = $field_definition->getName();
+      if (!$field_definition->isRequired()) {
+        $entity_form[$field_name]['#access'] = FALSE;
+      }
+    }
+    // The media name will be automatically populated by the source plugin, we
+    // are OK with the default name in this case.
+    $entity_form['name']['#access'] = FALSE;
+
+    // We've already set the just-uploaded file as the value of the source
+    // field, so hide that too.
+    // @see \Drupal\media\Plugin\Field\FieldWidget\MediaFileWidget::value().
+    $source_field = $entity->getSource()
+      ->getSourceFieldDefinition($entity->bundle->entity)
+      ->getName();
+    $entity_form[$source_field]['#access'] = FALSE;
+
+    // Inline entities inherit the parent language, so hide translation-related
+    // fields as well.
+    $langcode_key = $this->entityType->getKey('langcode');
+    if ($langcode_key && isset($entity_form[$langcode_key])) {
+      $entity_form[$langcode_key]['#access'] = FALSE;
+    }
+    if (!empty($entity_form['#translating'])) {
+      // Hide the non-translatable fields.
+      foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) {
+        if (isset($entity_form[$field_name]) && $field_name != $langcode_key) {
+          $entity_form[$field_name]['#access'] = $field_definition->isTranslatable();
+        }
+      }
+    }
+
+    return $entity_form;
+  }
+
+  /**
+   * Validates the entity form.
+   *
+   * @param array $entity_form
+   *   The entity form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   */
+  public function entityFormValidate(array &$entity_form, FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+    // Perform entity validation only if the inline form was submitted,
+    // skipping other requests such as file uploads.
+    if (!$this->hostFormSubmitted($form_state)) {
+      return;
+    }
+    $mids = [];
+    $form_mids = $form_state->getValue($entity_form['#field_name']);
+    if (!empty($form_mids)) {
+      foreach ($form_mids as $form_mid) {
+        if (!empty($form_mid['mids'][0])) {
+          $mids[] = $form_mid['mids'][0];
+        }
+      }
+    }
+    foreach ($mids as $mid) {
+      $media_form_key = 'media_' . $mid;
+      if (empty($entity_form[$media_form_key]['media_form']['form'])) {
+        continue;
+      }
+      $media_form = $entity_form[$media_form_key]['media_form']['form'];
+      /** @var \Drupal\media\MediaInterface $entity */
+      $entity = $media_form['#entity'];
+      if ($entity instanceof ContentEntityInterface) {
+        $this->buildEntity($media_form, $entity, $form_state);
+        $form_display = EntityFormDisplay::collectRenderDisplay($entity, $media_form['#form_mode']);
+        $form_display->validateFormValues($entity, $media_form, $form_state);
+        $entity->setValidationRequired(FALSE);
+
+        foreach ($form_state->getErrors() as $name => $message) {
+          // $name may be unknown in $form_state and
+          // $form_state->setErrorByName($name, $message) may suppress the error
+          // message.
+          $form_state->setError($triggering_element, $message);
+        }
+
+        // If no validation errors are present, set the media back to Published.
+        if (empty($form_state->getErrors())) {
+          $entity->setPublished()->save();
+        }
+      }
+    }
+  }
+
+  /**
+   * Builds an updated entity object based upon the submitted form values.
+   *
+   * @param array $entity_form
+   *   The entity form.
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function buildEntity(array $entity_form, ContentEntityInterface $entity, FormStateInterface $form_state) {
+    $form_display = EntityFormDisplay::collectRenderDisplay($entity, $entity_form['#form_mode']);
+    $form_display->extractFormValues($entity, $entity_form, $form_state);
+    // Invoke all specified builders for copying form values to entity fields.
+    if (isset($entity_form['#entity_builders'])) {
+      foreach ($entity_form['#entity_builders'] as $function) {
+        call_user_func_array($function, [$entity->getEntityTypeId(), $entity, &$entity_form, $form_state]);
+      }
+    }
+  }
+
+  /**
+   * Check if the form processing is due to a host submit.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The $form_state object.
+   *
+   * @return bool
+   *   TRUE if the form is being processed due to the host entity's form being
+   *   submitted (and not other AJAX-based submissions, such as uploads, etc.).
+   */
+  protected function hostFormSubmitted(FormStateInterface $form_state) {
+    $triggering_element = $form_state->getTriggeringElement();
+    // @TODO Figure out a more robust way of doing this.
+    if (strpos($triggering_element['#name'], '_button') === FALSE) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php
new file mode 100644
index 0000000..2d4b074
--- /dev/null
+++ b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php
@@ -0,0 +1,420 @@
+<?php
+
+namespace Drupal\media\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\file\Entity\File;
+use Drupal\file\Plugin\Field\FieldWidget;
+use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
+use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\file\Plugin\Field\FieldType\FileItem;
+use Drupal\media\Entity\Media;
+use Drupal\media\Entity\MediaType;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Link;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'media_file' widget.
+ *
+ * @FieldWidget(
+ *   id = "media_file",
+ *   label = @Translation("File"),
+ *   field_types = {
+ *     "entity_reference"
+ *   }
+ * )
+ */
+class MediaFileWidget extends FileWidget {
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info, RendererInterface $renderer) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $element_info);
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['third_party_settings'],
+      $container->get('element_info'),
+      $container->get('renderer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Uses nearly the same code that FileWidget does here, except that we have to
+   * dig down through the target Media entity to *its* file field's upload
+   * location and upload validators.
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $field_settings = $this->getFieldSettings();
+
+    // The field settings include defaults for the field type. However, this
+    // widget is a base class for other widgets (e.g., ImageWidget) that may act
+    // on field types without these expected settings.
+    $field_settings += [
+      'display_default' => NULL,
+      'display_field' => NULL,
+      'description_field' => NULL,
+    ];
+
+    $defaults = [
+      'mids' => [],
+      'fids' => [],
+      'display' => (bool) $field_settings['display_default'],
+      'description' => '',
+    ];
+
+    $target_bundle = array_shift($this->fieldDefinition->getSetting('handler_settings')['target_bundles']);
+    /** @var \Drupal\media\Entity\MediaType $media_type */
+    $media_type = MediaType::load($target_bundle);
+    $source = $media_type->getSource();
+    $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($media_type));
+    $file_item = new FileItem($source_data_definition);
+
+    $element_info = $this->elementInfo->getInfo('media_managed_file');
+    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
+
+    $element += [
+      '#type' => 'media_managed_file',
+      '#upload_location' => $file_item->getUploadLocation(),
+      '#upload_validators' => $file_item->getUploadValidators(),
+      '#value_callback' => [get_class($this), 'value'],
+      '#process' => array_merge($element_info['#process'], [[get_class($this), 'process']]),
+      '#progress_indicator' => $this->getSetting('progress_indicator'),
+      // Allows this field to return an array instead of a single value.
+      '#extended' => TRUE,
+      // Add properties needed by value() and process() methods.
+      '#field_name' => $this->fieldDefinition->getName(),
+      // This is actually talking about the entity type ON WHICH this field is
+      // placed, not the entity type TO WHICH it can refer. In fact, it seems
+      // only to be used, unnecessarily, on FileWidget::validateMultipleCount().
+      '#entity_type' => $items->getEntity()->getEntityTypeId(),
+      '#display_field' => (bool) $field_settings['display_field'],
+      '#display_default' => $field_settings['display_default'],
+      '#description_field' => $field_settings['description_field'],
+      '#cardinality' => $cardinality,
+    ];
+
+    $element['#weight'] = $delta;
+
+    // Save mid, the target_id value that this field ultimately cares about.
+    if (!isset($items[$delta]->mids) && isset($items[$delta]->target_id)) {
+      $items[$delta]->mids = [$items[$delta]->target_id];
+    }
+    $element['#default_value'] = $items[$delta]->getValue() + $defaults;
+
+    $default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value'];
+    if (empty($default_fids)) {
+      $file_upload_help = [
+        '#theme' => 'file_upload_help',
+        '#description' => $element['#description'],
+        '#upload_validators' => $element['#upload_validators'],
+        '#cardinality' => $cardinality,
+      ];
+      $element['#description'] = $this->renderer->renderPlain($file_upload_help);
+      $element['#multiple'] = $cardinality != 1 ? TRUE : FALSE;
+      if ($cardinality != 1 && $cardinality != -1) {
+        $element['#element_validate'][] = [get_class($this), 'validateMultipleCount'];
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    // Override FileWidget::massageFormValues() in order to reference Media
+    // entities instead of files.
+    $new_values = [];
+    foreach ($values as &$value) {
+      $value['mids'] = !empty($value['mids']) ? $value['mids'] : [];
+      foreach ($value['mids'] as $mid) {
+        $new_value = $value;
+        $new_value['target_id'] = $mid;
+        unset($new_value['mids']);
+        $new_values[] = $new_value;
+      }
+    }
+
+    return $new_values;
+  }
+
+  /**
+   * Retrieves the value for the element.
+   */
+  public static function value($element, $input, FormStateInterface $form_state) {
+    $return = parent::value($element, $input, $form_state);
+
+    $media_type = FALSE;
+    $host_entity = $form_state->getFormObject()->getEntity();
+    if ($host_entity && $host_entity instanceof ContentEntityInterface) {
+      $handler_settings = $host_entity->getFieldDefinitions()[$element['#field_name']]->getSetting('handler_settings');
+      // If this field happens to point to multiple media types (bundles), just
+      // use the first one that implements a file source.
+      $media_type = !empty($handler_settings['target_bundles']) ? self::getFileMediaTypeFromSet($handler_settings['target_bundles']) : FALSE;
+    }
+
+    // When the form has just been submitted with file(s) being uploaded,
+    // $input['mids'] should be empty. FileWidget::value() calls
+    // ManagedFile::valueCallback(), which populates $return['fids']. If there's
+    // info in there AND we're not doing the subsequent form rebuild, it's time
+    // to make the media.
+    if (empty($input['mids']) && !empty($return['fids']) && !$form_state->isRebuilding() && $media_type) {
+      foreach ($return['fids'] as $fid) {
+        /** @var \Drupal\media\MediaInterface $media_entity */
+        $media_entity = Media::create([
+          'bundle' => $media_type,
+          'uid' => \Drupal::currentUser()->id(),
+          'langcode' => $host_entity->language()->getId(),
+        ]);
+        // We create media items as unpublished to prevent "abandoned" uploads
+        // from producing visible media items on the site. In
+        // \Drupal\media\Form\MediaInlineForm::entityFormValidate() we make sure
+        // we set them back to published when the main form is submitted.
+        $media_entity->setUnpublished();
+        $source_field = $media_entity->getSource()
+          ->getSourceFieldDefinition($media_entity->bundle->entity)
+          ->getName();
+        $media_entity->set($source_field, $fid);
+        $media_entity->save();
+        $return['mids'][] = $media_entity->id();
+      }
+    }
+    elseif (!empty($input['mids'])) {
+      $mid = $input['mids'];
+      $media_fields_key = 'media_' . $mid;
+
+      if (!empty($input[$media_fields_key])) {
+        $media_entity = Media::load($mid);
+        foreach ($input[$media_fields_key] as $field_name => $field_value) {
+          $media_entity->set($field_name, $field_value);
+        }
+        $media_entity->save();
+      }
+      else {
+        // Warn process() that this is a preexisting value.
+        $return['media_already_saved'] = TRUE;
+      }
+      $return['mids'] = [$mid];
+    }
+
+    return $return;
+  }
+
+  /**
+   * Expands the file element to include the inline version of the media form.
+   */
+  public static function process($element, FormStateInterface $form_state, $form) {
+    $element = parent::process($element, $form_state, $form);
+    $mid = FALSE;
+    $show_form = FALSE;
+
+    if (!empty($element['#value']['target_id'])) {
+      // This is an existing entity ref and we're loading the edit page the
+      // first time. Just show the icon and name.
+      $mid = $element['#value']['target_id'];
+    }
+    elseif (!empty($element['#value']['mids']) && count($element['#value']['mids']) === 1) {
+      // If $element['mids']['#value'] has multiple values, that means this is
+      // the first pass after someone uploaded several files; ignore. If it has
+      // only ONE, then this media entity was just created and we want to force
+      // the user to fill out all the new entity's required fields. I.e., show
+      // the form, as well as the standard icon and name.
+      $mid = $element['#value']['mids'][0];
+
+      if (empty($element['#value']['media_already_saved'])) {
+        $show_form = TRUE;
+      }
+    }
+
+    if ($mid) {
+      // First, get rid of the file link that FileManaged has created, and
+      // replace with a media-entity link.
+      $fid = $element['#value']['fids'][0];
+      unset($element['file_' . $fid]);
+      // Under some circumstances this button said "remove selected," but since
+      // we just removed the checkbox this button should instead always just say
+      // "remove."
+      $element['remove_button']['#value'] = t('Remove');
+
+      // Now show the media icon and name.
+      $form_element_name = 'media_' . $mid;
+      $form_element_parents = array_merge($element['#parents'], [$form_element_name]);
+      $media_entity = Media::load($mid);
+
+      $element[$form_element_name]['media_icon'] = [
+        '#theme' => 'image_style',
+        '#style_name' => 'thumbnail',
+        '#uri' => $media_entity->getSource()->getMetadata($media_entity, 'thumbnail_uri'),
+        '#weight' => -20,
+      ];
+      $link_build = Link::createFromRoute($media_entity->getName(), 'entity.media.canonical', ['media' => $mid])->toRenderable();
+      $element[$form_element_name]['media_name'] = $link_build + ['#weight' => -10];
+
+      if ($show_form) {
+        // Create and add the media entity form.
+        $form_mode = 'add_inline';
+        $media_form = [
+          '#entity' => $media_entity,
+          '#form_mode' => $form_mode,
+          '#parents' => $form_element_parents,
+        ];
+        $inline_form_handler = \Drupal::entityTypeManager()
+          ->getHandler('media', $form_mode);
+        $media_form = $inline_form_handler->entityForm($media_form, $form_state);
+        $element[$form_element_name]['media_form'] = [
+          '#type' => 'container',
+          'form' => $media_form,
+        ];
+
+        // Prevent the media form elements that were added from failing
+        // required field validation when the form is first processed.
+        // @TODO Finish this.
+//        $element['upload_button']['#limit_validation_errors'] = [];
+//        $element['remove_button']['#limit_validation_errors'] = [];
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Form submission handler for upload/remove button of formElement().
+   *
+   * Overrides FileWidget::submit(), performing the exact same functionality,
+   * only maintaining awareness of mids as well as fids.
+   *
+   * @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
+   * @see file_managed_file_submit()
+   */
+  public static function submit($form, FormStateInterface $form_state) {
+    // During the form rebuild, formElement() will create field item widget
+    // elements using re-indexed deltas, so clear out FormState::$input to
+    // avoid a mismatch between old and new deltas. The rebuilt elements will
+    // have #default_value set appropriately for the current state of the field,
+    // so nothing is lost in doing this.
+    $button = $form_state->getTriggeringElement();
+    $parents = array_slice($button['#parents'], 0, -2);
+    NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
+
+    // Go one level up in the form, to the widgets container.
+    $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
+    $field_name = $element['#field_name'];
+    $parents = $element['#field_parents'];
+
+    $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
+    foreach ($submitted_values as $delta => $submitted_value) {
+      if (empty($submitted_value['fids'])) {
+        unset($submitted_values[$delta]);
+      }
+    }
+
+    // If there are more files uploaded via the same widget, we have to separate
+    // them, as we display each file in its own widget.
+    $new_values = [];
+    foreach ($submitted_values as $submitted_value) {
+      if (is_array($submitted_value['fids']) && is_array($submitted_value['mids'])) {
+        foreach ($submitted_value['fids'] as $delta => $fid) {
+          $new_value = $submitted_value;
+          $new_value['fids'] = [$fid];
+          $new_value['mids'] = [$submitted_value['mids'][$delta]];
+          $new_values[] = $new_value;
+        }
+      }
+    }
+
+    // Re-index deltas after removing empty items.
+    $submitted_values = array_values($new_values);
+
+    // Update form_state values.
+    NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
+
+    // Update items.
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+    $field_state['items'] = $submitted_values;
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    // This widget is restricted to entity_reference fields pointing to media
+    // entities.
+    if ($field_definition->getSetting('target_type') !== 'media') {
+      return FALSE;
+    }
+
+    // This widget can be chosen only if the field points to a single target
+    // Media Type, and this type has a source of type "File".
+    $handler_settings = $field_definition->getSetting('handler_settings');
+    if (empty($handler_settings['target_bundles']) || count($handler_settings['target_bundles']) !== 1) {
+      return FALSE;
+    }
+    $type_name = reset($handler_settings['target_bundles']);
+    $media_type = MediaType::load($type_name);
+    if ($media_type) {
+      $source = $media_type->getSource();
+      if (is_a($source, '\Drupal\media\Plugin\media\Source\File')) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Given a set of media types, fetch the first one with a File source.
+   *
+   * @param array $types
+   *   An array where the values are media type IDs.
+   *
+   * @return bool|string
+   *   The ID of the first type found (from the set passed in) with a source
+   *   plugin that implements
+   *   \Drupal\media\Plugin\media\Source\File, or FALSE if none found.
+   */
+  public static function getFileMediaTypeFromSet(array $types) {
+    $media_types = MediaType::loadMultiple($types);
+    if (!$media_types) {
+      return FALSE;
+    }
+
+    foreach ($media_types as $media_type) {
+      /** @var \Drupal\media\MediaTypeInterface $media_type */
+      $source = $media_type->getSource();
+      if (is_a($source, '\Drupal\media\Plugin\media\Source\File')) {
+        return $media_type->id();
+      }
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/media/tests/fixtures/example_1.pdf b/core/modules/media/tests/fixtures/example_1.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/media/tests/fixtures/example_2.pdf b/core/modules/media/tests/fixtures/example_2.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
new file mode 100644
index 0000000..b109fc7
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
@@ -0,0 +1,276 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * @group media
+ */
+class MediaInlineFileWidgetTest extends MediaJavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node'];
+
+  protected $strictConfigSchema = FALSE;
+
+  /**
+   * The bundle name of the test node.
+   *
+   * @var string
+   */
+  protected $nodeTypeId;
+
+  /**
+   * The bundle name of the test media.
+   *
+   * @var string
+   */
+  protected $mediaTypeId;
+
+  /**
+   * An indexed array of valid paths for test files.
+   *
+   * @var array
+   */
+  protected $testFilePaths;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $media_type = $this->createMediaType(['bundle' => 'file'], 'file');
+    $this->mediaTypeId = $media_type->id();
+    // Create two test fields on this media type, one of them required.
+    $media_required_field_storage = FieldStorageConfig::create([
+      'type' => 'string',
+      'entity_type' => 'media',
+      'field_name' => 'field_media_required',
+      'settings' => [
+        'max_length' => 255,
+      ],
+    ]);
+    $media_required_field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $media_required_field_storage,
+      'bundle' => $this->mediaTypeId,
+      'required' => TRUE,
+    ])->save();
+    entity_get_form_display('media', $media_type->id(), 'add_inline')
+      ->setComponent('field_media_required', ['type' => 'string_textfield'])
+      ->save();
+    $media_nonrequired_field_storage = FieldStorageConfig::create([
+      'type' => 'string',
+      'entity_type' => 'media',
+      'field_name' => 'field_media_nonrequired',
+      'settings' => [
+        'max_length' => 255,
+      ],
+    ]);
+    $media_nonrequired_field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $media_nonrequired_field_storage,
+      'bundle' => $this->mediaTypeId,
+      'required' => FALSE,
+    ])->save();
+    // Deliberately add it to the form, to test that even then we don't show
+    // this field when embedding the form inside the widget.
+    entity_get_form_display('media', $this->mediaTypeId, 'add_inline')
+      ->setComponent('field_media_nonrequired', ['type' => 'string_textfield'])
+      ->save();
+    entity_get_display('media', $this->mediaTypeId, 'default')
+      ->setComponent('field_media_file', [
+        'type' => 'file_default',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->setComponent('field_media_required', [
+        'type' => 'string',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->setComponent('field_media_nonrequired', [
+        'type' => 'string',
+        'label' => 'hidden',
+        'settings' => [],
+      ])
+      ->save();
+
+    $node_type = $this->drupalCreateContentType();
+    $this->nodeTypeId = $node_type->id();
+    $field_storage = FieldStorageConfig::create([
+      'type' => 'entity_reference',
+      'cardinality' => -1,
+      'entity_type' => 'node',
+      'field_name' => 'field_media_reference',
+      'settings' => [
+        'target_type' => 'media',
+      ],
+    ]);
+    $field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => $this->nodeTypeId,
+      'settings' => [
+        'handler_settings' => [
+          'target_bundles' => [
+            $this->mediaTypeId => $this->mediaTypeId,
+          ],
+        ],
+      ],
+    ])->save();
+    entity_get_form_display('node', $this->nodeTypeId, 'default')
+      ->setComponent('field_media_reference', ['type' => 'media_file'])
+      ->save();
+    entity_get_display('node', $this->nodeTypeId, 'default')
+      ->setComponent('field_media_reference', [
+        'type' => 'entity_reference_entity_view',
+        'label' => 'hidden',
+        'settings' => [
+          'view_mode' => 'full',
+        ],
+      ])->save();
+
+    // Create a couple of files for testing.
+    for ($i = 0; $i < 2; $i++) {
+      $test_filename = $this->randomMachineName() . '.txt';
+      $test_filepath = 'public://' . $test_filename;
+      file_put_contents($test_filepath, $this->randomMachineName());
+      $this->testFilePaths[$i] = \Drupal::service('file_system')->realpath($test_filepath);
+    }
+
+  }
+
+  /**
+   * Tests the file widget behavior.
+   */
+  public function testInlineFileWidget() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('/node/add/' . $this->nodeTypeId);
+
+    // Ensure the media field is present, but no required fields exist in there.
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    $assert_session->elementNotExists('css', '.required', $media_field);
+
+    // Upload a file and see that new required fields appeared.
+    $first_upload_element_id = 'edit-field-media-reference-0-upload';
+    $page->attachFileToField($first_upload_element_id, $this->testFilePaths[0]);
+    $result = $assert_session->waitForButton('Remove');
+    $this->assertNotEmpty($result);
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    // The name field should be present and required.
+    $assert_session->elementExists('css', '.field--name-name input.required', $media_field);
+    // The additional field sould be present and required.
+    $assert_session->elementExists('css', '.field--name-field-media-required input.required', $media_field);
+    // The non-required field should not be there.
+    $assert_session->elementNotExists('css', '.field--name-field-media-nonrequired input', $media_field);
+
+    // By now the media entity should already exist, but unpublished.
+    $filename1 = basename($this->testFilePaths[0]);
+    $media = $this->container->get('entity_type.manager')->getStorage('media')
+      ->loadByProperties(['name' => $filename1]);
+    $media = reset($media);
+    /** @var \Drupal\media\MediaInterface $media */
+    $this->assertNotNull($media);
+    $this->assertFalse($media->isPublished());
+
+    // Save the node and check page elements correspond to what is expected.
+    $node_title = 'Host Node 1';
+    $page->fillField('Title', $node_title);
+    $required_text = $this->randomMachineName();
+    $page->fillField('field_media_required', $required_text);
+    $page->pressButton('Save');
+    // The node has been correctly saved.
+    $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been created");
+    // We have a reference media container.
+    $media_field = $assert_session->elementExists('css', '.field--name-field-media-reference');
+    // We have a file link inside that container.
+    $assert_session->elementExists('css', '.field--name-field-media-file a', $media_field);
+    // The required field is present.
+    $required_element = $assert_session->elementExists('css', '.field--name-field-media-required', $media_field);
+    // The required field text is what we expect.
+    $this->assertEquals($required_text, $required_element->getText());
+
+    // The media item should now be published.
+    $media = $this->container->get('entity_type.manager')->getStorage('media')
+      ->loadUnchanged($media->id());
+    /** @var \Drupal\media\MediaInterface $media */
+    $this->assertTrue($media->isPublished());
+
+    // Edit the node, add a second file, check the widget supports multiple
+    // values and creates a second media without messing up.
+    $node = $this->container->get('entity_type.manager')->getStorage('node')
+      ->loadByProperties(['title' => $node_title]);
+    $node = reset($node);
+    $this->drupalGet("/node/{$node->id()}/edit");
+    // First file is still there, and remove button is present.
+    $assert_session->elementContains('css', '#edit-field-media-reference-table', $filename1);
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    $remove_button = $media_field->findButton('Remove');
+    $this->assertNotNull($remove_button);
+    $second_upload_element_id = 'edit-field-media-reference-1-upload';
+    // Upload the second file.
+    $filename2 = basename($this->testFilePaths[1]);
+    $page->attachFileToField($second_upload_element_id, $this->testFilePaths[1]);
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Fill in the second media required field and save.
+    $required_text2 = $this->randomMachineName();
+    $page->fillField('field_media_required', $required_text2);
+    $page->pressButton('Save');
+
+    // Check everything was saved correctly.
+    $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been updated");
+    $assert_session->pageTextContains($filename1);
+    $assert_session->pageTextContains($filename2);
+    $assert_session->pageTextContains($required_text);
+    $assert_session->pageTextContains($required_text2);
+
+    // Both media entities should exist now.
+    foreach ([$filename1, $filename2] as $filename) {
+      $media = $this->container->get('entity_type.manager')->getStorage('media')
+        ->loadByProperties(['name' => $filename]);
+      $media = reset($media);
+      /** @var \Drupal\media\MediaInterface $media */
+      $this->assertTrue($media->isPublished());
+    }
+
+    // Edit the node, verify we can successfuly remove one of the items.
+    $this->drupalGet("/node/{$node->id()}/edit");
+    $assert_session->pageTextContains($filename1);
+    $assert_session->pageTextContains($filename2);
+    $assert_session->buttonExists('edit-field-media-reference-0-remove-button');
+    $page->pressButton('edit-field-media-reference-0-remove-button');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextNotContains($filename1);
+    $assert_session->pageTextContains($filename2);
+    $page->pressButton('Save');
+    $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been updated");
+    $assert_session->pageTextNotContains($filename1);
+    $assert_session->pageTextContains($filename2);
+    $assert_session->pageTextNotContains($required_text);
+    $assert_session->pageTextContains($required_text2);
+
+    // Removing the file without saving the node should also be possible.
+    $this->drupalGet('/node/add/' . $this->nodeTypeId);
+    $first_upload_element_id = 'edit-field-media-reference-0-upload';
+    $page->attachFileToField($first_upload_element_id, $this->testFilePaths[0]);
+    $result = $assert_session->waitForButton('Remove');
+    $this->assertNotEmpty($result);
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    // The additional field sould be present and required.
+    $assert_session->elementExists('css', '.field--name-field-media-required input.required', $media_field);
+    $page->pressButton('Remove');
+    $assert_session->assertWaitOnAjaxRequest();
+    $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]');
+    // The non-required field should have disappeared.
+    $assert_session->elementNotExists('css', '.field--name-field-media-required input.required', $media_field);
+  }
+
+}
