diff --git a/config/schema/media_entity.schema.yml b/config/schema/media_entity.schema.yml
index 1c1f720..2d88660 100644
--- a/config/schema/media_entity.schema.yml
+++ b/config/schema/media_entity.schema.yml
@@ -67,3 +67,13 @@ field.formatter.settings.media_thumbnail:
     image_style:
       type: string
       label: 'Image style'
+
+media_entity.bundle.field_aware_type:
+  type: mapping
+  mapping:
+    source_field:
+      type: string
+      label: 'Source field'
+
+media_entity.bundle.type.generic:
+  type: media_entity.bundle.field_aware_type
diff --git a/src/Annotation/MediaType.php b/src/Annotation/MediaType.php
index 65a3d70..0d43633 100644
--- a/src/Annotation/MediaType.php
+++ b/src/Annotation/MediaType.php
@@ -40,4 +40,11 @@ class MediaType extends Plugin {
    */
   public $description = '';
 
+  /**
+   * The field types that can be used as a source field for this type.
+   *
+   * @var string[]
+   */
+  public $allowed_field_types = [];
+
 }
diff --git a/src/Entity/MediaBundle.php b/src/Entity/MediaBundle.php
index 8833f82..719e365 100644
--- a/src/Entity/MediaBundle.php
+++ b/src/Entity/MediaBundle.php
@@ -2,12 +2,15 @@
 
 namespace Drupal\media_entity\Entity;
 
-use Drupal\Core\Entity\EntityDescriptionInterface;
 use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
+use Drupal\Core\Entity\EntityDescriptionInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
+use Drupal\field\FieldStorageConfigInterface;
 use Drupal\media_entity\MediaBundleInterface;
 use Drupal\media_entity\MediaInterface;
+use Drupal\media_entity\SourceFieldInterface;
 
 /**
  * Defines the Media bundle configuration entity.
@@ -121,7 +124,7 @@ class MediaBundle extends ConfigEntityBundleBase implements MediaBundleInterface
   /**
    * Default status of this media bundle.
    *
-   * @var array
+   * @var bool
    */
   public $status = TRUE;
 
@@ -241,4 +244,72 @@ class MediaBundle extends ConfigEntityBundleBase implements MediaBundleInterface
     $this->new_revision = $new_revision;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageInterface $storage) {
+    parent::preSave($storage);
+
+    // If the handler uses a source field, we'll need to store its name before
+    // saving. We'd need to double-save if we did this in postSave().
+    $handler = $this->getType();
+    if ($handler instanceof SourceFieldInterface) {
+      $storage = $handler->getSourceField($this)->getFieldStorageDefinition();
+      // If the field storage is a new (unsaved) config entity, save it.
+      if ($storage instanceof FieldStorageConfigInterface && $storage->isNew()) {
+        $storage->save();
+      }
+      // Store the field name. We always want to update this value because the
+      // field name may have changed, or a new field may have been created,
+      // depending on the user's actions or the handler's behavior.
+      $configuration = $handler->getConfiguration();
+      $configuration['source_field'] = $storage->getName();
+      $this->setTypeConfiguration($configuration);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+    parent::postSave($storage, $update);
+
+    // If the handler is using a source field, we may need to save it if it's
+    // new. The field storage is guaranteed to exist already because preSave()
+    // took care of that.
+    $handler = $this->getType();
+    if ($handler instanceof SourceFieldInterface) {
+      $field = $handler->getSourceField($this);
+
+      // If the field is new, save it and add it to this bundle's view and form
+      // displays.
+      if ($field->isNew()) {
+        // Ensure the field is saved correctly before adding it to the displays.
+        $field->save();
+
+        $entity_type = $field->getTargetEntityTypeId();
+        $bundle = $field->getTargetBundle();
+
+        if ($field->isDisplayConfigurable('form')) {
+          // Use the default widget and settings.
+          $component = \Drupal::service('plugin.manager.field.widget')
+            ->prepareConfiguration($field->getType(), []);
+
+          entity_get_form_display($entity_type, $bundle, 'default')
+            ->setComponent($field->getName(), $component)
+            ->save();
+        }
+        if ($field->isDisplayConfigurable('view')) {
+          // Use the default formatter and settings.
+          $component = \Drupal::service('plugin.manager.field.formatter')
+            ->prepareConfiguration($field->getType(), []);
+
+          entity_get_display($entity_type, $bundle, 'default')
+            ->setComponent($field->getName(), $component)
+            ->save();
+        }
+      }
+    }
+  }
+
 }
