diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index 6327656..0a5e7fe 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\field\FieldStorageConfigInterface;
@@ -201,7 +202,10 @@ public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterfac
       return FALSE;
     }
 
-    return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original);
+    $current_schema = $this->getSchemaFromStorageDefinition($storage_definition);
+    $this->processFieldStorageSchema($current_schema);
+
+    return $current_schema != $this->loadFieldSchemaData($original);
   }
 
   /**
@@ -852,6 +856,7 @@ protected function loadFieldSchemaData(FieldStorageDefinitionInterface $storage_
    *   The field schema data array.
    */
   protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
+    $this->processFieldStorageSchema($schema);
     $this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
   }
 
@@ -1102,6 +1107,23 @@ protected function processIdentifierSchema(&$schema, $key) {
   }
 
   /**
+   * Processes the schema for a field storage definition.
+   *
+   * @param array &$field_storage_schema
+   *   An array that contains the schema data for a field storage definition.
+   */
+  protected function processFieldStorageSchema(array &$field_storage_schema) {
+    // Clean up some schema properties that should not be taken into account
+    // after a field storage has been created.
+    foreach ($field_storage_schema as $table_name => $table_schema) {
+      foreach ($table_schema['fields'] as $key => $schema) {
+        unset($field_storage_schema[$table_name]['fields'][$key]['initial']);
+        unset($field_storage_schema[$table_name]['fields'][$key]['initial_from_field']);
+      }
+    }
+  }
+
+  /**
    * Performs the specified operation on a field.
    *
    * This figures out whether the field is stored in a dedicated or shared table
@@ -1613,29 +1635,62 @@ protected function hasNullFieldPropertyData($table_name, $column_name) {
    *   - foreign keys: The schema definition for the foreign keys.
    *
    * @throws \Drupal\Core\Field\FieldException
-   *   Exception thrown if the schema contains reserved column names.
+   *   Exception thrown if the schema contains reserved column names or if the
+   *   initial values definition is invalid.
    */
   protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
     $schema = [];
+    $table_mapping = $this->storage->getTableMapping();
     $field_schema = $storage_definition->getSchema();
 
     // Check that the schema does not include forbidden column names.
-    if (array_intersect(array_keys($field_schema['columns']), $this->storage->getTableMapping()->getReservedColumns())) {
+    if (array_intersect(array_keys($field_schema['columns']), $table_mapping->getReservedColumns())) {
       throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
     }
 
     $field_name = $storage_definition->getName();
     $base_table = $this->storage->getBaseTable();
 
+    // Define the initial values, if any.
+    $initial_value = $initial_value_from_field = [];
+    $storage_definition_is_new = empty($this->loadFieldSchemaData($storage_definition));
+    if ($storage_definition_is_new && $storage_definition instanceof BaseFieldDefinition && $table_mapping->allowsSharedTableStorage($storage_definition)) {
+      if (($initial_storage_value = $storage_definition->getInitialValue()) && !empty($initial_storage_value)) {
+        // We only support initial values for fields that are stored in shared
+        // tables (i.e. single-value fields).
+        // @todo Implement initial value support for multi-value fields in
+        //   https://www.drupal.org/node/2883851.
+        $initial_value = reset($initial_storage_value);
+      }
+
+      if ($initial_value_field_name = $storage_definition->getInitialValueFromField()) {
+        // Check that the field used for populating initial values is valid. We
+        // must use the last installed version of that, as the new field might
+        // be created in an update function and the storage definition of the
+        // "from" field might get changed later.
+        $last_installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
+        if (!isset($last_installed_storage_definitions[$initial_value_field_name])) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field $initial_value_field_name does not exist.");
+        }
+
+        if ($storage_definition->getType() !== $last_installed_storage_definitions[$initial_value_field_name]->getType()) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: The field types do not match.");
+        }
+
+        if (!$table_mapping->allowsSharedTableStorage($last_installed_storage_definitions[$initial_value_field_name])) {
+          throw new FieldException("Illegal initial value definition on {$storage_definition->getName()}: Both fields have to be stored in the shared entity tables.");
+        }
+
+        $initial_value_from_field = $table_mapping->getColumnNames($initial_value_field_name);
+      }
+    }
+
     // A shared table contains rows for entities where the field is empty
     // (since other fields stored in the same table might not be empty), thus
     // the only columns that can be 'not null' are those for required
-    // properties of required fields. However, even those would break in the
-    // case where a new field is added to a table that contains existing rows.
-    // For now, we only hardcode 'not null' to a couple "entity keys", in order
-    // to keep their indexes optimized.
-    // @todo Revisit once we have support for 'initial' in
-    //   https://www.drupal.org/node/2346019.
+    // properties of required fields. For now, we only hardcode 'not null' to a
+    // few "entity keys", in order to keep their indexes optimized.
+    // @todo Fix this in https://www.drupal.org/node/2841291.
     $not_null_keys = $this->entityType->getKeys();
     // Label fields are not necessarily required.
     unset($not_null_keys['label']);
