diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml
index 4783f6d..50a285b 100644
--- a/core/modules/media/config/schema/media.schema.yml
+++ b/core/modules/media/config/schema/media.schema.yml
@@ -36,6 +36,10 @@ media.type.*:
       sequence:
         type: string
 
+media.source.file:
+  type: media.source.field_aware
+  label: 'File handler configuration'
+
 action.configuration.media_delete_action:
   type: action_configuration_default
   label: 'Delete media configuration'
diff --git a/core/modules/media/src/Plugin/media/Source/File.php b/core/modules/media/src/Plugin/media/Source/File.php
new file mode 100644
index 0000000..a85b67a
--- /dev/null
+++ b/core/modules/media/src/Plugin/media/Source/File.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\media\Plugin\media\Source;
+
+use Drupal\file\FileInterface;
+use Drupal\media\MediaInterface;
+use Drupal\media\MediaSourceBase;
+
+/**
+ * File entity media source.
+ *
+ * @see \Drupal\file\FileInterface
+ *
+ * @MediaSource(
+ *   id = "file",
+ *   label = @Translation("File"),
+ *   description = @Translation("Provides business logic and metadata for local files and documents."),
+ *   allowed_field_types = {"file"},
+ *   default_thumbnail_filename = "generic.png"
+ * )
+ */
+class File extends MediaSourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadataAttributes() {
+    return [
+      'mime' => $this->t('MIME type'),
+      'size' => $this->t('Size'),
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMetadata(MediaInterface $media, $name) {
+    /** @var \Drupal\file\FileInterface $file */
+    $file = $media->get($this->configuration['source_field'])->entity;
+    if (!$file) {
+      return parent::getMetadata($media, $name);
+    }
+    switch ($name) {
+
+      case 'default_name':
+        return $file->getFilename();
+
+      case 'thumbnail_uri':
+        return $this->getThumbnail($file);
+
+      case 'mime':
+        return $file->getMimeType() ?: FALSE;
+
+      case 'size':
+        $size = $file->getSize();
+        return is_numeric($size) ? $size : FALSE;
+
+      default:
+        return parent::getMetadata($media, $name);
+
+    }
+  }
+
+  /**
+   * Gets the thumbnail image URI based on a file entity.
+   *
+   * @param \Drupal\file\FileInterface $file
+   *   A file entity.
+   *
+   * @return string
+   *   File URI of the thumbnail image.
+   */
+  protected function getThumbnail(FileInterface $file) {
+    $icon_base = $this->configFactory->get('media.settings')->get('icon_base');
+
+    // We try to automatically use the most specific icon present in the
+    // $icon_base directory, based on the MIME type. For instance, if an
+    // icon file named "pdf.png" is present, it will be used if the file
+    // matches this MIME type.
+    $mimetype = $file->getMimeType();
+    $mimetype = explode('/', $mimetype);
+
+    $icon_names = array(
+      $mimetype[0] . '--' . $mimetype[1],
+      $mimetype[1],
+      $mimetype[0],
+    );
+    foreach ($icon_names as $icon_name) {
+      $thumbnail = $icon_base . '/' . $icon_name . '.png';
+      if (is_file($thumbnail)) {
+        return $thumbnail;
+      }
+    }
+
+    return $icon_base . '/generic.png';
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceFileTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceFileTest.php
new file mode 100644
index 0000000..793add2
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceFileTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\media\Entity\Media;
+
+/**
+ * Tests the file media source.
+ *
+ * @group media
+ */
+class MediaSourceFileTest extends MediaSourceTestBase {
+
+  /**
+   * Tests the file media source.
+   */
+  public function testMediaFileSource() {
+    $type_id = 'test_media_file_type';
+    $source_field_id = 'field_media_file';
+    $provided_fields = [
+      'mime',
+      'size',
+    ];
+
+    $session = $this->getSession();
+    $page = $session->getPage();
+    $assert_session = $this->assertSession();
+
+    // Create file media source.
+    $this->createMediaTypeTest($type_id, 'file', $provided_fields);
+
+    // Create a custom field for the media type to store the mime metadata
+    // attribute.
+    $fields = [
+      'field_string_mime' => 'string',
+      'field_string_size' => 'string',
+    ];
+    $this->createMediaTypeFields($fields, $type_id);
+
+    // Hide the name field widget to test default name generation.
+    $this->hideMediaTypeFieldWidget('name', $type_id);
+
+    $this->drupalGet("admin/structure/media/manage/{$type_id}");
+    $page->selectFieldOption("field_map[mime]", 'field_string_mime');
+    $page->selectFieldOption("field_map[size]", 'field_string_size');
+    $page->pressButton('Save');
+
+    // Create a media item.
+    $this->drupalGet("media/add/{$type_id}");
+    $page->attachFileToField("files[{$source_field_id}_0]", \Drupal::root() . '/sites/README.txt');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->pressButton('Save and publish');
+
+    $assert_session->addressEquals('media/1');
+
+    // Make sure the thumbnail is displayed.
+    $assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'generic.png');
+
+    // Load the media and check that all fields are properly populated.
+    $media = Media::load(1);
+    $this->assertEquals('README.txt', $media->label());
+    $this->assertEquals('text/plain', $media->get('field_string_mime')->value);
+    $this->assertEquals(filesize(\Drupal::root() . '/sites/README.txt'), $media->get('field_string_size')->value);
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php
new file mode 100644
index 0000000..6bf090d4
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Base class for media source tests.
+ */
+abstract class MediaSourceTestBase extends MediaJavascriptTestBase {
+
+  /**
+   * Creates storage and field instance, attached to a given media type.
+   *
+   * @param string $field_name
+   *   The field name.
+   * @param string $field_type
+   *   The field storage type.
+   * @param string $type_id
+   *   The media type config entity ID.
+   */
+  protected function createMediaTypeField($field_name, $field_type, $type_id) {
+    $storage = FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'media',
+      'type' => $field_type,
+    ]);
+    $storage->save();
+
+    FieldConfig::create([
+      'field_storage' => $storage,
+      'bundle' => $type_id,
+    ])->save();
+
+    // Make the field widget visible in the form display.
+    $component = \Drupal::service('plugin.manager.field.widget')
+      ->prepareConfiguration($field_type, []);
+
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
+    $entity_form_display = \Drupal::entityTypeManager()
+      ->getStorage('entity_form_display')
+      ->load('media.' . $type_id . '.default');
+    if (!$entity_form_display) {
+      $entity_form_display = EntityFormDisplay::create(array(
+        'targetEntityType' => 'media',
+        'bundle' => $type_id,
+        'mode' => 'default',
+        'status' => TRUE,
+      ));
+    }
+    $entity_form_display->setComponent($field_name, $component)
+      ->save();
+  }
+
+  /**
+   * Create a set of fields in a media type.
+   *
+   * @param array $fields
+   *   An associative array where keys are field names and values field types.
+   * @param string $type_id
+   *   The media type config entity ID.
+   */
+  protected function createMediaTypeFields(array $fields, $type_id) {
+    foreach ($fields as $field_name => $field_type) {
+      $this->createMediaTypeField($field_name, $field_type, $type_id);
+    }
+  }
+
+  /**
+   * Hides a widget in the default form display config.
+   *
+   * @param string $field_name
+   *   The field name.
+   * @param string $type_id
+   *   The media type config entity ID.
+   */
+  protected function hideMediaTypeFieldWidget($field_name, $type_id) {
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
+    $entity_form_display = \Drupal::entityTypeManager()
+      ->getStorage('entity_form_display')
+      ->load('media.' . $type_id . '.default');
+    $entity_form_display->removeComponent($field_name)->save();
+  }
+
+  /**
+   * Test generic media type creation.
+   *
+   * @param string $type_id
+   *   The media type config entity ID.
+   * @param string $source_id
+   *   The media source ID.
+   * @param array $provided_fields
+   *   (optional) An array of field machine names this type provides.
+   *
+   * @return \Drupal\media\MediaTypeInterface
+   *   The type created.
+   */
+  public function createMediaTypeTest($type_id, $source_id, array $provided_fields = []) {
+    $session = $this->getSession();
+    $page = $session->getPage();
+    $assert_session = $this->assertSession();
+
+    $this->drupalGet('admin/structure/media/add');
+    $page->fillField('label', $type_id);
+    $this->assertJsCondition("jQuery('.machine-name-value').text() === '{$type_id}'");
+
+    // Make sure the source is available.
+    $this->assertSession()->fieldExists('Media source');
+    $this->assertSession()->optionExists('Media source', $source_id);
+    $page->selectFieldOption('Media source', $source_id);
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Make sure the provided fields are visible on the form.
+    if (!empty($provided_fields)) {
+      foreach ($provided_fields as $provided_field) {
+        $assert_session->selectExists("field_map[{$provided_field}]");
+      }
+    }
+
+    // Save the page to create the type.
+    $page->pressButton('Save');
+    $assert_session->statusCodeEquals(200);
+    $assert_session->pageTextContains('The media type ' . $type_id . ' has been added.');
+    $this->drupalGet('admin/structure/media');
+    $assert_session->pageTextContains($type_id);
+
+    // Bundle definitions are statically cached in the context of the test, we
+    // need to make sure we have updated information before proceeding with the
+    // actions on the UI.
+    \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
+
+    return MediaType::load($type_id);
+  }
+
+}
diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml
new file mode 100644
index 0000000..15de2ac
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml
@@ -0,0 +1,44 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.file.field_media_file
+    - media.type.file
+  module:
+    - file
+id: media.file.default
+targetEntityType: media
+bundle: file
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  field_media_file:
+    settings:
+      progress_indicator: throbber
+    third_party_settings: {  }
+    type: file_generic
+    weight: 26
+    region: content
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    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/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml
new file mode 100644
index 0000000..697f117
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml
@@ -0,0 +1,50 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.media.file.field_media_file
+    - image.style.thumbnail
+    - media.type.file
+  module:
+    - file
+    - image
+    - user
+id: media.file.default
+targetEntityType: media
+bundle: file
+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_file:
+    label: above
+    settings: {  }
+    third_party_settings: {  }
+    type: file_default
+    weight: 6
+    region: content
+  thumbnail:
+    type: image
+    weight: 5
+    label: hidden
+    settings:
+      image_style: thumbnail
+      image_link: ''
+    region: content
+    third_party_settings: {  }
+  uid:
+    label: hidden
+    type: author
+    weight: 0
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml b/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml
new file mode 100644
index 0000000..740d5df
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.media.field_media_file
+    - media.type.file
+  module:
+    - file
+  enforced:
+    module:
+      - media
+id: media.file.field_media_file
+field_name: field_media_file
+entity_type: media
+bundle: file
+label: File
+description: ''
+required: true
+translatable: true
+default_value: {  }
+default_value_callback: ''
+settings:
+  file_directory: '[date:custom:Y]-[date:custom:m]'
+  file_extensions: 'txt pdf'
+  max_filesize: ''
+  handler: 'default:file'
+  handler_settings: {  }
+  description_field: false
+field_type: file
diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml b/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml
new file mode 100644
index 0000000..efe7954
--- /dev/null
+++ b/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml
@@ -0,0 +1,25 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - file
+    - media
+  enforced:
+    module:
+      - media
+id: media.field_media_file
+field_name: field_media_file
+entity_type: media
+type: file
+settings:
+  uri_scheme: public
+  target_type: file
+  display_field: false
+  display_default: false
+module: file
+locked: true
+cardinality: 1
+translatable: true
+indexes: { }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/profiles/standard/config/optional/media.type.file.yml b/core/profiles/standard/config/optional/media.type.file.yml
new file mode 100644
index 0000000..109d801
--- /dev/null
+++ b/core/profiles/standard/config/optional/media.type.file.yml
@@ -0,0 +1,14 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - media
+id: file
+label: File
+description: 'Use the "File" media type for uploading local files.'
+source: file
+queue_thumbnail_downloads: false
+new_revision: false
+source_configuration:
+  source_field: field_media_file
+field_map: { }