diff --git a/src/MediaBundleForm.php b/src/MediaBundleForm.php
index 014a538..5d310f0 100644
--- a/src/MediaBundleForm.php
+++ b/src/MediaBundleForm.php
@@ -373,7 +373,7 @@ class MediaBundleForm extends EntityForm {
 
     // Override the "status" base field default value, for this bundle.
     $fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle->id());
-    $media = $this->entityTypeManager->getStorage('media')->create(array('bundle' => $bundle->id()));
+    $media = $this->entityTypeManager->getStorage('media')->create(['bundle' => $bundle->id()]);
     $value = (bool) $form_state->getValue(['options', 'status']);
     if ($media->status->value != $value) {
       $fields['status']->getConfig($bundle->id())->setDefaultValue($value)->save();
diff --git a/src/MediaTypeBase.php b/src/MediaTypeBase.php
index a472e7d..2922b17 100644
--- a/src/MediaTypeBase.php
+++ b/src/MediaTypeBase.php
@@ -3,19 +3,19 @@
 namespace Drupal\media_entity;
 
 use Drupal\Component\Plugin\PluginBase;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Form\FormStateInterface;
 
 /**
  * Base implementation of media type plugin.
  */
-abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, ContainerFactoryPluginInterface {
+abstract class MediaTypeBase extends PluginBase implements SourceFieldInterface, ContainerFactoryPluginInterface {
   use StringTranslationTrait;
 
   /**
@@ -105,7 +105,9 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C
    * {@inheritdoc}
    */
   public function defaultConfiguration() {
-    return [];
+    return [
+      'source_field' => NULL,
+    ];
   }
 
   /**
@@ -138,7 +140,28 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    return [];
+    $options = [];
+
+    foreach ($this->entityFieldManager->getFieldStorageDefinitions('media') as $field_name => $field) {
+      $allowed_type = in_array($field->getType(), $this->pluginDefinition['allowed_field_types'], TRUE);
+      if ($allowed_type && !$field->isBaseField()) {
+        $options[$field_name] = $field->getLabel();
+      }
+    }
+
+    // If there are existing fields to choose from, allow the user to reuse one.
+    if ($options) {
+      $form['source_field'] = [
+        '#type' => 'select',
+        '#title' => $this->t('Field with source information.'),
+        '#default_value' => $this->configuration['source_field'],
+        '#empty_option' => $this->t('- Create -'),
+        '#empty_value' => NULL,
+        '#options' => $options,
+        '#description' => $this->t('The field on media items of this type that will store the source information.'),
+      ];
+    }
+    return $form;
   }
 
   /**
@@ -158,4 +181,90 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C
     return 'media:' . $media->bundle() . ':' . $media->uuid();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getSourceField(MediaBundleInterface $bundle) {
+    // If we don't know the name of the source field, we definitely need to
+    // create it.
+    if (empty($this->configuration['source_field'])) {
+      return $this->createSourceField($bundle);
+    }
+    // Even if we do know the name of the source field, there is no guarantee
+    // that it already exists. So check for the field and create it if needed.
+    $field = $this->configuration['source_field'];
+    $fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle->id());
+    return isset($fields[$field]) ? $fields[$field] : $this->createSourceField($bundle);
+  }
+
+  /**
+   * Returns the source field storage definition.
+   *
+   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
+   *   The field storage definition. Will be unsaved if new.
+   */
+  protected function getSourceFieldStorage() {
+    // If we don't know the name of the source field, we definitely need to
+    // create its storage.
+    if (empty($this->configuration['source_field'])) {
+      return $this->createSourceFieldStorage();
+    }
+    // Even if we do know the name of the source field, we cannot guarantee that
+    // its storage exists. So check for the storage and create it if needed.
+    $field = $this->configuration['source_field'];
+    $fields = $this->entityFieldManager->getFieldStorageDefinitions('media');
+    return isset($fields[$field]) ? $fields[$field] : $this->createSourceFieldStorage();
+  }
+
+  /**
+   * Creates the source field storage definition.
+   *
+   * @return \Drupal\field\FieldStorageConfigInterface
+   *   The unsaved field storage definition.
+   */
+  abstract protected function createSourceFieldStorage();
+
+  /**
+   * Creates the source field definition for a bundle.
+   *
+   * @param \Drupal\media_entity\MediaBundleInterface $bundle
+   *   The bundle.
+   *
+   * @return \Drupal\field\FieldConfigInterface
+   *   The unsaved field definition. The field storage definition, if new,
+   *   should also be unsaved.
+   */
+  abstract protected function createSourceField(MediaBundleInterface $bundle);
+
+  /**
+   * Determine the name of the source field.
+   *
+   * @return string
+   *   The source field name. If one is already stored in configuration, it is
+   *   returned. Otherwise, a new, unused one is generated.
+   */
+  protected function getSourceFieldName() {
+    if ($this->configuration['source_field']) {
+      return $this->configuration['source_field'];
+    }
+
+    $base_id = 'field_media_' . $this->getPluginId();
+    $tries = 0;
+    $storage = $this->entityTypeManager->getStorage('field_storage_config');
+
+    // Iterate at least once, until no field with the generated ID is found.
+    do {
+      $id = $base_id;
+      // If we've tried before, increment and append the suffix.
+      if ($tries) {
+        $id .= '_' . $tries;
+      }
+      $field = $storage->load('media.' . $id);
+      $tries++;
+    }
+    while ($field);
+
+    return $id;
+  }
+
 }
diff --git a/src/MediaTypeInterface.php b/src/MediaTypeInterface.php
index 2a171c2..767217f 100644
--- a/src/MediaTypeInterface.php
+++ b/src/MediaTypeInterface.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\media_entity;
 
-use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
 
 /**
diff --git a/src/Plugin/Action/DeleteMedia.php b/src/Plugin/Action/DeleteMedia.php
index b2a98d2..4a5a48e 100644
--- a/src/Plugin/Action/DeleteMedia.php
+++ b/src/Plugin/Action/DeleteMedia.php
@@ -84,7 +84,7 @@ class DeleteMedia extends ActionBase implements ContainerFactoryPluginInterface
    * {@inheritdoc}
    */
   public function execute($object = NULL) {
-    $this->executeMultiple(array($object));
+    $this->executeMultiple([$object]);
   }
 
   /**
diff --git a/src/Plugin/MediaEntity/Type/Generic.php b/src/Plugin/MediaEntity/Type/Generic.php
index b83ba9d..7fe313e 100644
--- a/src/Plugin/MediaEntity/Type/Generic.php
+++ b/src/Plugin/MediaEntity/Type/Generic.php
@@ -3,6 +3,7 @@
 namespace Drupal\media_entity\Plugin\MediaEntity\Type;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\media_entity\MediaBundleInterface;
 use Drupal\media_entity\MediaInterface;
 use Drupal\media_entity\MediaTypeBase;
 
@@ -12,7 +13,8 @@ use Drupal\media_entity\MediaTypeBase;
  * @MediaType(
  *   id = "generic",
  *   label = @Translation("Generic media"),
- *   description = @Translation("Generic media type.")
+ *   description = @Translation("Generic media type."),
+ *   allowed_field_types = {"string"}
  * )
  */
 class Generic extends MediaTypeBase {
@@ -42,6 +44,8 @@ class Generic extends MediaTypeBase {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
     $form['text'] = [
       '#type' => 'markup',
       '#markup' => $this->t("This type provider doesn't need configuration."),
@@ -50,4 +54,32 @@ class Generic extends MediaTypeBase {
     return $form;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function createSourceFieldStorage() {
+    return $this->entityTypeManager
+      ->getStorage('field_storage_config')
+      ->create([
+        'entity_type' => 'media',
+        'field_name' => $this->getSourceFieldName(),
+        // Strings are harmless, inoffensive puppies: a good choice for a
+        // generic media type.
+        'type' => 'string',
+      ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createSourceField(MediaBundleInterface $bundle) {
+    /** @var \Drupal\field\FieldConfigInterface $field */
+    return $this->entityTypeManager
+      ->getStorage('field_config')
+      ->create([
+        'field_storage' => $this->getSourceFieldStorage(),
+        'bundle' => $bundle->id(),
+      ]);
+  }
+
 }
diff --git a/src/SourceFieldInterface.php b/src/SourceFieldInterface.php
new file mode 100644
index 0000000..6c245d7
--- /dev/null
+++ b/src/SourceFieldInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\media_entity;
+
+/**
+ * Interface for media type plugins that depend on a field.
+ */
+interface SourceFieldInterface extends MediaTypeInterface {
+
+  /**
+   * Returns the source field for a bundle using this plugin.
+   *
+   * @param \Drupal\media_entity\MediaBundleInterface $bundle
+   *   The media bundle.
+   *
+   * @return \Drupal\field\FieldConfigInterface
+   */
+  public function getSourceField(MediaBundleInterface $bundle);
+
+}
diff --git a/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml b/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml
index 6ff7e14..257c840 100644
--- a/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml
+++ b/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml
@@ -5,3 +5,6 @@ media_entity.bundle.type.test_type:
     test_config_value:
       type: string
       label: 'Test config value'
+    source_field:
+      type: string
+      label: 'Source field'
diff --git a/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php
index 60d68a0..b8fee53 100644
--- a/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php
+++ b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php
@@ -11,7 +11,8 @@ use Drupal\media_entity\Plugin\MediaEntity\Type\Generic;
  * @MediaType(
  *   id = "test_type",
  *   label = @Translation("Test type"),
- *   description = @Translation("Test media type.")
+ *   description = @Translation("Test media type."),
+ *   allowed_field_types = {"string"},
  * )
  */
 class TestType extends Generic {
@@ -30,7 +31,7 @@ class TestType extends Generic {
    * {@inheritdoc}
    */
   public function defaultConfiguration() {
-    return [
+    return parent::defaultConfiguration() + [
       'test_config_value' => 'This is default value.',
     ];
   }
@@ -39,6 +40,8 @@ class TestType extends Generic {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
     $form['test_config_value'] = [
       '#type' => 'textfield',
       '#title' => $this->t('Test config value'),
diff --git a/tests/src/FunctionalJavascript/BundleCreationTest.php b/tests/src/FunctionalJavascript/BundleCreationTest.php
new file mode 100644
index 0000000..5e6eac9
--- /dev/null
+++ b/tests/src/FunctionalJavascript/BundleCreationTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\Tests\media_entity\FunctionalJavascript;
+
+/**
+ * Tests the media bundle creation.
+ *
+ * @group media_entity
+ */
+class BundleCreationTest extends MediaEntityJavascriptTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'media_entity_test_type',
+  ];
+
+  /**
+   * Tests the media bundle creation form.
+   */
+  public function testBundleCreationFormWithDefaultField() {
+    $label = 'Bundle with Default Field';
+    $bundleMachineName = str_replace(' ', '_', strtolower($label));
+
+    $this->drupalGet('admin/structure/media/add');
+    $page = $this->getSession()->getPage();
+
+    // Fill in a label to the bundle.
+    $page->fillField('label', $label);
+    // Wait for machine name generation. Default: waitUntilVisible(), does not
+    // work properly.
+    $this->getSession()
+      ->wait(5000, "jQuery('.machine-name-value').text() === '{$bundleMachineName}'");
+
+    // Select our test bundle type.
+    $this->assertSession()->fieldExists('Type provider');
+    $this->assertSession()->optionExists('Type provider', 'test_type');
+    $page->selectFieldOption('Type provider', 'test_type');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+
+    $page->pressButton('Save media bundle');
+
+    // Check whether the source field was correctly created.
+    $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}/fields");
+
+    // Check 2nd column of first data row, to be machine name for field name.
+    $this->assertSession()
+      ->elementContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[2]', 'field_media_test_type');
+    // Check 3rd column of first data row, to be correct field type.
+    $this->assertSession()
+      ->elementTextContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[3]', 'Text (plain)');
+
+    // Check that the source field is correctly assigned to media bundle.
+    $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}");
+
+    $this->assertSession()
+      ->fieldValueEquals('type_configuration[test_type][source_field]', 'field_media_test_type');
+  }
+
+  /**
+   * Test creation of media bundle, reusing an existing source field.
+   */
+  public function testBundleCreationReuseSourceField() {
+    // Create a new bundle, which should create a new field we can reuse.
+    $this->drupalGet('/admin/structure/media/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('label', 'Pastafazoul');
+    $this->getSession()
+      ->wait(5000, "jQuery('.machine-name-value').text() === 'pastafazoul'");
+    $page->selectFieldOption('Type provider', 'generic');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save media bundle');
+
+    $label = 'Bundle reusing Default Field';
+    $bundleMachineName = str_replace(' ', '_', strtolower($label));
+
+    $this->drupalGet('admin/structure/media/add');
+    $page = $this->getSession()->getPage();
+
+    // Fill in a label to the bundle.
+    $page->fillField('label', $label);
+
+    // Wait for machine name generation. Default: waitUntilVisible(), does not
+    // work properly.
+    $this->getSession()
+      ->wait(5000, "jQuery('.machine-name-value').text() === '{$bundleMachineName}'");
+
+    // Select our test bundle type.
+    $this->assertSession()->fieldExists('Type provider');
+    $this->assertSession()->optionExists('Type provider', 'test_type');
+    $page->selectFieldOption('Type provider', 'test_type');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    // Select the existing field for re-use.
+    $page->selectFieldOption('type_configuration[test_type][source_field]', 'field_media_generic');
+    $page->pressButton('Save media bundle');
+
+    // Check that there are not fields created.
+    $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}/fields");
+    // The reused field should be present...
+    $this->assertSession()->pageTextContains('field_media_generic');
+    // ...not a new, unique one.
+    $this->assertSession()->pageTextNotContains('field_media_generic_1');
+  }
+
+}
diff --git a/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php b/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
index c8219fe..4cd1c7e 100644
--- a/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
+++ b/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
@@ -140,6 +140,11 @@ class MediaUiJavascriptTest extends MediaEntityJavascriptTestBase {
     $this->assertFalse($loaded_bundle->getStatus());
     $this->assertEquals($loaded_bundle->field_map, ['field_1' => 'name']);
 
+    // We need to clear the statically cached field definitions to account for
+    // fields that have been created by API calls in this test, since they exist
+    // in a separate memory space from the web server.
+    $this->container->get('entity_field.manager')->clearCachedFieldDefinitions();
+
     // Test that a media being created with default status to "FALSE" will be
     // created unpublished.
     /** @var \Drupal\media_entity\MediaInterface $unpublished_media */
diff --git a/tests/src/Kernel/BasicCreationTest.php b/tests/src/Kernel/BasicCreationTest.php
index 306b553..90514c6 100644
--- a/tests/src/Kernel/BasicCreationTest.php
+++ b/tests/src/Kernel/BasicCreationTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\media_entity\Kernel;
 
+use Drupal\field\Entity\FieldConfig;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\media_entity\Entity\Media;
 use Drupal\media_entity\Entity\MediaBundle;
@@ -79,7 +80,7 @@ class BasicCreationTest extends KernelTestBase {
     $this->assertEquals($test_bundle->get('label'), 'Test bundle', 'Could not assure the correct bundle label.');
     $this->assertEquals($test_bundle->get('description'), 'Test bundle.', 'Could not assure the correct bundle description.');
     $this->assertEquals($test_bundle->get('type'), 'generic', 'Could not assure the correct bundle plugin type.');
-    $this->assertEquals($test_bundle->get('type_configuration'), [], 'Could not assure the correct plugin configuration.');
+    $this->assertEquals($test_bundle->get('type_configuration'), ['source_field' => 'field_media_generic_1'], 'Could not assure the correct plugin configuration.');
     $this->assertEquals($test_bundle->get('field_map'), [], 'Could not assure the correct field map.');
   }
 
@@ -112,4 +113,51 @@ class BasicCreationTest extends KernelTestBase {
     $this->assertEquals($media->label(), $expected_name, 'The media was not created with a default name.');
   }
 
+  /**
+   * Tests creating and updating bundles programmatically.
+   */
+  public function testProgrammaticBundleManipulation() {
+    // Creating a bundle programmatically without specifying a source field
+    // should create one automagically.
+    /** @var FieldConfig $field */
+    $field = $this->testBundle->getType()->getSourceField($this->testBundle);
+    $this->assertInstanceOf(FieldConfig::class, $field);
+    $this->assertEquals('field_media_generic', $field->getName());
+    $this->assertFalse($field->isNew());
+
+    // Saving with a non-existent source field should create it.
+    $this->testBundle->setTypeConfiguration([
+      'source_field' => 'field_magick',
+    ]);
+    $this->testBundle->save();
+    $field = $this->testBundle->getType()->getSourceField($this->testBundle);
+    $this->assertInstanceOf(FieldConfig::class, $field);
+    $this->assertEquals('field_magick', $field->getName());
+    $this->assertFalse($field->isNew());
+
+    // Trying to save without a source field should create a new, de-duped one.
+    $this->testBundle->setTypeConfiguration([]);
+    $this->testBundle->save();
+    $field = $this->testBundle->getType()->getSourceField($this->testBundle);
+    $this->assertInstanceOf(FieldConfig::class, $field);
+    $this->assertEquals('field_media_generic_1', $field->getName());
+    $this->assertFalse($field->isNew());
+
+    // Trying to reuse an existing field should, well, reuse the existing field.
+    $this->testBundle->setTypeConfiguration([
+      'source_field' => 'field_magick',
+    ]);
+    $this->testBundle->save();
+    $field = $this->testBundle->getType()->getSourceField($this->testBundle);
+    $this->assertInstanceOf(FieldConfig::class, $field);
+    $this->assertEquals('field_magick', $field->getName());
+    $this->assertFalse($field->isNew());
+    // No new de-duped fields should have been created.
+    $duplicates = FieldConfig::loadMultiple([
+      'media.' . $this->testBundle->id() . '.field_magick_1',
+      'media.' . $this->testBundle->id() . '.field_media_generic_2',
+    ]);
+    $this->assertEmpty($duplicates);
+  }
+
 }