@@ -1654,6 +1709,14 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
 
       $schema['fields'][$schema_field_name] = $column_schema;
       $schema['fields'][$schema_field_name]['not null'] = in_array($field_name, $not_null_keys);
+
+      // Use the initial value of the field storage, if available.
+      if ($initial_value && isset($initial_value[$field_column_name])) {
+        $schema['fields'][$schema_field_name]['initial'] = $initial_value[$field_column_name];
+      }
+      elseif (!empty($initial_value_from_field)) {
+        $schema['fields'][$schema_field_name]['initial_from_field'] = $initial_value_from_field[$field_column_name];
+      }
     }
 
     if (!empty($field_schema['indexes'])) {
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 37cc103..25df63b 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -510,6 +510,89 @@ public function setDefaultValueCallback($callback) {
   }
 
   /**
+   * Returns the initial value for the field.
+   *
+   * @return array
+   *   The initial value for the field, as a numerically indexed array of items,
+   *   each item being a property/value array (array() for no default value).
+   */
+  public function getInitialValue() {
+    $value = isset($this->definition['initial_value']) ? $this->definition['initial_value'] : [];
+
+    // Normalize into the "array keyed by delta" format.
+    if (isset($value) && !is_array($value)) {
+      $value = [
+        [$this->getMainPropertyName() => $value],
+      ];
+    }
+
+    return $value;
+  }
+
+  /**
+   * Sets an initial value for the field.
+   *
+   * @param mixed $value
+   *   The initial value for the field. This can be either:
+   *   - a literal, in which case it will be assigned to the first property of
+   *     the first item;
+   *   - a numerically indexed array of items, each item being a property/value
+   *     array;
+   *   - a non-numerically indexed array, in which case the array is assumed to
+   *     be a property/value array and used as the first item;
+   *   - an empty array for no initial value.
+   *
+   * @return $this
+   */
+  public function setInitialValue($value) {
+    // @todo Implement initial value support for multi-value fields in
+    //   https://www.drupal.org/node/2883851.
+    if ($this->isMultiple()) {
+      throw new FieldException('Multi-value fields can not have an initial value.');
+    }
+
+    if ($value === NULL) {
+      $value = [];
+    }
+    // Unless the value is an empty array, we may need to transform it.
+    if (!is_array($value) || !empty($value)) {
+      if (!is_array($value)) {
+        $value = [[$this->getMainPropertyName() => $value]];
+      }
+      elseif (is_array($value) && !is_numeric(array_keys($value)[0])) {
+        $value = [0 => $value];
+      }
+    }
+    $this->definition['initial_value'] = $value;
+
+    return $this;
+  }
+
+  /**
+   * Returns the name of the field that will be used for getting initial values.
+   *
+   * @return string|null
+   *   The field name.
+   */
+  public function getInitialValueFromField() {
+    return isset($this->definition['initial_value_from_field']) ? $this->definition['initial_value_from_field'] : NULL;
+  }
+
+  /**
+   * Sets a field that will be used for getting initial values.
+   *
+   * @param string $field_name
+   *   The name of the field that will be used for getting initial values.
+   *
+   * @return $this
+   */
+  public function setInitialValueFromField($field_name) {
+    $this->definition['initial_value_from_field'] = $field_name;
+
+    return $this;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
index 646ad2e..92733bc 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityTypeEvents;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\entity_test_update\Entity\EntityTestUpdate;
@@ -817,4 +818,119 @@ public function testLongNameFieldIndexes() {
     $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.');
   }
 
+  /**
+   * Tests adding a base field with initial values.
+   */
+  public function testInitialValue() {
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
+    $db_schema = $this->database->schema();
+
+    // Create two entities before adding the base field.
+    /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
+    $storage->create()->save();
+    $storage->create()->save();
+
+    // Add a base field with an initial value.
+    $this->addBaseField();
+    $storage_definition = BaseFieldDefinition::create('string')
+      ->setLabel(t('A new base field'))
+      ->setInitialValue('test value');
+
+    $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
+    $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
+    $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
+
+    // Check that the initial values have been applied.
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
+    $entities = $storage->loadMultiple();
+    $this->assertEquals('test value', $entities[1]->get('new_base_field')->value);
+    $this->assertEquals('test value', $entities[2]->get('new_base_field')->value);
+  }
+
+  /**
+   * Tests adding a base field with initial values inherited from another field.
+   */
+  public function testInitialValueFromField() {
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
+    $db_schema = $this->database->schema();
+
+    // Create two entities before adding the base field.
+    /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
+    $storage->create(['name' => 'First entity'])->save();
+    $storage->create(['name' => 'Second entity'])->save();
+
+    // Add a base field with an initial value inherited from another field.
+    $this->addBaseField();
+    $storage_definition = BaseFieldDefinition::create('string')
+      ->setLabel(t('A new base field'))
+      ->setInitialValueFromField('name');
+
+    $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
+    $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
+    $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
+
+    // Check that the initial values have been applied.
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
+    $entities = $storage->loadMultiple();
+    $this->assertEquals('First entity', $entities[1]->get('new_base_field')->value);
+    $this->assertEquals('Second entity', $entities[2]->get('new_base_field')->value);
+  }
+
+  /**
+   * Tests the error handling when using initial values from another field.
+   */
+  public function testInitialValueFromFieldErrorHandling() {
+    // Check that setting invalid values for 'initial value from field' doesn't
+    // work.
+    try {
+      $this->addBaseField();
+      $storage_definition = BaseFieldDefinition::create('string')
+        ->setLabel(t('A new base field'))
+        ->setInitialValueFromField('field_that_does_not_exist');
+      $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
+      $this->fail('Using a non-existent field as initial value does not work.');
+    }
+    catch (FieldException $e) {
+      $this->assertEquals('Illegal initial value definition on new_base_field: The field field_that_does_not_exist does not exist.', $e->getMessage());
+      $this->pass('Using a non-existent field as initial value does not work.');
+    }
+
+    try {
+      $this->addBaseField();
+      $storage_definition = BaseFieldDefinition::create('integer')
+        ->setLabel(t('A new base field'))
+        ->setInitialValueFromField('name');
+      $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
+      $this->fail('Using a field of a different type as initial value does not work.');
+    }
+    catch (FieldException $e) {
+      $this->assertEquals('Illegal initial value definition on new_base_field: The field types do not match.', $e->getMessage());
+      $this->pass('Using a field of a different type as initial value does not work.');
+    }
+
+    try {
+      // Add a base field that will not be stored in the shared tables.
+      $initial_field = BaseFieldDefinition::create('string')
+        ->setName('initial_field')
+        ->setLabel(t('An initial field'))
+        ->setCardinality(2);
+      $this->state->set('entity_test_update.additional_base_field_definitions', ['initial_field' => $initial_field]);
+      $this->entityDefinitionUpdateManager->installFieldStorageDefinition('initial_field', 'entity_test_update', 'entity_test', $initial_field);
+
+      // Now add the base field which will try to use the previously added field
+      // as the source of its initial values.
+      $new_base_field = BaseFieldDefinition::create('string')
+        ->setName('new_base_field')
+        ->setLabel(t('A new base field'))
+        ->setInitialValueFromField('initial_field');
+      $this->state->set('entity_test_update.additional_base_field_definitions', ['initial_field' => $initial_field, 'new_base_field' => $new_base_field]);
+      $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $new_base_field);
+      $this->fail('Using a field that is not stored in the shared tables as initial value does not work.');
+    }
+    catch (FieldException $e) {
+      $this->assertEquals('Illegal initial value definition on new_base_field: Both fields have to be stored in the shared entity tables.', $e->getMessage());
+      $this->pass('Using a field that is not stored in the shared tables as initial value does not work.');
+    }
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
index 341ed9c..616f6ba 100644
--- a/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
@@ -195,6 +195,52 @@ public function testFieldDefaultValue() {
   }
 
   /**
+   * Tests field initial value.
+   *
+   * @covers ::getInitialValue
+   * @covers ::setInitialValue
+   */
+  public function testFieldInitialValue() {
+    $definition = BaseFieldDefinition::create($this->fieldType);
+    $default_value = [
+      'value' => $this->randomMachineName(),
+    ];
+    $expected_default_value = [$default_value];
+    $definition->setInitialValue($default_value);
+    $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
+      ->disableOriginalConstructor()
+      ->getMock();
+    // Set the field item list class to be used to avoid requiring the typed
+    // data manager to retrieve it.
+    $definition->setClass('Drupal\Core\Field\FieldItemList');
+    $this->assertEquals($expected_default_value, $definition->getInitialValue($entity));
+
+    $data_definition = $this->getMockBuilder('Drupal\Core\TypedData\DataDefinition')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $data_definition->expects($this->any())
+      ->method('getClass')
+      ->will($this->returnValue('Drupal\Core\Field\FieldItemBase'));
+    $definition->setItemDefinition($data_definition);
+
+    // Set default value only with a literal.
+    $definition->setInitialValue($default_value['value']);
+    $this->assertEquals($expected_default_value, $definition->getInitialValue($entity));
+
+    // Set default value with an indexed array.
+    $definition->setInitialValue($expected_default_value);
+    $this->assertEquals($expected_default_value, $definition->getInitialValue($entity));
+
+    // Set default value with an empty array.
+    $definition->setInitialValue([]);
+    $this->assertEquals([], $definition->getInitialValue($entity));
+
+    // Set default value with NULL.
+    $definition->setInitialValue(NULL);
+    $this->assertEquals([], $definition->getInitialValue($entity));
+  }
+
+  /**
    * Tests field translatable methods.
    *
    * @covers ::isTranslatable
