From a820153fa530341048c604555286fff1e1428329 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sat, 13 Apr 2013 18:06:40 +0100
Subject: [PATCH] Issue #1735118 by swentel, yched, xjm, larowlan, alexpott,
 tim.plunkett: Convert Field API to CMI.

---
 .../field/widget/DatetimeDatelistWidget.php   |   2 +-
 .../field/widget/DatetimeDefaultWidget.php    |   2 +-
 .../datetime/Tests/DateTimeFieldTest.php      |  11 +-
 .../edit/lib/Drupal/edit/EditorBase.php       |   2 +-
 .../edit/lib/Drupal/edit/EditorInterface.php  |   2 +-
 .../edit/lib/Drupal/edit/EditorSelector.php   |   2 +-
 .../Drupal/edit/EditorSelectorInterface.php   |   2 +-
 .../lib/Drupal/edit/MetadataGenerator.php     |   2 +-
 .../edit/MetadataGeneratorInterface.php       |   2 +-
 .../edit/Plugin/edit/editor/DirectEditor.php  |   2 +-
 .../edit/Plugin/edit/editor/FormEditor.php    |   2 +-
 .../lib/Drupal/edit/Tests/EditTestBase.php    |   5 +-
 .../Plugin/edit/editor/WysiwygEditor.php      |   2 +-
 .../editor/Plugin/edit/editor/Editor.php      |   2 +-
 .../Drupal/entity/Tests/EntityDisplayTest.php |   6 -
 core/modules/field/field.api.php              |  25 +-
 core/modules/field/field.attach.inc           |  23 +-
 core/modules/field/field.crud.inc             | 755 +++++-------------
 core/modules/field/field.info.inc             |   2 +-
 core/modules/field/field.info.yml             |   2 -
 core/modules/field/field.install              | 325 ++++----
 core/modules/field/field.module               | 247 ++----
 core/modules/field/field.views.inc            |   6 +-
 .../field/lib/Drupal/field/FieldInfo.php      | 109 +--
 .../field/lib/Drupal/field/FieldInstance.php  | 135 ----
 .../field/FieldInstanceStorageController.php  |  36 +
 .../Drupal/field/Plugin/Core/Entity/Field.php | 600 ++++++++++++++
 .../Plugin/Core/Entity/FieldInstance.php      | 528 ++++++++++++
 .../Plugin/Type/Formatter/FormatterBase.php   |   2 +-
 .../Type/Formatter/FormatterInterface.php     |   2 +-
 .../Type/Formatter/FormatterPluginManager.php |   2 +-
 .../field/Plugin/Type/Widget/WidgetBase.php   |   4 +-
 .../Plugin/Type/Widget/WidgetInterface.php    |   2 +-
 .../Drupal/field/Plugin/views/field/Field.php |   5 +-
 .../lib/Drupal/field/Tests/BulkDeleteTest.php |  24 +-
 .../field/lib/Drupal/field/Tests/CrudTest.php |  81 +-
 .../field/Tests/FieldAttachStorageTest.php    |   8 +-
 .../field/Tests/FieldImportChangeTest.php     |  64 ++
 .../field/Tests/FieldImportCreateTest.php     |  81 ++
 .../field/Tests/FieldImportDeleteTest.php     |  93 +++
 .../lib/Drupal/field/Tests/FieldInfoTest.php  |  39 +-
 .../field/Tests/FieldInstanceCrudTest.php     |  36 +-
 .../Drupal/field/Tests/FieldUnitTestBase.php  |   5 +-
 .../Drupal/field/Tests/TranslationTest.php    |  27 +-
 .../modules/field_test/field_test.storage.inc |  20 +-
 .../config/field.field.field_test_import.yml  |  20 +
 ...t_entity.test_bundle.field_test_import.yml |  20 +
 .../field_test_config.info.yml                |   6 +
 .../field_test_config.module                  |   6 +
 .../field.field.field_test_import_staging.yml |  20 +
 ....test_bundle.field_test_import_staging.yml |  20 +
 .../field_sql_storage.install                 |  89 ++-
 .../field_sql_storage.module                  |  48 +-
 .../Tests/FieldSqlStorageTest.php             |   4 +-
 core/modules/field_ui/field_ui.admin.inc      |  20 +-
 core/modules/file/file.install                |  18 +-
 .../Drupal/file/Tests/FileFieldWidgetTest.php |  13 +-
 .../lib/Drupal/file/Tests/FileItemTest.php    |   3 -
 core/modules/forum/forum.install              |  11 +-
 .../Drupal/hal/Tests/NormalizerTestBase.php   |   1 -
 core/modules/image/image.module               |   8 +-
 .../Tests/Condition/NodeConditionTest.php     |   2 -
 .../Drupal/number/Tests/NumberFieldTest.php   |   5 -
 .../Drupal/options/Tests/OptionsFieldTest.php |   9 +-
 core/modules/options/options.module           |  11 +-
 .../Tests/EntitySerializationTest.php         |   1 -
 .../Tests/DrupalUnitTestBaseTest.php          |   3 -
 .../Tests/Database/SelectComplexTest.php      |   5 -
 .../Tests/Entity/EntityUnitTestBase.php       |   1 -
 .../system/Tests/Entity/FieldAccessTest.php   |   2 -
 .../Tests/Upgrade/FieldUpgradePathTest.php    | 128 +++
 .../Upgrade/UserPictureUpgradePathTest.php    |   2 +-
 .../tests/upgrade/drupal-7.field.database.php | 335 ++++++++
 .../taxonomy/Tests/VocabularyUnitTest.php     |  13 +-
 .../Tests/Formatter/TextPlainUnitTest.php     |   3 -
 .../translation_entity.admin.inc              |   9 +-
 core/modules/user/user.install                |   4 -
 .../Tests/Plugin/RelationshipJoinTestBase.php |   1 -
 78 files changed, 2799 insertions(+), 1376 deletions(-)
 delete mode 100644 core/modules/field/lib/Drupal/field/FieldInstance.php
 create mode 100644 core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
 create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
 create mode 100644 core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
 create mode 100644 core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php
 create mode 100644 core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
 create mode 100644 core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php
 create mode 100644 core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml
 create mode 100644 core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml
 create mode 100644 core/modules/field/tests/modules/field_test_config/field_test_config.info.yml
 create mode 100644 core/modules/field/tests/modules/field_test_config/field_test_config.module
 create mode 100644 core/modules/field/tests/modules/field_test_config/staging/field.field.field_test_import_staging.yml
 create mode 100644 core/modules/field/tests/modules/field_test_config/staging/field.instance.test_entity.test_bundle.field_test_import_staging.yml

diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php
index 2be0e27b3c4d..bc3fd06dfcf5 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php
@@ -12,7 +12,7 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Plugin\PluginSettingsBase;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\datetime\DateHelper;
 
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php
index 75ccad3d7307..5e2221782e66 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php
@@ -12,7 +12,7 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Plugin\PluginSettingsBase;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\Core\Datetime\DrupalDateTime;
 
 /**
diff --git a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
index 232d0192e468..bb3bddfd0aa9 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
@@ -48,13 +48,12 @@ function setUp() {
     $this->drupalLogin($web_user);
 
     // Create a field with settings to validate.
-    $this->field = array(
+    $this->field = field_create_field(array(
       'field_name' => drupal_strtolower($this->randomName()),
       'type' => 'datetime',
       'settings' => array('datetime_type' => 'date'),
-    );
-    field_create_field($this->field);
-    $this->instance = array(
+    ));
+    $this->instance = field_create_instance(array(
       'field_name' => $this->field['field_name'],
       'entity_type' => 'test_entity',
       'bundle' => 'test_bundle',
@@ -64,8 +63,7 @@ function setUp() {
       'settings' => array(
         'default_value' => 'blank',
       ),
-    );
-    field_create_instance($this->instance);
+    ));
 
     $this->display_options = array(
       'type' => 'datetime_default',
@@ -304,6 +302,7 @@ function testDefaultValue() {
 
     // Set the default value to 'blank'.
     $this->instance['settings']['default_value'] = 'blank';
+    $this->instance['default_value_function'] = 'datetime_default_value';
     field_update_instance($this->instance);
 
     // Display creation form.
diff --git a/core/modules/edit/lib/Drupal/edit/EditorBase.php b/core/modules/edit/lib/Drupal/edit/EditorBase.php
index d2e4d0948821..a0844d1c270c 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorBase.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorBase.php
@@ -9,7 +9,7 @@
 
 use Drupal\Component\Plugin\PluginBase;
 use Drupal\edit\EditorInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Defines a base editor (Create.js PropertyEditor widget) implementation.
diff --git a/core/modules/edit/lib/Drupal/edit/EditorInterface.php b/core/modules/edit/lib/Drupal/edit/EditorInterface.php
index 251233a496de..df4c7f9d2364 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorInterface.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorInterface.php
@@ -8,7 +8,7 @@
 namespace Drupal\edit;
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Defines an interface for in-place editors (Create.js PropertyEditor widgets).
diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelector.php b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
index 131eea2be8cd..fe713400412b 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorSelector.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorSelector.php
@@ -9,7 +9,7 @@
 
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Component\Utility\NestedArray;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Selects an in-place editor (an Editor plugin) for a field.
diff --git a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php b/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php
index 2c180cd4a2f7..926c7a6ba8d6 100644
--- a/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php
+++ b/core/modules/edit/lib/Drupal/edit/EditorSelectorInterface.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\edit;
 
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Interface for selecting an in-place editor (an Editor plugin) for a field.
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
index 6fc1bdc15a86..b613442c251d 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
@@ -9,7 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\edit\Access\EditEntityFieldAccessCheckInterface;
 
 
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
index 2b6b1d8624ad..71e447d5c52c 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGeneratorInterface.php
@@ -8,7 +8,7 @@
 namespace Drupal\edit;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Interface for generating in-place editing metadata for an entity field.
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
index 28b86f9d6e08..ef6e516e0b21 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
@@ -9,7 +9,7 @@
 
 use Drupal\edit\EditorBase;
 use Drupal\Component\Annotation\Plugin;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Defines the "direct" Create.js PropertyEditor widget.
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
index 65ddc581a1db..988037f7ad58 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
@@ -9,7 +9,7 @@
 
 use Drupal\edit\EditorBase;
 use Drupal\Component\Annotation\Plugin;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Defines the "form" Create.js PropertyEditor widget.
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
index 6ac8b3b0f4be..bdf56c54a45a 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
@@ -28,7 +28,6 @@ function setUp() {
     parent::setUp();
 
     $this->installSchema('system', 'variable');
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     $this->installSchema('entity_test', array('entity_test', 'entity_test_rev'));
 
     // Set default storage backend.
@@ -58,12 +57,12 @@ function setUp() {
    */
   function createFieldWithInstance($field_name, $type, $cardinality, $label, $instance_settings, $widget_type, $widget_settings, $formatter_type, $formatter_settings) {
     $field = $field_name . '_field';
-    $this->$field = array(
+    $this->field = array(
       'field_name' => $field_name,
       'type' => $type,
       'cardinality' => $cardinality,
     );
-    $this->$field_name = field_create_field($this->$field);
+    $this->$field = field_create_field($this->field);
 
     $instance = $field_name . '_instance';
     $this->$instance = array(
diff --git a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
index 81387f0519f9..acc044505f65 100644
--- a/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
+++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/edit/editor/WysiwygEditor.php
@@ -9,7 +9,7 @@
 
 use Drupal\edit\EditorBase;
 use Drupal\Component\Annotation\Plugin;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Defines the "wysiwyg" Create.js PropertyEditor widget.
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
index da8f18971a78..a35d25439037 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
@@ -11,7 +11,7 @@
 use Drupal\Component\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
 use Drupal\edit\EditorInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 
 /**
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
index eb15f23b8141..1257f48cf3d4 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
@@ -24,12 +24,6 @@ public static function getInfo() {
     );
   }
 
-  protected function setUp() {
-    parent::setUp();
-
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
-  }
-
   /**
    * Tests basic CRUD operations on EntityDisplay objects.
    */
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index cba467d50e05..095c28d49702 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -234,6 +234,9 @@ function hook_field_info_alter(&$info) {
  *     indexes specified by the field-type module. Some storage engines might
  *     not support indexes.
  *   - foreign keys: (optional) An array of Schema API foreign key definitions.
+ *     Note, however, that the field data is not necessarily stored in SQL.
+ *     Also, the possible usage is limited, as you cannot specify another field
+ *     as related, only existing SQL tables, such as {taxonomy_term_data}.
  */
 function hook_field_schema($field) {
   if ($field['type'] == 'text_long') {
@@ -1302,7 +1305,7 @@ function hook_field_storage_details_alter(&$details, $field) {
  *   FIELD_LOAD_REVISION to load the version indicated by each entity.
  * @param $fields
  *   An array listing the fields to be loaded. The keys of the array are field
- *   IDs, and the values of the array are the entity IDs (or revision IDs,
+ *   UUIDs, and the values of the array are the entity IDs (or revision IDs,
  *   depending on the $age parameter) to add each field to.
  * @param $options
  *   An associative array of additional options, with the following keys:
@@ -1370,7 +1373,7 @@ function hook_field_storage_load($entity_type, $entities, $age, $fields, $option
  *   FIELD_STORAGE_INSERT when inserting a new entity.
  * @param $fields
  *   An array listing the fields to be written. The keys and values of the
- *   array are field IDs.
+ *   array are field UUIDs.
  */
 function hook_field_storage_write(\Drupal\Core\Entity\EntityInterface $entity, $op, $fields) {
   $id = $entity->id();
@@ -1464,7 +1467,7 @@ function hook_field_storage_write(\Drupal\Core\Entity\EntityInterface $entity, $
  *   The entity on which to operate.
  * @param $fields
  *   An array listing the fields to delete. The keys and values of the
- *   array are field IDs.
+ *   array are field UUIDs.
  */
 function hook_field_storage_delete(\Drupal\Core\Entity\EntityInterface $entity, $fields) {
   foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
@@ -1488,7 +1491,7 @@ function hook_field_storage_delete(\Drupal\Core\Entity\EntityInterface $entity,
  *   The entity on which to operate.
  * @param $fields
  *   An array listing the fields to delete. The keys and values of the
- *   array are field IDs.
+ *   array are field UUIDs.
  */
 function hook_field_storage_delete_revision(\Drupal\Core\Entity\EntityInterface $entity, $fields) {
   $vid = $entity->getRevisionId();
@@ -1712,13 +1715,13 @@ function hook_field_storage_delete_instance($instance) {
  *   FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
  *   FIELD_LOAD_REVISION to load the version indicated by each entity.
  * @param $skip_fields
- *   An array keyed by field IDs whose data has already been loaded and
+ *   An array keyed by field UUIDs whose data has already been loaded and
  *   therefore should not be loaded again. Add a key to this array to indicate
  *   that your module has already loaded a field.
  * @param $options
  *   An associative array of additional options, with the following keys:
- *   - field_id: The field ID that should be loaded. If unset, all fields should
- *     be loaded.
+ *   - field_id: The field UUID that should be loaded. If unset, all fields
+ *     should be loaded.
  *   - deleted: If TRUE, deleted fields should be loaded as well as non-deleted
  *     fields. If unset or FALSE, only non-deleted fields should be loaded.
  */
@@ -1735,11 +1738,11 @@ function hook_field_storage_pre_load($entity_type, $entities, $age, &$skip_field
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity with fields to save.
  * @param $skip_fields
- *   An array keyed by field IDs whose data has already been written and
+ *   An array keyed by field UUIDs whose data has already been written and
  *   therefore should not be written again. The values associated with these
  *   keys are not specified.
  * @return
- *   Saved field IDs are set set as keys in $skip_fields.
+ *   Saved field UUIDs are set as keys in $skip_fields.
  */
 function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
   if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
@@ -1770,11 +1773,11 @@ function hook_field_storage_pre_insert(\Drupal\Core\Entity\EntityInterface $enti
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity with fields to save.
  * @param $skip_fields
- *   An array keyed by field IDs whose data has already been written and
+ *   An array keyed by field UUIDs whose data has already been written and
  *   therefore should not be written again. The values associated with these
  *   keys are not specified.
  * @return
- *   Saved field IDs are set set as keys in $skip_fields.
+ *   Saved field UUIDs are set as keys in $skip_fields.
  */
 function hook_field_storage_pre_update(\Drupal\Core\Entity\EntityInterface $entity, &$skip_fields) {
   $first_call = &drupal_static(__FUNCTION__, array());
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index bf55c16f3a37..87bd6bbb2a44 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -1170,7 +1170,7 @@ function field_attach_insert(EntityInterface $entity) {
   $storages = array();
   foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
     $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['id'];
+    $field_id = $field['uuid'];
     $field_name = $field['field_name'];
     if (!empty($entity->$field_name)) {
       // Collect the storage backend if the field has not been written yet.
@@ -1211,7 +1211,7 @@ function field_attach_update(EntityInterface $entity) {
   $storages = array();
   foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
     $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['id'];
+    $field_id = $field['uuid'];
     $field_name = $field['field_name'];
     // Leave the field untouched if $entity comes with no $field_name property,
     // but empty the field if it comes as a NULL value or an empty array.
@@ -1254,7 +1254,7 @@ function field_attach_delete(EntityInterface $entity) {
   $storages = array();
   foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
     $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['id'];
+    $field_id = $field['uuid'];
     $storages[$field['storage']['type']][$field_id] = $field_id;
   }
 
@@ -1287,7 +1287,7 @@ function field_attach_delete_revision(EntityInterface $entity) {
   $storages = array();
   foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
     $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['id'];
+    $field_id = $field['uuid'];
     $storages[$field['storage']['type']][$field_id] = $field_id;
   }
 
@@ -1531,11 +1531,16 @@ function field_entity_bundle_create($entity_type, $bundle) {
  * Implements hook_entity_bundle_rename().
  */
 function field_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
-  db_update('field_config_instance')
-    ->fields(array('bundle' => $bundle_new))
-    ->condition('entity_type', $entity_type)
-    ->condition('bundle', $bundle_old)
-    ->execute();
+  $instances = field_read_instances();
+  foreach ($instances as $instance) {
+    if ($instance->entity_type == $entity_type && $instance->bundle == $bundle_old) {
+      $id_new = $instance['entity_type'] . '.' . $bundle_new . '.' . $instance['field_name'];
+      $instance->id = $id_new;
+      $instance->bundle = $bundle_new;
+      $instance->allowBundleRename();
+      $instance->save();
+    }
+  }
 
   // Clear the cache.
   field_cache_clear();
diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc
index a2b88b5a738c..3138b00bf987 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -6,6 +6,8 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\field\FieldException;
 
 /**
@@ -28,8 +30,8 @@
  * This function does not bind the field to any bundle; use
  * field_create_instance() for that.
  *
- * @param $field
- *   A field definition array. The field_name and type properties are required.
+ * @param array $field
+ *   A field definition. The field_name and type properties are required.
  *   Other properties, if omitted, will be given the following default values:
  *   - cardinality: 1
  *   - locked: FALSE
@@ -48,157 +50,16 @@
  *     - settings: each omitted setting is given the default value specified in
  *       hook_field_storage_info().
  *
- * @return
- *   The $field array with the id property filled in.
+ * @return \Drupal\field\Plugin\Core\Entity\Field
+ *   The field entity.
  *
  * @throws Drupal\field\FieldException
  *
  * See: @link field Field API data structures @endlink.
  */
-function field_create_field($field) {
-  // Field name is required.
-  if (empty($field['field_name'])) {
-    throw new FieldException('Attempt to create an unnamed field.');
-  }
-  // Field type is required.
-  if (empty($field['type'])) {
-    throw new FieldException('Attempt to create a field with no type.');
-  }
-  // Field name cannot contain invalid characters.
-  if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $field['field_name'])) {
-    throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
-  }
-
-  // Field name cannot be longer than 32 characters. We use drupal_strlen()
-  // because the DB layer assumes that column widths are given in characters,
-  // not bytes.
-  if (drupal_strlen($field['field_name']) > 32) {
-    throw new FieldException(t('Attempt to create a field with a name longer than 32 characters: %name',
-      array('%name' => $field['field_name'])));
-  }
-
-  // Ensure the field name is unique over active and disabled fields.
-  // We do not care about deleted fields.
-  $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
-  if (!empty($prior_field)) {
-    $message = $prior_field['active']?
-      t('Attempt to create field name %name which already exists and is active.', array('%name' => $field['field_name'])):
-      t('Attempt to create field name %name which already exists, although it is inactive.', array('%name' => $field['field_name']));
-    throw new FieldException($message);
-  }
-
-  // Disallow reserved field names. This can't prevent all field name
-  // collisions with existing entity properties, but some is better
-  // than none.
-  foreach (entity_get_info() as $type => $info) {
-    if (in_array($field['field_name'], $info['entity_keys'])) {
-      throw new FieldException(t('Attempt to create field name %name which is reserved by entity type %type.', array('%name' => $field['field_name'], '%type' => $type)));
-    }
-  }
-
-  $field += array(
-    'entity_types' => array(),
-    'cardinality' => 1,
-    'translatable' => FALSE,
-    'locked' => FALSE,
-    'settings' => array(),
-    'storage' => array(),
-    'deleted' => 0,
-  );
-
-  // Check that the field type is known.
-  $field_type = field_info_field_types($field['type']);
-  if (!$field_type) {
-    throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
-  }
-  // Create all per-field-type properties (needed here as long as we have
-  // settings that impact column definitions).
-  $field['settings'] += field_info_field_settings($field['type']);
-  $field['module'] = $field_type['module'];
-  $field['active'] = 1;
-
-  // Provide default storage.
-  $field['storage'] += array(
-    'type' => variable_get('field_storage_default', 'field_sql_storage'),
-    'settings' => array(),
-  );
-  // Check that the storage type is known.
-  $storage_type = field_info_storage_types($field['storage']['type']);
-  if (!$storage_type) {
-    throw new FieldException(t('Attempt to create a field with unknown storage type %type.', array('%type' => $field['storage']['type'])));
-  }
-  // Provide default storage settings.
-  $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
-  $field['storage']['module'] = $storage_type['module'];
-  $field['storage']['active'] = 1;
-  // Collect storage information.
-  module_load_install($field['module']);
-  $schema = (array) module_invoke($field['module'], 'field_schema', $field);
-  $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
-  // 'columns' are hardcoded in the field type.
-  $field['columns'] = $schema['columns'];
-  if (array_intersect(array_keys($field['columns']), field_reserved_columns())) {
-    throw new FieldException(t('Illegal field type columns.'));
-  }
-  // 'foreign keys' are hardcoded in the field type.
-  $field['foreign keys'] = $schema['foreign keys'];
-  // 'indexes' can be both hardcoded in the field type, and specified in the
-  // incoming $field definition.
-  $field += array(
-    'indexes' => array(),
-  );
-  $field['indexes'] += $schema['indexes'];
-
-  // The serialized 'data' column contains everything from $field that does not
-  // have its own column and is not automatically populated when the field is
-  // read.
-  $data = $field;
-  unset($data['columns'], $data['field_name'], $data['type'], $data['active'], $data['module'], $data['storage_type'], $data['storage_active'], $data['storage_module'], $data['locked'], $data['cardinality'], $data['deleted']);
-  // Additionally, do not save the 'bundles' property populated by
-  // field_info_field().
-  unset($data['bundles']);
-
-  $record = array(
-    'field_name' => $field['field_name'],
-    'type' => $field['type'],
-    'module' => $field['module'],
-    'active' => $field['active'],
-    'storage_type' => $field['storage']['type'],
-    'storage_module' => $field['storage']['module'],
-    'storage_active' => $field['storage']['active'],
-    'locked' => $field['locked'],
-    'data' => $data,
-    'cardinality' => $field['cardinality'],
-    'translatable' => $field['translatable'],
-    'deleted' => $field['deleted'],
-  );
-
-  // Store the field and get the id back.
-  drupal_write_record('field_config', $record);
-  $field['id'] = $record['id'];
-
-  // Invoke hook_field_storage_create_field after the field is
-  // complete (e.g. it has its id).
-  try {
-    // Invoke hook_field_storage_create_field after
-    // drupal_write_record() sets the field id.
-    module_invoke($storage_type['module'], 'field_storage_create_field', $field);
-  }
-  catch (Exception $e) {
-    // If storage creation failed, remove the field_config record before
-    // rethrowing the exception.
-    db_delete('field_config')
-      ->condition('id', $field['id'])
-      ->execute();
-    throw $e;
-  }
-
-  // Clear caches
-  field_cache_clear(TRUE);
-
-  // Invoke external hooks after the cache is cleared for API consistency.
-  module_invoke_all('field_create_field', $field);
-
+function field_create_field(array $field) {
+  $field = entity_create('field_entity', $field);
+  $field->save();
   return $field;
 }
 
@@ -212,90 +73,33 @@ function field_create_field($field) {
  * semantics, or if there are external dependencies on field settings
  * that cannot be updated.
  *
- * @param $field
- *   A field structure. $field['field_name'] must provided; it
- *   identifies the field that will be updated to match this
- *   structure. Any other properties of the field that are not
- *   specified in $field will be left unchanged, so it is not
- *   necessary to pass in a fully populated $field structure.
+ * @param mixed $field
+ *   Either the \Drupal\field\Plugin\Core\Entity\Field object to update, or a
+ *   field array structure. If the latter, $field['field_name'] must provided;
+ *   it identifies the field that will be updated to match this structure. Any
+ *   other properties of the field that are not specified in $field will be left
+ *   unchanged, so it is not necessary to pass in a fully populated $field
+ *   structure.
  *
  * @throws Drupal\field\FieldException
  *
  * @see field_create_field()
  */
 function field_update_field($field) {
-  // Check that the specified field exists.
-  $prior_field = field_read_field($field['field_name']);
-  if (empty($prior_field)) {
-    throw new FieldException('Attempt to update a non-existent field.');
-  }
-
-  // Use the prior field values for anything not specifically set by the new
-  // field to be sure that all values are set.
-  $field += $prior_field;
-  $field['settings'] += $prior_field['settings'];
-
-  // Some updates are always disallowed.
-  if ($field['type'] != $prior_field['type']) {
-    throw new FieldException("Cannot change an existing field's type.");
-  }
-  if ($field['entity_types'] != $prior_field['entity_types']) {
-    throw new FieldException("Cannot change an existing field's entity_types property.");
-  }
-  if ($field['storage']['type'] != $prior_field['storage']['type']) {
-    throw new FieldException("Cannot change an existing field's storage type.");
-  }
-
-  // Collect the new storage information, since what is in
-  // $prior_field may no longer be right.
-  module_load_install($field['module']);
-  $schema = (array) module_invoke($field['module'], 'field_schema', $field);
-  $schema += array('columns' => array(), 'indexes' => array());
-  // 'columns' are hardcoded in the field type.
-  $field['columns'] = $schema['columns'];
-  // 'indexes' can be both hardcoded in the field type, and specified in the
-  // incoming $field definition.
-  $field += array(
-    'indexes' => array(),
-  );
-  $field['indexes'] += $schema['indexes'];
-
-  $has_data = field_has_data($field);
-
-  // See if any module forbids the update by throwing an exception.
-  foreach (module_implements('field_update_forbid') as $module) {
-    $function = $module . '_field_update_forbid';
-    $function($field, $prior_field, $has_data);
+  // Module developers can still pass in an array of properties.
+  if (is_array($field)) {
+    $field_loaded = entity_load('field_entity', $field['field_name']);
+    if (empty($field_loaded)) {
+      throw new FieldException('Attempt to update a non-existent field.');
+    }
+    // Merge incoming values.
+    foreach ($field as $key => $value) {
+      $field_loaded[$key] = $value;
+    }
+    $field = $field_loaded;
   }
 
-  // Tell the storage engine to update the field. Do this before
-  // saving the new definition since it still might fail.
-  $storage_type = field_info_storage_types($field['storage']['type']);
-  module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data);
-
-  // Save the new field definition. @todo: refactor with
-  // field_create_field.
-
-  // The serialized 'data' column contains everything from $field that does not
-  // have its own column and is not automatically populated when the field is
-  // read.
-  $data = $field;
-  unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']);
-  // Additionally, do not save the 'bundles' property populated by
-  // field_info_field().
-  unset($data['bundles']);
-
-  $field['data'] = $data;
-
-  // Store the field and create the id.
-  $primary_key = array('id');
-  drupal_write_record('field_config', $field, $primary_key);
-
-  // Clear caches
-  field_cache_clear(TRUE);
-
-  // Invoke external hooks after the cache is cleared for API consistency.
-  module_invoke_all('field_update_field', $field, $prior_field, $has_data);
+  $field->save();
 }
 
 /**
@@ -324,12 +128,9 @@ function field_read_field($field_name, $include_additional = array()) {
 /**
  * Reads in fields that match an array of conditions.
  *
- * @param array $params
- *   An array of conditions to match against. Keys are columns from the
- *   'field_config' table, values are conditions to match. Additionally,
- *   conditions on the 'entity_type' and 'bundle' columns from the
- *   'field_config_instance' table are supported (select fields having an
- *   instance on a given bundle).
+ * @param array $conditions
+ *   An array of conditions to match against. Keys are names of properties found
+ *   in field configuration files, and values are conditions to match.
  * @param array $include_additional
  *   The default behavior of this function is to not return fields that are
  *   inactive or have been deleted. Setting
@@ -341,69 +142,71 @@ function field_read_field($field_name, $include_additional = array()) {
  *   $include_additional['include_deleted'] is TRUE, the array is keyed by
  *   field ID, otherwise it is keyed by field name.
  */
-function field_read_fields($params = array(), $include_additional = array()) {
-  $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC));
-  $query->fields('fc');
-
-  // Turn the conditions into a query.
-  foreach ($params as $key => $value) {
-    // Allow filtering on the 'entity_type' and 'bundle' columns of the
-    // field_config_instance table.
-    if ($key == 'entity_type' || $key == 'bundle') {
-      if (empty($fci_join)) {
-        $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id');
-      }
-      $key = 'fci.' . $key;
-    }
-    else {
-      $key = 'fc.' . $key;
-    }
+function field_read_fields($conditions = array(), $include_additional = array()) {
+  // Include inactive fields if specified in the $include_additional parameter.
+  $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive'];
+  // Include deleted fields if specified either in the $include_additional or
+  // the $conditions parameters.
+  $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']) || (isset($conditions['deleted']) && $conditions['deleted']);
 
-    $query->condition($key, $value);
+  // Get fields stored in configuration.
+  if (isset($conditions['field_name'])) {
+    // Optimize for the most frequent case where we do have a specific ID.
+    $fields = entity_load_multiple('field_entity', array($conditions['field_name']));
+  }
+  else {
+    // No specific ID, we need to examine all existing fields.
+    $fields = entity_load_multiple('field_entity');
   }
 
-  if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
-    $query
-      ->condition('fc.active', 1)
-      ->condition('fc.storage_active', 1);
+  // Merge deleted fields (stored in state) if needed.
+  if ($include_deleted) {
+    $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
+    foreach ($deleted_fields as $id => $config) {
+      $fields[$id] = entity_create('field_entity', $config);
+    }
   }
-  $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']);
-  if (!$include_deleted) {
-    $query->condition('fc.deleted', 0);
+
+  // Translate "do not include inactive instances" into actual conditions.
+  if (!$include_inactive) {
+    $conditions['active'] = 1;
+    $conditions['storage.active'] = 1;
   }
 
-  $fields = array();
-  $results = $query->execute();
-  foreach ($results as $record) {
-    $field = unserialize($record['data']);
-    $field['id'] = $record['id'];
-    $field['field_name'] = $record['field_name'];
-    $field['type'] = $record['type'];
-    $field['module'] = $record['module'];
-    $field['active'] = $record['active'];
-    $field['storage']['type'] = $record['storage_type'];
-    $field['storage']['module'] = $record['storage_module'];
-    $field['storage']['active'] = $record['storage_active'];
-    $field['locked'] = $record['locked'];
-    $field['cardinality'] = $record['cardinality'];
-    $field['translatable'] = $record['translatable'];
-    $field['deleted'] = $record['deleted'];
+  // Collect matching fields.
+  $matching_fields = array();
+  foreach ($fields as $field) {
+    foreach ($conditions as $key => $value) {
+      // Extract the actual value against which the condition is checked.
+      switch ($key) {
+        case 'storage.active':
+          $checked_value = $field->storage['active'];
+          break;
+
+        case 'field_name';
+          $checked_value = $field->id;
+          break;
+
+        default:
+          $checked_value = $field->$key;
+          break;
+      }
 
-    module_invoke_all('field_read_field', $field);
+      // Skip to the next field as soon as one condition does not match.
+      if ($checked_value != $value) {
+        continue 2;
+      }
+    }
 
-    // Populate storage information.
-    module_load_install($field['module']);
-    $schema = (array) module_invoke($field['module'], 'field_schema', $field);
-    $schema += array('columns' => array(), 'indexes' => array());
-    $field['columns'] = $schema['columns'];
+    module_invoke_all('field_read_field', $field);
 
-    $field_name = $field['field_name'];
-    if ($include_deleted) {
-      $field_name = $field['id'];
-    }
-    $fields[$field_name] = $field;
+    // When returning deleted fields, key the results by UUID since they can
+    // include several fields with the same ID.
+    $key = $include_deleted ? $field->uuid : $field->id;
+    $matching_fields[$key] = $field;
   }
-  return $fields;
+
+  return $matching_fields;
 }
 
 /**
@@ -413,36 +216,15 @@ function field_read_fields($params = array(), $include_additional = array()) {
  *   The field name to delete.
  */
 function field_delete_field($field_name) {
-  // Delete all non-deleted instances.
-  $field = field_info_field($field_name);
-  if (isset($field['bundles'])) {
-    foreach ($field['bundles'] as $entity_type => $bundles) {
-      foreach ($bundles as $bundle) {
-        $instance = field_info_instance($entity_type, $field_name, $bundle);
-        field_delete_instance($instance, FALSE);
-      }
-    }
+  if ($field = field_info_field($field_name)) {
+    $field->delete();
   }
-
-  // Mark field data for deletion.
-  module_invoke($field['storage']['module'], 'field_storage_delete_field', $field);
-
-  // Mark the field for deletion.
-  db_update('field_config')
-    ->fields(array('deleted' => 1))
-    ->condition('field_name', $field_name)
-    ->execute();
-
-  // Clear the cache.
-  field_cache_clear(TRUE);
-
-  module_invoke_all('field_delete_field', $field);
 }
 
 /**
  * Creates an instance of a field, binding it to a bundle.
  *
- * @param $instance
+ * @param array $instance
  *   A field instance definition array. The field_name, entity_type and
  *   bundle properties are required. Other properties, if omitted,
  *   will be given the following default values:
@@ -456,80 +238,27 @@ function field_delete_field($field_name) {
  *     - type: the default widget specified in hook_field_info().
  *     - settings: each omitted setting is given the default value specified in
  *       hook_field_widget_info().
- *   - display:
- *     Settings for the 'default' view mode will be added if not present, and
- *     each view mode in the definition will be completed with the following
- *     default values:
- *     - label: 'above'
- *     - type: the default formatter specified in hook_field_info().
- *     - settings: each omitted setting is given the default value specified in
- *       hook_field_formatter_info().
- *     View modes not present in the definition are left empty, and the field
- *     will not be displayed in this mode.
  *
- * @return
- *   The $instance array with the id property filled in.
+ * @return \Drupal\field\Plugin\Core\Entity\FieldInstance
+ *   The field instance entity.
  *
  * @throws Drupal\field\FieldException
  *
  * See: @link field Field API data structures @endlink.
  */
-function field_create_instance(&$instance) {
-  $field = field_read_field($instance['field_name']);
-  if (empty($field)) {
-    throw new FieldException(t("Attempt to create an instance of a field @field_name that doesn't exist or is currently inactive.", array('@field_name' => $instance['field_name'])));
-  }
-  // Check that the required properties exists.
-  if (empty($instance['entity_type'])) {
-    throw new FieldException(t('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $instance['field_name'])));
-  }
-  if (empty($instance['bundle'])) {
-    throw new FieldException(t('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $instance['field_name'])));
-  }
-  // Check that the field can be attached to this entity type.
-  if (!empty($field['entity_types']) && !in_array($instance['entity_type'], $field['entity_types'])) {
-    throw new FieldException(t('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $instance['field_name'], '@entity_type' => $instance['entity_type'])));
-  }
-
-  // Set the field id.
-  $instance['field_id'] = $field['id'];
-
-  // Note that we do *not* prevent creating a field on non-existing bundles,
-  // because that would break the 'Body as field' upgrade for contrib
-  // node types.
-
-  // TODO: Check that the widget type is known and can handle the field type ?
-  // TODO: Check that the formatters are known and can handle the field type ?
-  // TODO: Check that the display view modes are known for the entity type ?
-  // Those checks should probably happen in _field_write_instance() ?
-  // Problem : this would mean that a UI module cannot update an instance with a disabled formatter.
-
-  // Ensure the field instance is unique within the bundle.
-  // We only check for instances of active fields, since adding an instance of
-  // a disabled field is not supported.
-  $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
-  if (!empty($prior_instance)) {
-    $message = t('Attempt to create an instance of field @field_name on bundle @bundle that already has an instance of that field.', array('@field_name' => $instance['field_name'], '@bundle' => $instance['bundle']));
-    throw new FieldException($message);
-  }
-
-  _field_write_instance($instance);
-
-  // Clear caches
-  field_cache_clear();
-
-  // Invoke external hooks after the cache is cleared for API consistency.
-  module_invoke_all('field_create_instance', $instance);
-
+function field_create_instance(array $instance) {
+  $instance = entity_create('field_instance', $instance);
+  $instance->save();
   return $instance;
 }
 
 /**
  * Updates an instance of a field.
  *
- * @param $instance
- *   An associative array representing an instance structure. The required keys
- *   and values are:
+ * @param mixed $instance
+ *   Either the \Drupal\field\Plugin\Core\Entity\FieldInstance to update, or an
+ *   associative array representing an instance structure. If the latter, the
+ *   required keys and values are:
  *   - entity_type: The type of the entity the field is attached to.
  *   - bundle: The bundle this field belongs to.
  *   - field_name: The name of an existing field.
@@ -542,103 +271,20 @@ function field_create_instance(&$instance) {
  * @see field_create_instance()
  */
 function field_update_instance($instance) {
-  // Check that the specified field exists.
-  $field = field_read_field($instance['field_name']);
-  if (empty($field)) {
-    throw new FieldException(t('Attempt to update an instance of a nonexistent field @field.', array('@field' => $instance['field_name'])));
-  }
-
-  // Check that the field instance exists (even if it is inactive, since we
-  // want to be able to replace inactive widgets with new ones).
-  $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
-  if (empty($prior_instance)) {
-    throw new FieldException(t("Attempt to update an instance of field @field on bundle @bundle that doesn't exist.", array('@field' => $instance['field_name'], '@bundle' => $instance['bundle'])));
-  }
-
-  $instance['id'] = $prior_instance['id'];
-  $instance['field_id'] = $prior_instance['field_id'];
-
-  _field_write_instance($instance, TRUE);
-
-  // Clear caches.
-  field_cache_clear();
-
-  module_invoke_all('field_update_instance', $instance, $prior_instance);
-}
-
-/**
- * Stores an instance record in the field configuration database.
- *
- * @param $instance
- *   An instance structure.
- * @param $update
- *   Whether this is a new or existing instance.
- */
-function _field_write_instance(&$instance, $update = FALSE) {
-  $field = field_read_field($instance['field_name']);
-  $field_type = field_info_field_types($field['type']);
-
-  // Temporary workaround to allow incoming $instance as arrays or classed
-  // objects.
-  // @todo remove once the external APIs have been converted to use
-  // FieldInstance objects.
-  if (is_object($instance) && get_class($instance) == 'Drupal\field\FieldInstance') {
-    $instance = $instance->getArray();
+  // Module developers can still pass in an array of properties.
+  if (is_array($instance)) {
+    $instance_loaded = entity_load('field_instance', $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name']);
+    if (empty($instance_loaded)) {
+      throw new FieldException('Attempt to update a non-existent instance.');
+    }
+    // Merge incoming values.
+    foreach ($instance as $key => $value) {
+      $instance_loaded[$key] = $value;
+    }
+    $instance = $instance_loaded;
   }
 
-  // Set defaults.
-  $instance += array(
-    'settings' => array(),
-    'widget' => array(),
-    'required' => FALSE,
-    'label' => $instance['field_name'],
-    'description' => '',
-    'deleted' => 0,
-  );
-
-  // Set default instance settings.
-  $instance['settings'] += field_info_instance_settings($field['type']);
-
-  // Set default widget and settings.
-  $instance['widget'] += array(
-    // TODO: what if no 'default_widget' specified ?
-    'type' => $field_type['default_widget'],
-    'settings' => array(),
-  );
-  // If no weight specified, make sure the field sinks at the bottom.
-  if (!isset($instance['widget']['weight'])) {
-    $max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], 'form');
-    $instance['widget']['weight'] = isset($max_weight) ? $max_weight + 1 : 0;
-  }
-  // Check widget module.
-  $widget_type = field_info_widget_types($instance['widget']['type']);
-  $instance['widget']['module'] = $widget_type['module'];
-  $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
-
-  // The serialized 'data' column contains everything from $instance that does
-  // not have its own column and is not automatically populated when the
-  // instance is read.
-  $data = $instance;
-  unset($data['id'], $data['field_id'], $data['field_name'], $data['entity_type'], $data['bundle'], $data['deleted']);
-
-  $record = array(
-    'field_id' => $instance['field_id'],
-    'field_name' => $instance['field_name'],
-    'entity_type' => $instance['entity_type'],
-    'bundle' => $instance['bundle'],
-    'data' => $data,
-    'deleted' => $instance['deleted'],
-  );
-  // We need to tell drupal_update_record() the primary keys to trigger an
-  // update.
-  if ($update) {
-    $record['id'] = $instance['id'];
-    drupal_write_record('field_config_instance', $record, array('id'));
-  }
-  else {
-    drupal_write_record('field_config_instance', $record);
-    $instance['id'] = $record['id'];
-  }
+  $instance->save();
 }
 
 /**
@@ -672,9 +318,9 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi
  * Reads in field instances that match an array of conditions.
  *
  * @param $param
- *   An array of properties to use in selecting a field instance. Valid keys
- *   include any column of the field_config_instance table. If NULL, all
- *   instances will be returned.
+ *   An array of properties to use in selecting a field instance. Keys are names
+ *   of properties found in field instance configuration files, and values are
+ *   conditions to match.
  * @param $include_additional
  *   The default behavior of this function is to not return field instances that
  *   have been marked deleted, or whose field is inactive. Setting
@@ -684,84 +330,105 @@ function field_read_instance($entity_type, $field_name, $bundle, $include_additi
  * @return
  *   An array of instances matching the arguments.
  */
-function field_read_instances($params = array(), $include_additional = array()) {
+function field_read_instances($conditions = array(), $include_additional = array()) {
+  // Include instances of inactive fields if specified in the
+  // $include_additional parameter.
   $include_inactive = isset($include_additional['include_inactive']) && $include_additional['include_inactive'];
-  $include_deleted = isset($include_additional['include_deleted']) && $include_additional['include_deleted'];
+  // Include deleted instances if specified either in the $include_additional
+  // or the $conditions parameters.
+  $include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']) || (isset($conditions['deleted']) && $conditions['deleted']);
 
-  $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
-  $query->join('field_config', 'fc', 'fc.id = fci.field_id');
-  $query->fields('fci');
+  // Get instances stored in configuration.
+  if (isset($conditions['entity_type']) && isset($conditions['bundle']) && isset($conditions['field_name'])) {
+    // Optimize for the most frequent case where we do have a specific ID.
+    $instances = entity_load_multiple('field_instance', array($conditions['entity_type'] . '.' . $conditions['bundle'] . '.' . $conditions['field_name']));
+  }
+  else {
+    // No specific ID, we need to examine all existing instances.
+    $instances = entity_load_multiple('field_instance');
+  }
 
-  // Turn the conditions into a query.
-  foreach ($params as $key => $value) {
-    $query->condition('fci.' . $key, $value);
+  // Merge deleted instances (stored in state) if needed.
+  if ($include_deleted) {
+    $state = Drupal::state();
+    $deleted_fields = $state->get('field.field.deleted');
+    $deleted_instances = $state->get('field.instance.deleted') ?: array();
+    foreach ($deleted_instances as $id => $config) {
+      $instances[$id] = entity_create('field_instance', $config);
+    }
   }
+
+  // Translate "do not include inactive fields" into actual conditions.
   if (!$include_inactive) {
-    $query
-      ->condition('fc.active', 1)
-      ->condition('fc.storage_active', 1);
-  }
-  if (!$include_deleted) {
-    $query->condition('fc.deleted', 0);
-    $query->condition('fci.deleted', 0);
+    $conditions['field.active'] = 1;
+    $conditions['field.storage.active'] = 1;
   }
 
-  $instances = array();
-  $results = $query->execute();
-
-  foreach ($results as $record) {
-    // Filter out instances on unknown entity types (for instance because the
-    // module exposing them was disabled).
-    $entity_info = entity_get_info($record['entity_type']);
-    if ($include_inactive || $entity_info) {
-      $instance = unserialize($record['data']);
-      $instance['id'] = $record['id'];
-      $instance['field_id'] = $record['field_id'];
-      $instance['field_name'] = $record['field_name'];
-      $instance['entity_type'] = $record['entity_type'];
-      $instance['bundle'] = $record['bundle'];
-      $instance['deleted'] = $record['deleted'];
-
-      module_invoke_all('field_read_instance', $instance);
-      $instances[] = $instance;
+  // Collect matching instances.
+  $matching_instances = array();
+  foreach ($instances as $instance) {
+    // Only include instances on unknown entity types if 'include_inactive'.
+    if (!$include_inactive && !entity_get_info($instance->entity_type)) {
+      continue;
+    }
+
+    // Get data from the field. If the field is marked as deleted, we need to
+    // get it from the state storage.
+    $field = entity_load('field_entity', $instance->field_name);
+    if (empty($field) && $include_deleted && isset($deleted_fields[$instance->field_uuid])) {
+      $field = new Field($deleted_fields[$instance->field_uuid]);
+    }
+    if (empty($field)) {
+      continue;
+    }
+
+    // Only keep the instance if it matches all conditions.
+    foreach ($conditions as $key => $value) {
+      // Extract the actual value against which the condition is checked.
+      switch ($key) {
+        case 'field.active':
+          $checked_value = $field->active;
+          break;
+
+        case 'field.storage.active':
+          $checked_value = $field->storage['active'];
+          break;
+
+        case 'field_id':
+          $checked_value = $instance->field_uuid;
+          break;
+
+        default:
+          $checked_value = $instance->$key;
+          break;
+      }
+
+      // Skip to the next instance as soon as one condition does not match.
+      if ($checked_value != $value) {
+        continue 2;
+      }
     }
+
+    module_invoke_all('field_read_instance', $instance);
+
+    $matching_instances[] = $instance;
   }
-  return $instances;
+
+  return $matching_instances;
 }
 
 /**
  * Marks a field instance and its data for deletion.
  *
- * @param $instance
- *   An instance structure.
+ * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
+ *   The field instance.
  * @param $field_cleanup
  *   If TRUE, the field will be deleted as well if its last instance is being
  *   deleted. If FALSE, it is the caller's responsibility to handle the case of
  *   fields left without instances. Defaults to TRUE.
  */
-function field_delete_instance($instance, $field_cleanup = TRUE) {
-  // Mark the field instance for deletion.
-  db_update('field_config_instance')
-    ->fields(array('deleted' => 1))
-    ->condition('field_name', $instance['field_name'])
-    ->condition('entity_type', $instance['entity_type'])
-    ->condition('bundle', $instance['bundle'])
-    ->execute();
-
-  // Clear the cache.
-  field_cache_clear();
-
-  // Mark instance data for deletion.
-  $field = field_info_field($instance['field_name']);
-  module_invoke($field['storage']['module'], 'field_storage_delete_instance', $instance);
-
-  // Let modules react to the deletion of the instance.
-  module_invoke_all('field_delete_instance', $instance);
-
-  // Delete the field itself if we just deleted its last instance.
-  if ($field_cleanup && count($field['bundles']) == 0) {
-    field_delete_field($field['field_name']);
-  }
+function field_delete_instance(FieldInstance $instance, $field_cleanup = TRUE) {
+  $instance->delete($field_cleanup);
 }
 
 /**
@@ -858,6 +525,13 @@ function field_purge_batch($batch_size) {
   $info = entity_get_info();
   foreach ($instances as $instance) {
     $entity_type = $instance['entity_type'];
+
+    // EntityFieldQuery currently fails on conditions on comment bundle.
+    // Remove when http://drupal.org/node/731724 is fixed.
+    if ($entity_type == 'comment') {
+      continue;
+    }
+
     $ids = (object) array(
       'entity_type' => $entity_type,
       'bundle' => $instance['bundle'],
@@ -866,7 +540,7 @@ function field_purge_batch($batch_size) {
     $field = field_info_field_by_id($instance['field_id']);
     // Retrieve some entities.
     $query = $factory->get($entity_type)
-      ->condition('id:' . $field['id'] . '.deleted', 1)
+      ->condition('id:' . $field['uuid'] . '.deleted', 1)
       ->range(0, $batch_size);
     // If there's no bundle key, all results will have the same bundle.
     if (!empty($info[$entity_type]['entity_keys']['bundle'])) {
@@ -884,7 +558,7 @@ function field_purge_batch($batch_size) {
         $ids->entity_id = $entity_id;
         $entities[$entity_id] = _field_create_entity_from_ids($ids);
       }
-      field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
+      field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1));
       foreach ($entities as $entity) {
         // Purge the data for the entity.
         field_purge_data($entity, $field, $instance);
@@ -897,9 +571,10 @@ function field_purge_batch($batch_size) {
   }
 
   // Retrieve all deleted fields. Any that have no instances can be purged.
-  $fields = field_read_fields(array('deleted' => 1), array('include_deleted' => 1));
-  foreach ($fields as $field) {
-    $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1));
+  $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
+  foreach ($deleted_fields as $field) {
+    $field = new Field($field);
+    $instances = field_read_instances(array('field_id' => $field['uuid']), array('include_deleted' => 1));
     if (empty($instances)) {
       field_purge_field($field);
     }
@@ -946,14 +621,15 @@ function field_purge_data(EntityInterface $entity, $field, $instance) {
  *   The instance record to purge.
  */
 function field_purge_instance($instance) {
-  db_delete('field_config_instance')
-    ->condition('id', $instance['id'])
-    ->execute();
-
   // Notify the storage engine.
   $field = field_info_field_by_id($instance['field_id']);
   module_invoke($field['storage']['module'], 'field_storage_purge_instance', $instance);
 
+  $state = Drupal::state();
+  $deleted_instances = $state->get('field.instance.deleted');
+  unset($deleted_instances[$instance['uuid']]);
+  $state->set('field.instance.deleted', $deleted_instances);
+
   // Clear the cache.
   field_info_cache_clear();
 
@@ -971,14 +647,15 @@ function field_purge_instance($instance) {
  *   The field record to purge.
  */
 function field_purge_field($field) {
-  $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1));
+  $instances = field_read_instances(array('field_id' => $field['uuid']), array('include_deleted' => 1));
   if (count($instances) > 0) {
     throw new FieldException(t('Attempt to purge a field @field_name that still has instances.', array('@field_name' => $field['field_name'])));
   }
 
-  db_delete('field_config')
-    ->condition('id', $field['id'])
-    ->execute();
+  $state = Drupal::state();
+  $deleted_fields = $state->get('field.field.deleted');
+  unset($deleted_fields[$field['uuid']]);
+  $state->set('field.field.deleted', $deleted_fields);
 
   // Notify the storage engine.
   module_invoke($field['storage']['module'], 'field_storage_purge_field', $field);
diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc
index b422b475813c..4ec8bfefc312 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -5,7 +5,7 @@
  * Field Info API, providing information about available fields and field types.
  */
 
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\field\FieldInfo;
 
 /**
diff --git a/core/modules/field/field.info.yml b/core/modules/field/field.info.yml
index 8cd31f14ad57..f5c4a7dace46 100644
--- a/core/modules/field/field.info.yml
+++ b/core/modules/field/field.info.yml
@@ -3,6 +3,4 @@ description: 'Field API to add fields to entities like nodes and users.'
 package: Core
 version: VERSION
 core: 8.x
-dependencies:
-  - field_sql_storage
 required: true
diff --git a/core/modules/field/field.install b/core/modules/field/field.install
index 695fd898043a..c1a137831a09 100644
--- a/core/modules/field/field.install
+++ b/core/modules/field/field.install
@@ -5,162 +5,13 @@
  * Install, update, and uninstall functions for the Field module.
  */
 
+use Drupal\Component\Uuid\Uuid;
+use Drupal\field\Plugin\Core\Entity\Field;
+
 /**
  * Implements hook_schema().
  */
 function field_schema() {
-  // Static (meta) tables.
-  $schema['field_config'] = array(
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'The primary identifier for a field',
-      ),
-      'field_name' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'description' => 'The name of this field. Non-deleted field names are unique, but multiple deleted fields can have the same name.',
-      ),
-      'type' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'description' => 'The type of this field.',
-      ),
-     'module' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The module that implements the field type.',
-      ),
-      'active' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Boolean indicating whether the module that implements the field type is enabled.',
-      ),
-      'storage_type' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'description' => 'The storage backend for the field.',
-      ),
-      'storage_module' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The module that implements the storage backend.',
-      ),
-      'storage_active' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Boolean indicating whether the module that implements the storage backend is enabled.',
-      ),
-      'locked' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => '@TODO',
-      ),
-      'data' => array(
-        'type' => 'blob',
-        'size' => 'big',
-        'not null' => TRUE,
-        'serialize' => TRUE,
-        'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.',
-      ),
-      'cardinality' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'translatable' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'deleted' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array('id'),
-    'indexes' => array(
-      'field_name' => array('field_name'),
-      // Used by field_read_fields().
-      'active' => array('active'),
-      'storage_active' => array('storage_active'),
-      'deleted' => array('deleted'),
-      // Used by field_sync_field_status().
-      'module' => array('module'),
-      'storage_module' => array('storage_module'),
-      'type' => array('type'),
-      'storage_type' => array('storage_type'),
-    ),
-  );
-  $schema['field_config_instance'] = array(
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'The primary identifier for a field instance',
-      ),
-      'field_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'description' => 'The identifier of the field attached by this instance',
-      ),
-      'field_name' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-      'entity_type'       => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-      'bundle' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => ''
-      ),
-      'data' => array(
-        'type' => 'blob',
-        'size' => 'big',
-        'not null' => TRUE,
-        'serialize' => TRUE,
-      ),
-      'deleted' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array('id'),
-    'indexes' => array(
-      // Used by field_delete_instance().
-      'field_name_bundle' => array('field_name', 'entity_type', 'bundle'),
-      // Used by field_read_instances().
-      'deleted' => array('deleted'),
-    ),
-  );
   $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
   $schema['cache_field']['description'] = 'Cache table for the Field module to store already built field informations.';
 
@@ -229,12 +80,16 @@ function _update_7000_field_create_field(&$field) {
     'deleted' => (int) $field['deleted'],
   );
   // We don't use drupal_write_record() here because it depends on the schema.
-  $field['id'] = db_insert('field_config')
+  $field_id = db_insert('field_config')
     ->fields($record)
     ->execute();
 
-  // Create storage for the field.
-  field_sql_storage_field_storage_create_field($field);
+  // Create storage for the field. This requires a field entity, but cannot use
+  // the regular entity_create() function here.
+  $field_entity = new Field($field);
+  field_sql_storage_field_storage_create_field($field_entity);
+
+  $field['id'] = $field_id;
 }
 
 /**
@@ -271,7 +126,6 @@ function _update_7000_field_delete_field($field_name) {
     ->execute();
 }
 
-
 /**
  * Deletes an instance and all its data of a field stored in SQL Storage.
  *
@@ -355,6 +209,8 @@ function _update_7000_field_create_instance($field, &$instance) {
     'field_id' => $field['id'],
     'field_name' => $field['field_name'],
     'deleted' => 0,
+    'description' => '',
+    'required' => FALSE,
   );
 
   // The serialized 'data' column contains everything from $instance that does
@@ -382,15 +238,25 @@ function _update_7000_field_create_instance($field, &$instance) {
  */
 
 /**
- * Reassign all list.module fields to be controlled by options.module.
+ * Implements hook_update_dependencies().
+ */
+function field_update_dependencies() {
+  // Convert Field API to ConfigEntities after:
+  $dependencies['field'][8003] = array(
+    // - Custom block bodies have been turned to fields.
+    'block' => 8008,
+    // - User pictures have been turned to fields.
+    'user' => 8011,
+    // - The {file_usage}.id column has moved to varchar.
+    'file' => 8001,
+  );
+  return $dependencies;
+}
+
+/**
+ * Empty update - moved into field_update_8003().
  */
 function field_update_8001() {
-  db_update('field_config')
-    ->fields(array(
-      'module' => 'options',
-    ))
-    ->condition('module', 'list')
-    ->execute();
 }
 
 /**
@@ -442,7 +308,7 @@ function field_update_8002() {
 
   // Migration of 'extra_fields' display settings. Avoid calling
   // entity_get_info() by fetching the relevant variables directly in the
-  // cariables table.
+  // variable table.
   $variables = array_map('unserialize', db_query("SELECT name, value FROM {variable} WHERE name LIKE '%field_bundle_settings_%'")->fetchAllKeyed());
   foreach ($variables as $variable_name => $variable_value) {
     if (preg_match('/field_bundle_settings_(.*)__(.*)/', $variable_name, $matches)) {
@@ -483,6 +349,137 @@ function field_update_8002() {
   update_config_manifest_add('entity.display', array_keys($displays));
 }
 
+/**
+ * Convert fields and instances to config.
+ */
+function field_update_8003() {
+  $uuid = new Uuid();
+  $manifest_ids = array('fields' => array(), 'instances' => array());
+
+  $state = Drupal::state();
+  $deleted_fields = $state->get('field.field.deleted') ?: array();
+  $deleted_instances = $state->get('field.instance.deleted') ?: array();
+
+  $field_uuids = array();
+
+  // Migrate field definitions.
+  $records = db_query("SELECT * FROM {field_config}")->fetchAll(PDO::FETCH_ASSOC);
+  foreach ($records as $record) {
+    $record['data'] = unserialize($record['data']);
+
+    $config = array(
+      'id' => $record['field_name'],
+      'uuid' => $uuid->generate(),
+      'type' => $record['type'],
+      'module' => $record['module'],
+      'active' => $record['active'],
+      'settings' => $record['data']['settings'],
+      'storage' => array(
+        'type' => $record['storage_type'],
+        'module' => $record['storage_module'],
+        'active' => $record['storage_active'],
+        'settings' => $record['data']['storage']['settings'],
+      ),
+      'locked' => $record['locked'],
+      'cardinality' => $record['cardinality'],
+      'translatable' => $record['translatable'],
+      'entity_types' => $record['data']['entity_types'],
+      'indexes' => $record['data']['indexes'] ?: array(),
+      'status' => 1,
+      'langcode' => 'und',
+    );
+
+    // Reassign all list.module fields to be controlled by options.module.
+    if ($config['module'] == 'list') {
+      $config['module'] = 'options';
+    }
+
+    // Save in either config or state.
+    if (!$record['deleted']) {
+      Drupal::config('field.field.' . $config['id'])
+        ->setData($config)
+        ->save();
+      $manifest_ids['fields'][] = $config['id'];
+    }
+    else {
+      $config['deleted'] = TRUE;
+      $deleted_fields[$config['uuid']] = $config;
+      // Additionally, rename the data tables for deleted fields. Technically
+      // this would belong in an update in field_sql_storage.module, but it is
+      // easier to do it now, when the old numeric ID is available.
+      if ($config['storage']['type'] == 'field_sql_storage') {
+        $field = new Field($config);
+        $tables = array(
+          "field_deleted_data_{$record['id']}" => _field_sql_storage_tablename($field),
+          "field_deleted_revision_{$record['id']}" => _field_sql_storage_revision_tablename($field),
+        );
+        foreach ($tables as $table_old => $table_new) {
+          if (db_table_exists($table_old)) {
+            db_rename_table($table_old, $table_new);
+          }
+        }
+      }
+    }
+
+    // Store the UUID with the old field_id so that we can map the instances.
+    $field_uuids[$record['id']] = $config['uuid'];
+  }
+
+  // Migrate instance definitions.
+  $records = db_query("SELECT * FROM {field_config_instance}")->fetchAll(PDO::FETCH_ASSOC);
+  foreach ($records as $record) {
+    $record['data'] = unserialize($record['data']);
+
+    $config = array(
+      'id' => $record['entity_type'] . '.' . $record['bundle'] . '.' . $record['field_name'],
+      'uuid' => $uuid->generate(),
+      'field_uuid' => $field_uuids[$record['field_id']],
+      'entity_type' => $record['entity_type'],
+      'bundle' => $record['bundle'],
+      'label' => $record['data']['label'],
+      'description' => $record['data']['description'],
+      'required' => $record['data']['required'],
+      'default_value' => isset($record['data']['default_value']) ? $record['data']['default_value'] : array(),
+      'default_value_function' => isset($record['data']['default_value_function']) ? $record['data']['default_value_function'] : '',
+      'settings' => $record['data']['settings'],
+      'widget' => $record['data']['widget'],
+      'status' => 1,
+      'langcode' => 'und',
+    );
+
+    // Save in either config or state.
+    if (!$record['deleted']) {
+      Drupal::config('field.instance.' . $config['id'])
+        ->setData($config)
+        ->save();
+      $manifest_ids['instances'][] = $config['id'];
+    }
+    else {
+      $config['deleted'] = TRUE;
+      $deleted_instances[$config['uuid']] = $config;
+    }
+
+    // Update {file_usage} table in case this instance has a default image.
+    if (!empty($config['settings']['default_image'])) {
+      db_update('file_usage')
+        ->fields(array('id' => $config['field_uuid']))
+        ->condition('type', 'default_image')
+        ->condition('module', 'image')
+        ->condition('id', $record['field_id'])
+        ->condition('fid', $config['settings']['default_image'])
+        ->execute();
+    }
+  }
+
+  // Create the manifest files.
+  update_config_manifest_add('field.field', $manifest_ids['fields']);
+  update_config_manifest_add('field.instance', $manifest_ids['instances']);
+
+  // Save the deleted fields and instances in state.
+  $state->set('field.field.deleted', $deleted_fields);
+  $state->set('field.instance.deleted', $deleted_instances);
+}
+
 /**
  * @} End of "addtogroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 8e16741b5104..1e314846c9cf 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -49,144 +49,6 @@
  * 'subtitle' and 'photo' fields because they are both attached to the 'node'
  * bundle 'article'.
  *
- * Field definitions are represented as an array of key/value pairs.
- *
- * array $field:
- * - id: (integer, read-only) The primary identifier of the field. It is
- *   assigned automatically by field_create_field().
- * - field_name: (string) The name of the field. Each field name is unique
- *   within Field API. When a field is attached to an entity, the field's data
- *   is stored in $entity->$field_name. Maximum length is 32 characters.
- * - type: (string) The type of the field, such as 'text' or 'image'. Field
- *   types are defined by modules that implement hook_field_info().
- * - entity_types: (array) The array of entity types that can hold instances of
- *   this field. If empty or not specified, the field can have instances in any
- *   entity type.
- * - cardinality: (integer) The number of values the field can hold. Legal
- *   values are any positive integer or FIELD_CARDINALITY_UNLIMITED.
- * - translatable: (integer) Whether the field is translatable.
- * - locked: (integer) Whether or not the field is available for editing. If
- *   TRUE, users can't change field settings or create new instances of the
- *   field in the UI. Defaults to FALSE.
- * - module: (string, read-only) The name of the module that implements the
- *   field type.
- * - active: (integer, read-only) TRUE if the module that implements the field
- *   type is currently enabled, FALSE otherwise.
- * - deleted: (integer, read-only) TRUE if this field has been deleted, FALSE
- *   otherwise. Deleted fields are ignored by the Field Attach API. This
- *   property exists because fields can be marked for deletion but only actually
- *   destroyed by a separate garbage-collection process.
- * - columns: (array, read-only) An array of the Field API columns used to store
- *   each value of this field. The column list may depend on field settings; it
- *   is not constant per field type. Field API column specifications are exactly
- *   like Schema API column specifications but, depending onthe field storage
- *   module in use, the name of the column may not represent an actual column in
- *   an SQL database.
- * - indexes: (array) An array of indexes on data columns, using the same
- *   definition format as Schema API index specifications. Only columns that
- *   appear in the 'columns' setting are allowed. Note that field types can
- *   specify default indexes, which can be modified or added to when creating a
- *   field.
- * - foreign keys: (optional) An associative array of relations, using the same
- *   structure as the 'foreign keys' definition of hook_schema(). Note,
- *   however, that the field data is not necessarily stored in SQL. Also, the
- *   possible usage is limited, as you cannot specify another field as
- *   related, only existing SQL tables, such as filter formats.
- * - settings: (array) A sub-array of key/value pairs of field-type-specific
- *   settings. Each field type module defines and documents its own field
- *   settings.
- * - storage: (array) A sub-array of key/value pairs identifying the storage
- *   backend to use for the for the field.
- *   - type: (string) The storage backend used by the field. Storage backends
- *     are defined by modules that implement hook_field_storage_info().
- *   - module: (string, read-only) The name of the module that implements the
- *     storage backend.
- *   - active: (integer, read-only) TRUE if the module that implements the
- *     storage backend is currently enabled, FALSE otherwise.
- *   - settings: (array) A sub-array of key/value pairs of settings. Each
- *     storage backend defines and documents its own settings.
- *
- * Field instance definitions are represented as an array of key/value pairs.
- *
- * array $instance:
- * - id: (integer, read-only) The primary identifier of this field instance. It
- *   is assigned automatically by field_create_instance().
- * - field_id: (integer, read-only) The foreign key of the field attached to the
- *   bundle by this instance. It is populated automatically by
- *   field_create_instance().
- * - field_name: (string) The name of the field attached to the bundle by this
- *   instance.
- * - entity_type: (string) The name of the entity type the instance is attached
- *   to.
- * - bundle: (string) The name of the bundle that the field is attached to.
- * - label: (string) A human-readable label for the field when used with this
- *   bundle. For example, the label will be the title of Form API elements for
- *   this instance.
- * - description: (string)A human-readable description for the field when used
- *   with this bundle. For example, the description will be the help text of
- *   Form API elements for this instance.
- * - required: (integer) TRUE if a value for this field is required when used
- *   with this bundle, FALSE otherwise. Currently, required-ness is only
- *   enforced during Form API operations, not by field_attach_load(),
- *   field_attach_insert(), or field_attach_update().
- * - default_value_function: (string) The name of the function, if any, that
- *   will provide a default value.
- * - default_value: (array) If default_value_function is not set, then fixed
- *   values can be provided.
- * - deleted: (integer, read-only) TRUE if this instance has been deleted, FALSE
- *   otherwise. Deleted instances are ignored by the Field Attach API. This
- *   property exists because instances can be marked for deletion but only
- *   actually destroyed by a separate garbage-collection process.
- * - settings: (array) A sub-array of key/value pairs of field-type-specific
- *   instance settings. Each field type module defines and documents its own
- *   instance settings.
- * - widget: (array) A sub-array of key/value pairs identifying the Form API
- *   input widget for the field when used by this bundle.
- *   - type: (string) The type of the widget, such as text_textfield. Widget
- *     types are defined by modules that implement hook_field_widget_info().
- *   - settings: (array) A sub-array of key/value pairs of widget-type-specific
- *     settings. Each field widget type module defines and documents its own
- *     widget settings.
- *   - weight: (float) The weight of the widget relative to the other elements
- *     in entity edit forms.
- *   - module: (string, read-only) The name of the module that implements the
- *     widget type.
- * - display: (array) A sub-array of key/value pairs identifying the way field
- *   values should be displayed in each of the entity type's view modes, plus
- *   the 'default' mode. For each view mode, Field UI lets site administrators
- *   define whether they want to use a dedicated set of display options or the
- *   'default' options to reduce the number of displays to maintain as they add
- *   new fields. For nodes, on a fresh install, only the 'teaser' view mode is
- *   configured to use custom display options, all other view modes defined use
- *   the 'default' options by default. When programmatically adding field
- *   instances on nodes, it is therefore recommended to at least specify display
- *   options for 'default' and 'teaser'.
- *   - default: (array) A sub-array of key/value pairs describing the display
- *     options to be used when the field is being displayed in view modes that
- *     are not configured to use dedicated display options.
- *     - label: (string) Position of the label. 'inline', 'above' and 'hidden'
- *       are the values recognized by the default 'field' theme implementation.
- *     - type: (string) The type of the display formatter, or 'hidden' for no
- *       display.
- *     - settings: (array) A sub-array of key/value pairs of display options
- *       specific to the formatter.
- *       - weight: (float) The weight of the field relative to the other entity
- *         components displayed in this view mode.
- *       - module: (string, read-only) The name of the module which implements
- *         the display formatter.
- *   - some_mode: A sub-array of key/value pairs describing the display options
- *     to be used when the field is being displayed in the 'some_mode' view
- *     mode. Those options will only be actually applied at run time if the view
- *     mode is not configured to use default settings for this bundle.
- *     - ...
- *   - other_mode:
- *     - ...
- *
- * The (default) render arrays produced for field instances are documented at
- * field_attach_view().
- *
- * Bundles are represented by two strings, an entity type and a bundle name.
- *
  * - @link field_types Field Types API @endlink: Defines field types, widget
  *   types, and display formatters. Field modules use this API to provide field
  *   types like Text and Node Reference along with the associated form elements
@@ -334,7 +196,8 @@ function field_cron() {
  * required if there are any active fields of that type.
  */
 function field_system_info_alter(&$info, $file, $type) {
-  if ($type == 'module' && module_hook($file->name, 'field_info')) {
+  // It is not safe to call field_read_fields() during maintenance mode.
+  if ($type == 'module' && module_hook($file->name, 'field_info') && !defined('MAINTENANCE_MODE')) {
     $fields = field_read_fields(array('module' => $file->name), array('include_deleted' => TRUE));
     if ($fields) {
       $info['required'] = TRUE;
@@ -530,48 +393,81 @@ function field_modules_disabled($modules) {
 }
 
 /**
- * Refreshes the 'active' and 'storage_active' columns for fields.
+ * Refreshes the 'active' and 'storage[active]' values for fields.
  */
 function field_sync_field_status() {
-  // Refresh the 'active' and 'storage_active' columns according to the current
-  // set of enabled modules.
-  $modules = array_keys(drupal_container()->get('module_handler')->getModuleList());
-  foreach ($modules as $module_name) {
-    field_associate_fields($module_name);
+  $module_handler = Drupal::moduleHandler();
+  $state = Drupal::state();
+
+  // Get both deleted and non-deleted field definitions.
+  $fields = array();
+  foreach (config_get_storage_names_with_prefix('field.field') as $name) {
+    $field = Drupal::config($name)->get();
+    $fields[$field['uuid']] = $field;
   }
-  db_update('field_config')
-    ->fields(array('active' => 0))
-    ->condition('module', $modules, 'NOT IN')
-    ->execute();
-  db_update('field_config')
-    ->fields(array('storage_active' => 0))
-    ->condition('storage_module', $modules, 'NOT IN')
-    ->execute();
-}
+  $deleted_fields = $state->get('field.field.deleted') ?: array();
+  $fields += $deleted_fields;
 
-/**
- * Allows a module to update the database for fields and columns it controls.
- *
- * @param $module
- *   The name of the module to update on.
- */
-function field_associate_fields($module) {
-  // Associate field types.
-  $field_types = (array) module_invoke($module, 'field_info');
-  if ($field_types) {
-    db_update('field_config')
-      ->fields(array('module' => $module, 'active' => 1))
-      ->condition('type', array_keys($field_types))
-      ->execute();
+  if (empty($fields)) {
+    return;
   }
-  // Associate storage backends.
-  $storage_types = (array) module_invoke($module, 'field_storage_info');
-  if ($storage_types) {
-    db_update('field_config')
-      ->fields(array('storage_module' => $module, 'storage_active' => 1))
-      ->condition('storage_type', array_keys($storage_types))
-      ->execute();
+
+  // Set the 'module' and 'active' values for the current set of enabled
+  // modules.
+  $changed = array();
+  $modules = $module_handler->getModuleList();
+  foreach ($modules as $module => $module_info) {
+    // Collect field types and storage backends exposed by the module.
+    $field_types = (array) $module_handler->invoke($module, 'field_info');
+    $storage_types = (array) $module_handler->invoke($module, 'field_storage_info');
+
+    if ($field_types || $storage_types) {
+      foreach ($fields as $uuid => &$field) {
+        // Associate field types.
+        if (isset($field_types[$field['type']]) && ($field['module'] !== $module || !$field['active'])) {
+          $field['module'] = $module;
+          $field['active'] = TRUE;
+          $changed[$uuid] = $field;
+        }
+        // Associate storage backends.
+        if (isset($storage_types[$field['storage']['type']]) && ($field['storage']['module'] !== $module || !$field['storage']['active'])) {
+          $field['storage']['module'] = $module;
+          $field['storage']['active'] = TRUE;
+          $changed[$uuid] = $field;
+        }
+      }
+    }
   }
+
+  // Set fields with missing field type or storage modules to inactive.
+  foreach ($fields as $uuid => &$field) {
+    if (!isset($modules[$field['module']]) && $field['active']) {
+      $field['active'] = FALSE;
+      $changed[$uuid] = $field;
+    }
+    if (!isset($modules[$field['storage']['module']]) && $field['storage']['active']) {
+      $field['storage']['active'] = FALSE;
+      $changed[$uuid] = $field;
+    }
+  }
+
+  // Store the updated field definitions.
+  foreach ($changed as $uuid => $field) {
+    if (!empty($field['deleted'])) {
+      $deleted_fields[$uuid] = $field;
+    }
+    else {
+      Drupal::config('field.field.' . $field['id'])
+        ->set('module', $field['module'])
+        ->set('active', $field['active'])
+        ->set('storage.module', $field['storage']['module'])
+        ->set('storage.active', $field['storage']['active'])
+        ->save();
+    }
+  }
+  $state->set('field.field.deleted', $deleted_fields);
+
+  field_cache_clear();
 }
 
 /**
@@ -1011,7 +907,6 @@ function field_get_items(EntityInterface $entity, $field_name, $langcode = NULL)
  *   TRUE if the field has data for any entity; FALSE otherwise.
  */
 function field_has_data($field) {
-  $field = field_info_field_by_id($field['id']);
   $columns = array_keys($field['columns']);
   $factory = Drupal::service('entity.query');
   foreach ($field['bundles'] as $entity_type => $bundle) {
diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc
index d0458896b54b..b01e8821279a 100644
--- a/core/modules/field/field.views.inc
+++ b/core/modules/field/field.views.inc
@@ -72,7 +72,7 @@ function field_views_field_label($field_name) {
   foreach ($instances as $entity_name => $entity_type) {
     foreach ($entity_type as $bundle) {
       if (isset($bundle[$field_name])) {
-        $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1;
+        $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]->label] : 1;
         $all_labels[$entity_name][$bundle[$field_name]['label']] = TRUE;
       }
     }
@@ -296,10 +296,10 @@ function field_views_field_default_views_data($field) {
       else {
         $group = t('@group (historical data)', array('@group' => $group_name));
       }
-      $column_real_name = $field['storage']['details']['sql'][$type][$table][$column];
+      $column_real_name = $field['storage_details']['sql'][$type][$table][$column];
 
       // Load all the fields from the table by default.
-      $additional_fields = array_values($field['storage']['details']['sql'][$type][$table]);
+      $additional_fields = array_values($field['storage_details']['sql'][$type][$table]);
 
       $data[$table][$column_real_name] = array(
         'group' => $group,
diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php
index 7ff685001990..9b79960b3a45 100644
--- a/core/modules/field/lib/Drupal/field/FieldInfo.php
+++ b/core/modules/field/lib/Drupal/field/FieldInfo.php
@@ -142,17 +142,24 @@ public function getFieldMap() {
 
     $map = array();
 
-    $query = db_select('field_config_instance', 'fci');
-    $query->join('field_config', 'fc', 'fc.id = fci.field_id');
-    $query->fields('fc', array('type'));
-    $query->fields('fci', array('field_name', 'entity_type', 'bundle'))
-      ->condition('fc.active', 1)
-      ->condition('fc.storage_active', 1)
-      ->condition('fc.deleted', 0)
-      ->condition('fci.deleted', 0);
-    foreach ($query->execute() as $row) {
-      $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
-      $map[$row->field_name]['type'] = $row->type;
+    // Get active fields.
+    foreach (config_get_storage_names_with_prefix('field.field') as $config_id) {
+      $field_config = \Drupal::config($config_id)->get();
+      if ($field_config['active'] && $field_config['storage']['active']) {
+        $fields[$field_config['uuid']] = $field_config;
+      }
+    }
+    // Get field instances.
+    foreach (config_get_storage_names_with_prefix('field.instance') as $config_id) {
+      $instance_config = \Drupal::config($config_id)->get();
+      $field_uuid = $instance_config['field_uuid'];
+      // Filter out instances of inactive fields, and instances on unknown
+      // entity types.
+      if (isset($fields[$field_uuid])) {
+        $field = $fields[$field_uuid];
+        $map[$field['id']]['bundles'][$instance_config['entity_type']][] = $instance_config['bundle'];
+        $map[$field['id']]['type'] = $field['type'];
+      }
     }
 
     // Save in "static" and persistent caches.
@@ -181,7 +188,7 @@ public function getFields() {
     else {
       // Collect and prepare fields.
       foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
-        $this->fieldsById[$field['id']] = $this->prepareField($field);
+        $this->fieldsById[$field['uuid']] = $this->prepareField($field);
       }
 
       // Store in persistent cache.
@@ -191,7 +198,7 @@ public function getFields() {
     // Fill the name/ID map.
     foreach ($this->fieldsById as $field) {
       if (!$field['deleted']) {
-        $this->fieldIdsByName[$field['field_name']] = $field['id'];
+        $this->fieldIdsByName[$field['id']] = $field['uuid'];
       }
     }
 
@@ -229,7 +236,7 @@ public function getInstances($entity_type = NULL) {
         foreach (field_read_instances() as $instance) {
           $field = $this->getField($instance['field_name']);
           $instance = $this->prepareInstance($instance, $field['type']);
-          $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = new FieldInstance($instance);
+          $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
         }
 
         // Store in persistent cache.
@@ -275,8 +282,8 @@ public function getField($field_name) {
       $field = $this->prepareField($field);
 
       // Save in the "static" cache.
-      $this->fieldsById[$field['id']] = $field;
-      $this->fieldIdsByName[$field['field_name']] = $field['id'];
+      $this->fieldsById[$field['uuid']] = $field;
+      $this->fieldIdsByName[$field['field_name']] = $field['uuid'];
 
       return $field;
     }
@@ -309,14 +316,14 @@ public function getFieldById($field_id) {
     // bundle.
 
     // Cache miss: read from definition.
-    if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
+    if ($fields = field_read_fields(array('uuid' => $field_id), array('include_deleted' => TRUE))) {
       $field = current($fields);
       $field = $this->prepareField($field);
 
       // Store in the static cache.
-      $this->fieldsById[$field['id']] = $field;
+      $this->fieldsById[$field['uuid']] = $field;
       if (!$field['deleted']) {
-        $this->fieldIdsByName[$field['field_name']] = $field['id'];
+        $this->fieldIdsByName[$field['field_name']] = $field['uuid'];
       }
 
       return $field;
@@ -355,10 +362,10 @@ public function getBundleInstances($entity_type, $bundle) {
 
       // Extract the field definitions and save them in the "static" cache.
       foreach ($info['fields'] as $field) {
-        if (!isset($this->fieldsById[$field['id']])) {
-          $this->fieldsById[$field['id']] = $field;
+        if (!isset($this->fieldsById[$field['uuid']])) {
+          $this->fieldsById[$field['uuid']] = $field;
           if (!$field['deleted']) {
-            $this->fieldIdsByName[$field['field_name']] = $field['id'];
+            $this->fieldIdsByName[$field['field_name']] = $field['uuid'];
           }
         }
       }
@@ -377,27 +384,40 @@ public function getBundleInstances($entity_type, $bundle) {
     }
 
     // Cache miss: collect from the definitions.
-
     $instances = array();
 
-    // Collect the fields in the bundle.
-    $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
-    $fields = field_read_fields($params);
+    // Do not return anything for unknown entity types.
+    if (entity_get_info($entity_type)) {
 
-    // This iterates on non-deleted instances, so deleted fields are kept out of
-    // the persistent caches.
-    foreach (field_read_instances($params) as $instance) {
-      $field = $fields[$instance['field_name']];
+      // Collect names of fields and instances involved in the bundle, using the
+      // field map. The field map is already filtered to active, non-deleted
+      // fields and instances, so those are kept out of the persistent caches.
+      $config_ids = array();
+      foreach ($this->getFieldMap() as $field_name => $field_data) {
+        if (isset($field_data['bundles'][$entity_type]) && in_array($bundle, $field_data['bundles'][$entity_type])) {
+          $config_ids[$field_name] = "$entity_type.$bundle.$field_name";
+        }
+      }
 
-      $instance = $this->prepareInstance($instance, $field['type']);
-      $instances[$field['field_name']] = new FieldInstance($instance);
+      // Load and prepare the corresponding fields and instances entities.
+      if ($config_ids) {
+        $loaded_fields = entity_load_multiple('field_entity', array_keys($config_ids));
+        $loaded_instances = entity_load_multiple('field_instance', array_values($config_ids));
 
-      // If the field is not in our global "static" list yet, add it.
-      if (!isset($this->fieldsById[$field['id']])) {
-        $field = $this->prepareField($field);
+        foreach ($loaded_instances as $instance) {
+          $field = $loaded_fields[$instance['field_name']];
+
+          $instance = $this->prepareInstance($instance, $field['type']);
+          $instances[$field['field_name']] = $instance;
 
-        $this->fieldsById[$field['id']] = $field;
-        $this->fieldIdsByName[$field['field_name']] = $field['id'];
+          // If the field is not in our global "static" list yet, add it.
+          if (!isset($this->fieldsById[$field['uuid']])) {
+            $field = $this->prepareField($field);
+
+            $this->fieldsById[$field['uuid']] = $field;
+            $this->fieldIdsByName[$field['field_name']] = $field['uuid'];
+          }
+        }
       }
     }
 
@@ -480,20 +500,6 @@ public function prepareField($field) {
     $field['settings'] += field_info_field_settings($field['type']);
     $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
 
-    // Add storage details.
-    $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
-    drupal_alter('field_storage_details', $details, $field);
-    $field['storage']['details'] = $details;
-
-    // Populate the list of bundles using the field.
-    $field['bundles'] = array();
-    if (!$field['deleted']) {
-      $map = $this->getFieldMap();
-      if (isset($map[$field['field_name']])) {
-        $field['bundles'] = $map[$field['field_name']]['bundles'];
-      }
-    }
-
     return $field;
   }
 
@@ -553,4 +559,5 @@ public function prepareExtraFields($extra_fields, $entity_type, $bundle) {
 
     return $result;
   }
+
 }
diff --git a/core/modules/field/lib/Drupal/field/FieldInstance.php b/core/modules/field/lib/Drupal/field/FieldInstance.php
deleted file mode 100644
index ba6ea44b3a0a..000000000000
--- a/core/modules/field/lib/Drupal/field/FieldInstance.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\field\FieldInstance.
- */
-
-namespace Drupal\field;
-
-/**
- * Class for field instance objects.
- */
-class FieldInstance implements \ArrayAccess {
-
-  /**
-   * The instance definition, as read from configuration storage.
-   *
-   * @var array
-   */
-  public $definition;
-
-  /**
-   * The widget object used for this instance.
-   *
-   * @var Drupal\field\Plugin\Type\Widget\WidgetInterface
-   */
-  protected $widget;
-
-  /**
-   * Constructs a FieldInstance object.
-   *
-   * @param array $definition
-   *   The instance definition array, as read from configuration storage.
-   */
-  public function __construct(array $definition) {
-    $this->definition = $definition;
-  }
-
-  /**
-   * Returns the Widget plugin for the instance.
-   *
-   * @return Drupal\field\Plugin\Type\Widget\WidgetInterface
-   *   The Widget plugin to be used for the instance.
-   */
-  public function getWidget() {
-    if (empty($this->widget)) {
-      $widget_properties = $this->definition['widget'];
-
-      // Let modules alter the widget properties.
-      $context = array(
-        'entity_type' => $this->definition['entity_type'],
-        'bundle' => $this->definition['bundle'],
-        'field' => field_info_field($this->definition['field_name']),
-        'instance' => $this,
-      );
-      drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $this->definition['entity_type']), $widget_properties, $context);
-
-      $options = array(
-        'instance' => $this,
-        'type' => $widget_properties['type'],
-        'settings' => $widget_properties['settings'],
-        'weight' => $widget_properties['weight'],
-      );
-      $this->widget = drupal_container()->get('plugin.manager.field.widget')->getInstance($options);
-    }
-
-    return $this->widget;
-  }
-
-  /**
-   * Implements ArrayAccess::offsetExists().
-   */
-  public function offsetExists($offset) {
-    return isset($this->definition[$offset]) || array_key_exists($offset, $this->definition);
-  }
-
-  /**
-   * Implements ArrayAccess::offsetGet().
-   */
-  public function &offsetGet($offset) {
-    return $this->definition[$offset];
-  }
-
-  /**
-   * Implements ArrayAccess::offsetSet().
-   */
-  public function offsetSet($offset, $value) {
-    if (!isset($offset)) {
-      // Do nothing; $array[] syntax is not supported by this temporary wrapper.
-      return;
-    }
-    $this->definition[$offset] = $value;
-
-    // If the widget or formatter properties changed, the corrsponding plugins
-    // need to be re-instanciated.
-    if ($offset == 'widget') {
-      unset($this->widget);
-    }
-  }
-
-  /**
-   * Implements ArrayAccess::offsetUnset().
-   */
-  public function offsetUnset($offset) {
-    unset($this->definition[$offset]);
-
-    // If the widget or formatter properties changed, the corrsponding plugins
-    // need to be re-instanciated.
-    if ($offset == 'widget') {
-      unset($this->widget);
-    }
-  }
-
-  /**
-   * Returns the instance definition as a regular array.
-   *
-   * This is used as a temporary BC layer.
-   * @todo Remove once the external APIs have been converted to use
-   *   FieldInstance objects.
-   *
-   * @return array
-   *   The instance definition as a regular array.
-   */
-  public function getArray() {
-    return $this->definition;
-  }
-
-  /**
-   * Handles serialization of Drupal\field\FieldInstance objects.
-   */
-  public function __sleep() {
-    return array('definition');
-  }
-
-}
diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
new file mode 100644
index 000000000000..1af555ec5146
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\FieldInstanceStorageController.
+ */
+
+namespace Drupal\field;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\Entity\ConfigStorageController;
+
+/**
+ * Controller class for field instances.
+ *
+ * Note: the class take no special care about importing instances after their
+ * field in importCreate(), since this is guaranteed by the alphabetical order
+ * (field.field.* entries are processed before field.instance.* entries).
+ * @todo Revisit after http://drupal.org/node/1944368.
+ */
+class FieldInstanceStorageController extends ConfigStorageController {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function importDelete($name, Config $new_config, Config $old_config) {
+    // If the field has been deleted in the same import, the instance will be
+    // deleted by then, and there is nothing left to do. Just return TRUE so
+    // that the file does not get written to active store.
+    if (!$old_config->get()) {
+      return TRUE;
+    }
+    return parent::importDelete($name, $new_config, $old_config);
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
new file mode 100644
index 000000000000..e8ff98e670d3
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
@@ -0,0 +1,600 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Plugin\Core\Entity\Field.
+ */
+
+namespace Drupal\field\Plugin\Core\Entity;
+
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\field\FieldException;
+
+/**
+ * Defines the Field entity.
+ *
+ * @todo use 'field' as the id once hook_field_load() and friends
+ * are removed.
+ *
+ * @Plugin(
+ *   id = "field_entity",
+ *   label = @Translation("Field"),
+ *   module = "field",
+ *   controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   config_prefix = "field.field",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "id",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class Field extends ConfigEntityBase implements \ArrayAccess {
+
+  /**
+   * The maximum length of the field ID (machine name), in characters.
+   *
+   * For fields created through Field UI, this includes the 'field_' prefix.
+   */
+  const ID_MAX_LENGTH = 32;
+
+  /**
+   * The field ID (machine name).
+   *
+   * This is the name of the property under which the field values are placed in
+   * an entity : $entity-{>$field_id}. The maximum length is
+   * Field:ID_MAX_LENGTH.
+   *
+   * Example: body, field_main_image.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The field UUID.
+   *
+   * This is assigned automatically when the field is created.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The field type.
+   *
+   * Field types are defined by modules that implement hook_field_info().
+   *
+   * Example: text, number_integer.
+   *
+   * @var string
+   */
+  public $type;
+
+  /**
+   * The name of the module that provides the field type.
+   *
+   * @var string
+   */
+  public $module;
+
+  /**
+   * Flag indicating whether the field type module is enabled.
+   *
+   * @var bool
+   */
+  public $active;
+
+  /**
+   * Field-type specific settings.
+   *
+   * An array of key/value pairs, The keys and default values are defined by the
+   * field type in the 'settings' entry of hook_field_info().
+   *
+   * @var array
+   */
+  public $settings;
+
+  /**
+   * The field cardinality.
+   *
+   * The maximum number of values the field can hold. Possible values are
+   * positive integers or FIELD_CARDINALITY_UNLIMITED. Defaults to 1.
+   *
+   * @var integer
+   */
+  public $cardinality;
+
+  /**
+   * Flag indicating whether the field is translatable.
+   *
+   * Defaults to FALSE.
+   *
+   * @var bool
+   */
+  public $translatable;
+
+  /**
+   * The entity types on which the field is allowed to have instances.
+   *
+   * If empty or not specified, the field is allowed to have instances in any
+   * entity type.
+   *
+   * @var array
+   */
+  public $entity_types;
+
+  /**
+   * Flag indicating whether the field is available for editing.
+   *
+   * If TRUE, some actions not available though the UI (but are still possible
+   * through direct API manipulation):
+   * - field settings cannot be changed,
+   * - new instances cannot be created
+   * - existing instances cannot be deleted.
+   * Defaults to FALSE.
+   *
+   * @var bool
+   */
+  public $locked;
+
+  /**
+   * The field storage definition.
+   *
+   * An array of key/value pairs identifying the storage backend to use for the
+   * field:
+   * - type: (string) The storage backend used by the field. Storage backends
+   *   are defined by modules that implement hook_field_storage_info().
+   * - settings: (array) A sub-array of key/value pairs of settings. The keys
+   *   and default values are defined by the storage backend in the 'settings'
+   *   entry of hook_field_storage_info().
+   * - module: (string, read-only) The name of the module that implements the
+   *   storage backend.
+   * - active: (integer, read-only) TRUE if the module that implements the
+   *   storage backend is currently enabled, FALSE otherwise.
+   *
+   * @var array
+   */
+  public $storage;
+
+  /**
+   * The custom storage indexes for the field data storage.
+   *
+   * This set of indexes is merged with the "default" indexes specified by the
+   * field type in hook_field_schema() to determine the actual set of indexes
+   * that get created.
+   *
+   * The indexes are defined using the same definition format as Schema API
+   * index specifications. Only columns that are part of the field schema, as
+   * defined by the field type in hook_field_schema(), are allowed.
+   *
+   * Some storage backends might not support indexes, and discard that
+   * information.
+   *
+   * @var array
+   */
+  public $indexes;
+
+  /**
+   * Flag indicating whether the field is deleted.
+   *
+   * The delete() method marks the field as "deleted" and removes the
+   * corresponding entry from the config storage, but keeps its definition in
+   * the state storage while field data is purged by a separate
+   * garbage-collection process.
+   *
+   * Deleted fields stay out of the regular entity lifecycle (notably, their
+   * values are not populated in loaded entities, and are not saved back).
+   *
+   * @var bool
+   */
+  public $deleted;
+
+  /**
+   * The field schema.
+   *
+   * @var array
+   */
+  protected $schema;
+
+  /**
+   * The storage information for the field.
+   *
+   * @var array
+   */
+  protected $storageDetails;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $values, $entity_type = 'field_entity') {
+    // Check required properties.
+    if (empty($values['type'])) {
+      throw new FieldException('Attempt to create a field with no type.');
+    }
+    // Temporary BC layer: accept both 'id' and 'field_name'.
+    // @todo $field_name and the handling for it will be removed in
+    //   http://drupal.org/node/1953408.
+    if (empty($values['field_name']) && empty($values['id'])) {
+      throw new FieldException('Attempt to create an unnamed field.');
+    }
+    if (empty($values['id'])) {
+      $values['id'] = $values['field_name'];
+      unset($values['field_name']);
+    }
+    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['id'])) {
+      throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
+    }
+
+    // Provide defaults.
+    $values += array(
+      'settings' => array(),
+      'cardinality' => 1,
+      'translatable' => FALSE,
+      'entity_types' => array(),
+      'locked' => FALSE,
+      'deleted' => 0,
+      'storage' => array(),
+      'indexes' => array(),
+    );
+    parent::__construct($values, $entity_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportProperties() {
+    $names = array(
+      'id',
+      'uuid',
+      'status',
+      'langcode',
+      'type',
+      'settings',
+      'module',
+      'active',
+      'entity_types',
+      'storage',
+      'locked',
+      'cardinality',
+      'translatable',
+      'indexes',
+    );
+    $properties = array();
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    $module_handler = \Drupal::moduleHandler();
+    $storage_controller = \Drupal::service('plugin.manager.entity')->getStorageController($this->entityType);
+
+    // Clear the derived data about the field.
+    unset($this->schema, $this->storageDetails);
+
+    if ($this->isNew()) {
+      // Field name cannot be longer than Field::ID_MAX_LENGTH characters. We
+      // use drupal_strlen() because the DB layer assumes that column widths
+      // are given in characters rather than bytes.
+      if (drupal_strlen($this->id) > static::ID_MAX_LENGTH) {
+        throw new FieldException(format_string(
+          'Attempt to create a field with an ID longer than @max characters: %id', array(
+            '@max' => static::ID_MAX_LENGTH,
+            '%id' => $this->id,
+          )
+        ));
+      }
+
+      // Ensure the field name is unique (we do not care about deleted fields).
+      if ($prior_field = current($storage_controller->load(array($this->id)))) {
+        $message = $prior_field->active ?
+          'Attempt to create field name %id which already exists and is active.' :
+          'Attempt to create field name %id which already exists, although it is inactive.';
+        throw new FieldException(format_string($message, array('%id' => $this->id)));
+      }
+
+      // Disallow reserved field names. This can't prevent all field name
+      // collisions with existing entity properties, but some is better than
+      // none.
+      foreach (\Drupal::service('plugin.manager.entity')->getDefinitions() as $type => $info) {
+        if (in_array($this->id, $info['entity_keys'])) {
+          throw new FieldException(format_string('Attempt to create field %id which is reserved by entity type %type.', array('%id' => $this->id, '%type' => $type)));
+        }
+      }
+
+      // Check that the field type is known.
+      $field_type = field_info_field_types($this->type);
+      if (!$field_type) {
+        throw new FieldException(format_string('Attempt to create a field of unknown type %type.', array('%type' => $this->type)));
+      }
+      $this->module = $field_type['module'];
+      $this->active = 1;
+
+      // Make sure all settings are present, so that a complete field
+      // definition is passed to the various hooks and written to config.
+      $this->settings += $field_type['settings'];
+
+      // Provide default storage.
+      $this->storage += array(
+        'type' => variable_get('field_storage_default', 'field_sql_storage'),
+        'settings' => array(),
+      );
+      // Check that the storage type is known.
+      $storage_type = field_info_storage_types($this->storage['type']);
+      if (!$storage_type) {
+        throw new FieldException(format_string('Attempt to create a field with unknown storage type %type.', array('%type' => $this->storage['type'])));
+      }
+      $this->storage['module'] = $storage_type['module'];
+      $this->storage['active'] = 1;
+      // Provide default storage settings.
+      $this->storage['settings'] += $storage_type['settings'];
+
+      // Invoke the storage backend's hook_field_storage_create_field().
+      $module_handler->invoke($this->storage['module'], 'field_storage_create_field', array($this));
+
+      $hook = 'field_create_field';
+      $hook_args = array($this);
+    }
+    // Otherwise, the field is being updated.
+    else {
+      $original = $storage_controller->loadUnchanged($this->id());
+
+      // Some updates are always disallowed.
+      if ($this->type != $original->type) {
+        throw new FieldException("Cannot change an existing field's type.");
+      }
+      if ($this->entity_types != $original->entity_types) {
+        throw new FieldException("Cannot change an existing field's entity_types property.");
+      }
+      if ($this->storage['type'] != $original->storage['type']) {
+        throw new FieldException("Cannot change an existing field's storage type.");
+      }
+
+      // Make sure all settings are present, so that a complete field
+      // definition is saved. This allows calling code to perform partial
+      // updates on a field object.
+      $this->settings += $original->settings;
+
+      $has_data = field_has_data($this);
+
+      // See if any module forbids the update by throwing an exception. This
+      // invokes hook_field_update_forbid().
+      $module_handler->invokeAll('field_update_forbid', array($this, $original, $has_data));
+
+      // Tell the storage engine to update the field by invoking the
+      // hook_field_storage_update_field(). The storage engine can reject the
+      // definition update as invalid by raising an exception, which stops
+      // execution before the definition is written to config.
+      $module_handler->invoke($this->storage['module'], 'field_storage_update_field', array($this, $original, $has_data));
+
+      $hook = 'field_update_field';
+      $hook_args = array($this, $original, $has_data);
+    }
+
+    // Save the configuration.
+    $result = parent::save();
+    field_cache_clear();
+
+    // Invoke external hooks after the cache is cleared for API consistency.
+    // This invokes either hook_field_create_field() or
+    // hook_field_update_field() depending on whether the field is new.
+    $module_handler->invokeAll($hook, $hook_args);
+
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete() {
+    if (!$this->deleted) {
+      $module_handler = \Drupal::moduleHandler();
+      $instance_controller = \Drupal::service('plugin.manager.entity')->getStorageController('field_instance');
+      $state = \Drupal::state();
+
+      // Delete all non-deleted instances.
+      $instance_ids = array();
+      foreach ($this->getBundles() as $entity_type => $bundles) {
+        foreach ($bundles as $bundle) {
+          $instance_ids[] = "$entity_type.$bundle.$this->id";
+        }
+      }
+      foreach ($instance_controller->load($instance_ids) as $instance) {
+        // By default, FieldInstance::delete() will automatically try to delete
+        // a field definition when it is deleting the last instance of the
+        // field. Since the whole field is being deleted here, pass FALSE as
+        // the $field_cleanup parameter to prevent a loop.
+        $instance->delete(FALSE);
+      }
+
+      // Mark field data for deletion by invoking
+      // hook_field_storage_delete_field().
+      $module_handler->invoke($this->storage['module'], 'field_storage_delete_field', array($this));
+
+      // Delete the configuration of this field and save the field configuration
+      // in the key_value table so we can use it later during
+      // field_purge_batch(). This makes sure a new field can be created
+      // immediately with the same name.
+      $deleted_fields = $state->get('field.field.deleted') ?: array();
+      $config = $this->getExportProperties();
+      $config['deleted'] = TRUE;
+      $deleted_fields[$this->uuid] = $config;
+      $state->set('field.field.deleted', $deleted_fields);
+
+      parent::delete();
+
+      // Clear the cache.
+      field_cache_clear();
+
+      // Invoke hook_field_delete_field().
+      $module_handler->invokeAll('field_delete_field', array($this));
+    }
+  }
+
+  /**
+   * Returns the field schema.
+   *
+   * @return array
+   *   The field schema, as an array of key/value pairs in the format returned
+   *   by hook_field_schema():
+   *   - columns: An array of Schema API column specifications, keyed by column
+   *     name. This specifies what comprises a single value for a given field.
+   *     No assumptions should be made on how storage backends internally use
+   *     the original column name to structure their storage.
+   *   - indexes: An array of Schema API index definitions. Some storage
+   *     backends might not support indexes.
+   *   - foreign keys: An array of Schema API foreign key definitions. Note,
+   *     however, that depending on the storage backend specified for the field,
+   *     the field data is not necessarily stored in SQL.
+   */
+  public function getSchema() {
+    if (!isset($this->schema)) {
+      $module_handler = \Drupal::moduleHandler();
+
+      // Collect the schema from the field type.
+      // @todo Use $module_handler->loadInclude() once
+      // http://drupal.org/node/1941000 is fixed.
+      module_load_install($this->module);
+      // Invoke hook_field_schema() for the field.
+      $schema = (array) $module_handler->invoke($this->module, 'field_schema', array($this));
+      $schema += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
+
+      // Check that the schema does not include forbidden column names.
+      if (array_intersect(array_keys($schema['columns']), field_reserved_columns())) {
+        throw new FieldException('Illegal field type columns.');
+      }
+
+      // Merge custom indexes with those specified by the field type. Custom
+      // indexes prevail.
+      $schema['indexes'] = $this->indexes + $schema['indexes'];
+
+      $this->schema = $schema;
+    }
+
+    return $this->schema;
+  }
+
+  /**
+   * Returns information about how the storage backend stores the field data.
+   *
+   * The content of the returned value depends on the storage backend, and some
+   * storage backends might provide no information.
+   *
+   * It is strongly discouraged to use this information to perform direct write
+   * operations to the field data storage, bypassing the regular field saving
+   * APIs.
+   *
+   * Example return value for the default field_sql_storage backend:
+   * - 'sql'
+   *   - FIELD_LOAD_CURRENT
+   *     - Table name (string).
+   *       - Table schema (array)
+   *   - FIELD_LOAD_REVISION
+   *     - Table name (string).
+   *       - Table schema (array).
+   *
+   * @return array
+   *   The storage details.
+   *    - The first dimension is a store type (sql, solr, etc).
+   *    - The second dimension indicates the age of the values in the store
+   *      FIELD_LOAD_CURRENT or FIELD_LOAD_REVISION.
+   *    - Other dimensions are specific to the field storage backend.
+
+   */
+  public function getStorageDetails() {
+    if (!isset($this->storageDetails)) {
+      $module_handler = \Drupal::moduleHandler();
+
+      // Collect the storage details from the storage backend, and let other
+      // modules alter it. This invokes hook_field_storage_details() and
+      // hook_field_storage_details_alter().
+      $details = (array) $module_handler->invoke($this->storage['module'], 'field_storage_details', array($this));
+      $module_handler->alter('field_storage_details', $details, $this);
+
+      $this->storageDetails = $details;
+    }
+
+    return $this->storageDetails;
+  }
+
+  /**
+   * Returns the list of bundles where the field has instances.
+   *
+   * @return array
+   *   An array keyed by entity type names, whose values are arrays of bundle
+   *   names.
+   */
+  public function getBundles() {
+    if (empty($this->deleted)) {
+      $map = field_info_field_map();
+      if (isset($map[$this->id]['bundles'])) {
+        return $map[$this->id]['bundles'];
+      }
+    }
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    return isset($this->{$offset}) || in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &offsetGet($offset) {
+    switch ($offset) {
+      case 'id':
+        return $this->uuid;
+
+      case 'field_name':
+        return $this->id;
+
+      case 'columns':
+        $this->getSchema();
+        return $this->schema['columns'];
+
+      case 'foreign keys':
+        $this->getSchema();
+        return $this->schema['foreign keys'];
+
+      case 'bundles':
+        $bundles = $this->getBundles();
+        return $bundles;
+
+      case 'storage_details':
+        $this->getStorageDetails();
+        return $this->storageDetails;
+    }
+
+    return $this->{$offset};
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) {
+      $this->{$offset} = $value;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    if (!in_array($offset, array('columns', 'foreign keys', 'bundles', 'storage_details'))) {
+      unset($this->{$offset});
+    }
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
new file mode 100644
index 000000000000..cfe8eb129658
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
@@ -0,0 +1,528 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Plugin\Core\Entity\FieldInstance.
+ */
+
+namespace Drupal\field\Plugin\Core\Entity;
+
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\field\FieldException;
+
+/**
+ * Defines the Field instance entity.
+ *
+ * @Plugin(
+ *   id = "field_instance",
+ *   label = @Translation("Field instance"),
+ *   module = "field",
+ *   controller_class = "Drupal\field\FieldInstanceStorageController",
+ *   config_prefix = "field.instance",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class FieldInstance extends ConfigEntityBase implements \ArrayAccess {
+
+  /**
+   * The instance ID (machine name).
+   *
+   * The ID consists of 3 parts: the entity type, bundle and the field name.
+   *
+   * Example: node.article.body, user.user.field_main_image.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The instance UUID.
+   *
+   * This is assigned automatically when the instance is created.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The UUID of the field attached to the bundle by this instance.
+   *
+   * @var string
+   */
+  public $field_uuid;
+
+  /**
+   * The name of the field attached to the bundle by this instance.
+   *
+   * @var string
+   *
+   * @todo Revisit that in favor of a getField() method.
+   *   See http://drupal.org/node/1967106.
+   * @todo This variable is provided for backward compatibility and will be
+   *   removed.
+   */
+  public $field_name;
+
+  /**
+   * The name of the entity type the instance is attached to.
+   *
+   * @var string
+   */
+  public $entity_type;
+
+  /**
+   * The name of the bundle the instance is attached to.
+   *
+   * @var string
+   */
+  public $bundle;
+
+  /**
+   * The human-readable label for the instance.
+   *
+   * This will be used as the title of Form API elements for the field in entity
+   * edit forms, or as the label for the field values in displayed entities.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The instance description.
+   *
+   * A human-readable description for the field when used with this bundle.
+   * For example, the description will be the help text of Form API elements for
+   * this instance in entity edit forms.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * Field-type specific settings.
+   *
+   * An array of key/value pairs. The keys and default values are defined by the
+   * field type in the 'instance_settings' entry of hook_field_info().
+   *
+   * @var array
+   */
+  public $settings;
+
+  /**
+   * Flag indicating whether the field is required.
+   *
+   * TRUE if a value for this field is required when used with this bundle,
+   * FALSE otherwise. Currently, required-ness is only enforced at the Form API
+   * level in entity edit forms, not during direct API saves.
+   *
+   * @var bool
+   */
+  public $required;
+
+  /**
+   * Default field value.
+   *
+   * The default value is used when an entity is created, either:
+   * - through an entity creation form; the form elements for the field are
+   *   prepopulated with the default value.
+   * - through direct API calls (i.e. $entity->save()); the default value is
+   *   added if the $entity object provides no explicit entry (actual values or
+   *   "the field is empty") for the field.
+   *
+   * The default value is expressed as a numerically indexed array of items,
+   * each item being an array of key/value pairs matching the set of 'columns'
+   * defined by the "field schema" for the field type, as exposed in
+   * hook_field_schema(). If the number of items exceeds the cardinality of the
+   * field, extraneous items will be ignored.
+   *
+   * This property is overlooked if the $default_value_function is non-empty.
+   *
+   * Example for a number_integer field:
+   * @code
+   * array(
+   *   array('value' => 1),
+   *   array('value' => 2),
+   * )
+   * @endcode
+   *
+   * @var array
+   */
+  public $default_value;
+
+  /**
+   * The name of a callback function that returns default values.
+   *
+   * The function will be called with the following arguments:
+   * - \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being created.
+   * - \Drupal\field\Plugin\Core\Entity\Field $field
+   *   The field object.
+   * - \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
+   *   The field instance object.
+   * - string $langcode
+   *   The language of the entity being created.
+   * It should return an array of default values, in the same format as the
+   * $default_value property.
+   *
+   * This property takes precedence on the list of fixed values specified in the
+   * $default_value property.
+   *
+   * @var string
+   */
+  public $default_value_function;
+
+  /**
+   * The widget definition.
+   *
+   * An array of key/value pairs identifying the Form API input widget for
+   * the field when used by this bundle.
+   *   - type: (string) The plugin ID of the widget, such as text_textfield.
+   *   - settings: (array) A sub-array of key/value pairs of settings. The keys
+   *     and default values are defined by the widget plugin in the 'settings'
+   *     entry of its "plugin definition" (typycally plugin class annotations).
+   *   - weight: (float) The weight of the widget relative to the other
+   *     elements in entity edit forms.
+   *   - module: (string, read-only) The name of the module that provides the
+   *     widget plugin.
+   *
+   * @var array
+   */
+  public $widget;
+
+  /**
+   * Flag indicating whether the instance is deleted.
+   *
+   * The delete() method marks the instance as "deleted" and removes the
+   * corresponding entry from the config storage, but keeps its definition in
+   * the state storage while field data is purged by a separate
+   * garbage-collection process.
+   *
+   * Deleted instances stay out of the regular entity lifecycle (notably, their
+   * values are not populated in loaded entities, and are not saved back).
+   *
+   * @var bool
+   */
+  public $deleted;
+
+  /**
+   * The widget plugin used for this instance.
+   *
+   * @var \Drupal\field\Plugin\Type\Widget\WidgetInterface
+   */
+  protected $widgetPlugin;
+
+  /**
+   * Flag indicating whether the bundle name can be renamed or not.
+   *
+   * @var bool
+   */
+  protected $bundle_rename_allowed = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $values, $entity_type = 'field_instance') {
+    // Check required properties.
+    if (empty($values['entity_type'])) {
+      throw new FieldException(format_string('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $values['field_name'])));
+    }
+    if (empty($values['bundle'])) {
+      throw new FieldException(format_string('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $values['field_name'])));
+    }
+
+    // Accept incoming 'field_name' instead of 'field_uuid', for easier DX on
+    // creation of new instances.
+    if (isset($values['field_name']) && !isset($values['field_uuid'])) {
+      $field = field_info_field($values['field_name']);
+      if ($field) {
+        $values['field_uuid'] = $field->uuid;
+      }
+      else {
+        throw new FieldException(format_string('Attempt to create an instance of unknown, disabled, or deleted field @name', array('@name' => $values['field_name'])));
+      }
+    }
+    // Fill in the field_name property for data coming out of config.
+    // @todo Revisit that in favor of a getField() method.
+    //   See http://drupal.org/node/1967106.
+    elseif (isset($values['field_uuid']) && !isset($values['field_name'])) {
+      $field = current(field_read_fields(array('uuid' => $values['field_uuid']), array('include_inactive' => TRUE, 'include_deleted' => TRUE)));
+      if ($field) {
+        $values['field_name'] = $field->id;
+      }
+      else {
+        throw new FieldException(format_string('Attempt to create an instance of unknown field @uuid', array('@uuid' => $values['field_uuid'])));
+      }
+    }
+
+    if (empty($values['field_uuid'])) {
+      throw new FieldException('Attempt to create an instance of an unspecified field.');
+    }
+
+    // Provide defaults.
+    $values += array(
+      'label' => $values['field_name'],
+      'description' => '',
+      'required' => FALSE,
+      'default_value' => array(),
+      'default_value_function' => '',
+      'settings' => array(),
+      'widget' => array(),
+      'deleted' => 0,
+    );
+    parent::__construct($values, $entity_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->entity_type . '.' . $this->bundle . '.' . $this->field_name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportProperties() {
+    $names = array(
+      'id',
+      'uuid',
+      'status',
+      'langcode',
+      'field_uuid',
+      'entity_type',
+      'bundle',
+      'label',
+      'description',
+      'required',
+      'default_value',
+      'default_value_function',
+      'settings',
+      'widget',
+    );
+    $properties = array();
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    $module_handler = \Drupal::moduleHandler();
+    $entity_manager = \Drupal::service('plugin.manager.entity');
+    $field_controller = $entity_manager->getStorageController('field_entity');
+    $instance_controller = $entity_manager->getStorageController($this->entityType);
+
+    $field = current($field_controller->load(array($this->field_name)));
+
+    if ($this->isNew()) {
+      if (empty($field)) {
+        throw new FieldException(format_string("Attempt to save an instance of a field @field_id that doesn't exist or is currently inactive.", array('@field_name' => $this->field_name)));
+      }
+      // Check that the field can be attached to this entity type.
+      if (!empty($field->entity_types) && !in_array($this->entity_type, $field->entity_types)) {
+        throw new FieldException(format_string('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $this->field_name, '@entity_type' => $this->entity_type)));
+      }
+
+      // Assign the ID.
+      $this->id = $this->id();
+
+      // Ensure the field instance is unique within the bundle.
+      if ($prior_instance = current($instance_controller->load(array($this->id)))) {
+        throw new FieldException(format_string('Attempt to create an instance of field @field_name on bundle @bundle that already has an instance of that field.', array('@field_name' => $this->field_name, '@bundle' => $this->bundle)));
+      }
+
+      // Set the field UUID.
+      $this->field_uuid = $field->uuid;
+
+      $hook = 'field_create_instance';
+      $hook_args = array($this);
+    }
+    // Otherwise, the field instance is being updated.
+    else {
+      $original = \Drupal::service('plugin.manager.entity')
+        ->getStorageController($this->entityType)
+        ->loadUnchanged($this->getOriginalID());
+
+      // Some updates are always disallowed.
+      if ($this->entity_type != $original->entity_type) {
+        throw new FieldException("Cannot change an existing instance's entity_type.");
+      }
+      if ($this->bundle != $original->bundle && empty($this->bundle_rename_allowed)) {
+        throw new FieldException("Cannot change an existing instance's bundle.");
+      }
+      if ($this->field_name != $original->field_name || $this->field_uuid != $original->field_uuid) {
+        throw new FieldException("Cannot change an existing instance's field.");
+      }
+
+      $hook = 'field_update_instance';
+      $hook_args = array($this, $original);
+    }
+
+    $field_type_info = field_info_field_types($field->type);
+
+    // Set the default instance settings.
+    $this->settings += $field_type_info['instance_settings'];
+
+    // Set the default widget and settings.
+    $this->widget += array(
+      'type' => $field_type_info['default_widget'],
+      'settings' => array(),
+    );
+    // Get the widget module and settings from the widget type.
+    if ($widget_type_info = \Drupal::service('plugin.manager.field.widget')->getDefinition($this->widget['type'])) {
+      $this->widget['module'] = $widget_type_info['module'];
+      $this->widget['settings'] += $widget_type_info['settings'];
+    }
+    // If no weight is specified, make sure the field sinks to the bottom.
+    if (!isset($this->widget['weight'])) {
+      $max_weight = field_info_max_weight($this->entity_type, $this->bundle, 'form');
+      $this->widget['weight'] = isset($max_weight) ? $max_weight + 1 : 0;
+    }
+
+    // Save the configuration.
+    $result = parent::save();
+    field_cache_clear();
+
+    // Invoke external hooks after the cache is cleared for API consistency.
+    // This invokes hook_field_create_instance() or hook_field_update_instance()
+    // depending on whether the field is new.
+    $module_handler->invokeAll($hook, $hook_args);
+
+    return $result;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\Entity::delete().
+   *
+   * @param bool $field_cleanup
+   *   (optional) If TRUE, the field will be deleted as well if its last
+   *   instance is being deleted. If FALSE, it is the caller's responsibility to
+   *   handle the case of fields left without instances. Defaults to TRUE.
+   */
+  public function delete($field_cleanup = TRUE) {
+    if (!$this->deleted) {
+      $module_handler = \Drupal::moduleHandler();
+      $state = \Drupal::state();
+
+      // Delete the configuration of this instance and save the configuration
+      // in the key_value table so we can use it later during
+      // field_purge_batch().
+      $deleted_instances = $state->get('field.instance.deleted') ?: array();
+      $config = $this->getExportProperties();
+      $config['deleted'] = TRUE;
+      $deleted_instances[$this->uuid] = $config;
+      $state->set('field.instance.deleted', $deleted_instances);
+
+      parent::delete();
+
+      // Clear the cache.
+      field_cache_clear();
+
+      // Mark instance data for deletion by invoking
+      // hook_field_storage_delete_instance().
+      $field = field_info_field($this->field_name);
+      $module_handler->invoke($field->storage['module'], 'field_storage_delete_instance', array($this));
+
+      // Let modules react to the deletion of the instance with
+      // hook_field_delete_instance().
+      $module_handler->invokeAll('field_delete_instance', array($this));
+
+      // Delete the field itself if we just deleted its last instance.
+      if ($field_cleanup && count($field->getBundles()) == 0) {
+        $field->delete();
+      }
+    }
+  }
+
+  /**
+   * Returns the Widget plugin for the instance.
+   *
+   * @return Drupal\field\Plugin\Type\Widget\WidgetInterface
+   *   The Widget plugin to be used for the instance.
+   */
+  public function getWidget() {
+    if (empty($this->widgetPlugin)) {
+      $widget_properties = $this->widget;
+
+      // Let modules alter the widget properties.
+      $context = array(
+        'entity_type' => $this->entity_type,
+        'bundle' => $this->bundle,
+        'field' => field_info_field_by_id($this->field_uuid),
+        'instance' => $this,
+      );
+      // Invoke hook_field_widget_properties_alter() and
+      // hook_field_widget_properties_ENTITY_TYPE_alter().
+      drupal_alter(array('field_widget_properties', 'field_widget_properties_' . $this->entity_type), $widget_properties, $context);
+
+      $options = array(
+        'instance' => $this,
+        'type' => $widget_properties['type'],
+        'settings' => $widget_properties['settings'],
+        'weight' => $widget_properties['weight'],
+      );
+      $this->widgetPlugin = \Drupal::service('plugin.manager.field.widget')->getInstance($options);
+    }
+
+    return $this->widgetPlugin;
+  }
+
+  /**
+   * Allows a bundle to be renamed.
+   *
+   * Renaming a bundle on the instance is allowed when an entity's bundle
+   * is renamed and when field_entity_bundle_rename() does internal
+   * housekeeping.
+   */
+  public function allowBundleRename() {
+    $this->bundle_rename_allowed = TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    return (isset($this->{$offset}) || $offset == 'field_id');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &offsetGet($offset) {
+    if ($offset == 'field_id') {
+      return $this->field_uuid;
+    }
+    return $this->{$offset};
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    if ($offset == 'field_id') {
+      $offset = 'field_uuid';
+    }
+    $this->{$offset} = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    if ($offset == 'field_id') {
+      $offset = 'field_uuid';
+    }
+    unset($this->{$offset});
+  }
+
+}
+
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
index 92d6fbac0e80..5b2473ae9657 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
@@ -9,7 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Plugin\PluginSettingsBase;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Base class for 'Field formatter' plugin implementations.
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php
index 36693b0a2ec6..405881077288 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php
@@ -8,7 +8,7 @@
 namespace Drupal\field\Plugin\Type\Formatter;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\field\Plugin\PluginSettingsInterface;
 
 /**
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
index 424f27231024..4057c887a9ed 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
@@ -13,7 +13,7 @@
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 use Drupal\Core\Plugin\Discovery\AlterDecorator;
 use Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Plugin type manager for field formatters.
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
index b60552e109f4..39efcc80e83a 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
@@ -10,7 +10,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Plugin\PluginSettingsBase;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Base class for 'Field widget' plugin implementations.
@@ -52,7 +52,7 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface
    *   The plugin_id for the widget.
    * @param array $plugin_definition
    *   The plugin implementation definition.
-   * @param Drupal\field\FieldInstance $instance
+   * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
    *   The field instance to which the widget is associated.
    * @param array $settings
    *   The widget settings.
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php
index c0ffc9591afa..8711bbd66884 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetInterface.php
@@ -8,7 +8,7 @@
 namespace Drupal\field\Plugin\Type\Widget;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Interface definition for field widget plugins.
diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
index 4aff8f1ca072..f4b564f8b809 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
@@ -166,8 +166,8 @@ public function query($use_groupby = FALSE) {
       // Go through the list and determine the actual column name from field api.
       foreach ($options as $column) {
         $name = $column;
-        if (isset($this->field_info['storage']['details']['sql'][$rkey][$this->table][$column])) {
-          $name = $this->field_info['storage']['details']['sql'][$rkey][$this->table][$column];
+        if (isset($this->field_info['storage_details']['sql'][$rkey][$this->table][$column])) {
+          $name = $this->field_info['storage_details']['sql'][$rkey][$this->table][$column];
         }
 
         $fields[$column] = $name;
@@ -448,7 +448,6 @@ function fakeFieldInstance($formatter, $formatter_settings) {
       ),
 
       // Set the other fields to their default values.
-      // @see _field_write_instance().
       'required' => FALSE,
       'label' => $field_name,
       'description' => '',
diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php
index eabf59afcf03..9b031747a08e 100644
--- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\field\Tests;
 
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
+
 /**
  * Unit test class for field bulk delete and batch purge functionality.
  */
@@ -164,7 +166,7 @@ function testDeleteFieldInstance() {
     field_delete_instance($instance);
 
     // The instance still exists, deleted.
-    $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
+    $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
     $this->assertEqual(count($instances), 1, 'There is one deleted instance');
     $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle');
 
@@ -191,7 +193,7 @@ function testDeleteFieldInstance() {
       $ids->entity_id = $entity_id;
       $entities[$entity_id] = _field_create_entity_from_ids($ids);
     }
-    field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
+    field_attach_load($this->entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1));
     $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting');
     foreach ($entities as $id => $entity) {
       $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly");
@@ -247,19 +249,19 @@ function testPurgeInstance() {
     $this->checkHooksInvocations($hooks, $actual_hooks);
 
     // The instance still exists, deleted.
-    $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
+    $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
     $this->assertEqual(count($instances), 1, 'There is one deleted instance');
 
     // Purge the instance.
     field_purge_batch($batch_size);
 
     // The instance is gone.
-    $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
+    $instances = field_read_instances(array('field_id' => $field['uuid'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
     $this->assertEqual(count($instances), 0, 'The instance is gone');
 
     // The field still exists, not deleted, because it has a second instance.
-    $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
-    $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted');
+    $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1, 'include_inactive' => 1));
+    $this->assertTrue(isset($fields[$field['uuid']]), 'The field exists and is not deleted');
   }
 
   /**
@@ -300,8 +302,8 @@ function testPurgeField() {
     field_purge_batch(0);
 
     // The field still exists, not deleted.
-    $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1));
-    $this->assertTrue(isset($fields[$field['id']]) && !$fields[$field['id']]['deleted'], 'The field exists and is not deleted');
+    $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1));
+    $this->assertTrue(isset($fields[$field['uuid']]) && !$fields[$field['uuid']]->deleted, 'The field exists and is not deleted');
 
     // Delete the second instance.
     $bundle = next($this->bundles);
@@ -324,14 +326,14 @@ function testPurgeField() {
     $this->checkHooksInvocations($hooks, $actual_hooks);
 
     // The field still exists, deleted.
-    $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1));
-    $this->assertTrue(isset($fields[$field['id']]) && $fields[$field['id']]['deleted'], 'The field exists and is deleted');
+    $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1));
+    $this->assertTrue(isset($fields[$field['uuid']]) && $fields[$field['uuid']]->deleted, 'The field exists and is deleted');
 
     // Purge again to purge the instance and the field.
     field_purge_batch(0);
 
     // The field is gone.
-    $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
+    $fields = field_read_fields(array('uuid' => $field['uuid']), array('include_deleted' => 1, 'include_inactive' => 1));
     $this->assertEqual(count($fields), 0, 'The field is purged.');
   }
 }
diff --git a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php
index f7c3a9c9aa44..a0afa3f3629c 100644
--- a/core/modules/field/lib/Drupal/field/Tests/CrudTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/CrudTest.php
@@ -41,28 +41,29 @@ function testCreateField() {
       'type' => 'test_field',
     );
     field_test_memorize();
-    $field_definition = field_create_field($field_definition);
+    $field = field_create_field($field_definition);
     $mem = field_test_memorize();
-    $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.');
+    $this->assertIdentical($mem['field_test_field_create_field'][0][0]['field_name'], $field_definition['field_name'], 'hook_field_create_field() called with correct arguments.');
+    $this->assertIdentical($mem['field_test_field_create_field'][0][0]['type'], $field_definition['type'], 'hook_field_create_field() called with correct arguments.');
 
-    // Read the raw record from the {field_config_instance} table.
-    $result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']));
-    $record = $result->fetchAssoc();
-    $record['data'] = unserialize($record['data']);
+    // Read the configuration. Check against raw configuration data rather than
+    // the loaded ConfigEntity, to be sure we check that the defaults are
+    // applied on write.
+    $field_config = \Drupal::config('field.field.' . $field->id())->get();
 
     // Ensure that basic properties are preserved.
-    $this->assertEqual($record['field_name'], $field_definition['field_name'], 'The field name is properly saved.');
-    $this->assertEqual($record['type'], $field_definition['type'], 'The field type is properly saved.');
+    $this->assertEqual($field_config['id'], $field_definition['field_name'], 'The field name is properly saved.');
+    $this->assertEqual($field_config['type'], $field_definition['type'], 'The field type is properly saved.');
 
     // Ensure that cardinality defaults to 1.
-    $this->assertEqual($record['cardinality'], 1, 'Cardinality defaults to 1.');
+    $this->assertEqual($field_config['cardinality'], 1, 'Cardinality defaults to 1.');
 
     // Ensure that default settings are present.
     $field_type = field_info_field_types($field_definition['type']);
-    $this->assertIdentical($record['data']['settings'], $field_type['settings'], 'Default field settings have been written.');
+    $this->assertEqual($field_config['settings'], $field_type['settings'], 'Default field settings have been written.');
 
     // Ensure that default storage was set.
-    $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), 'The field type is properly saved.');
+    $this->assertEqual($field_config['storage']['type'], variable_get('field_storage_default'), 'The field type is properly saved.');
 
     // Guarantee that the name is unique.
     try {
@@ -143,7 +144,7 @@ function testCreateField() {
         'type' => 'test_field',
         'field_name' => 'ftvid',
       );
-      $field = field_create_field($field_definition);
+      field_create_field($field_definition);
       $this->fail(t('Cannot create a field bearing the name of an entity key.'));
     }
     catch (FieldException $e) {
@@ -157,11 +158,10 @@ function testCreateField() {
   function testCreateFieldFail() {
     $field_name = 'duplicate';
     $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure'));
-    $query = db_select('field_config')->condition('field_name', $field_name)->countQuery();
+    $field = entity_load('field_entity', $field_name);
 
-    // The field does not appear in field_config.
-    $count = $query->execute()->fetchField();
-    $this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
+    // The field does not exist.
+    $this->assertFalse($field, 'The field does not exist.');
 
     // Try to create the field.
     try {
@@ -172,9 +172,9 @@ function testCreateFieldFail() {
       $this->assertTrue(TRUE, 'Field creation (correctly) fails.');
     }
 
-    // The field does not appear in field_config.
-    $count = $query->execute()->fetchField();
-    $this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
+    // The field does not exist.
+    $field = entity_load('field_entity', $field_name);
+    $this->assertFalse($field, 'The field does not exist.');
   }
 
   /**
@@ -219,12 +219,6 @@ function testReadFields() {
       'bundle' => 'test_bundle',
     );
     field_create_instance($instance_definition);
-
-    // Check that criteria spanning over the field_config_instance table work.
-    $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle']));
-    $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
-    $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name']));
-    $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
   }
 
   /**
@@ -238,8 +232,9 @@ function testFieldIndexes() {
     );
     field_create_field($field_definition);
     $field = field_read_field($field_definition['field_name']);
+    $schema = $field->getSchema();
     $expected_indexes = array('value' => array('value'));
-    $this->assertEqual($field['indexes'], $expected_indexes, 'Field type indexes saved by default');
+    $this->assertEqual($schema['indexes'], $expected_indexes, 'Field type indexes saved by default');
 
     // Check that indexes specified by the field definition override the field
     // type indexes.
@@ -252,8 +247,9 @@ function testFieldIndexes() {
     );
     field_create_field($field_definition);
     $field = field_read_field($field_definition['field_name']);
+    $schema = $field->getSchema();
     $expected_indexes = array('value' => array());
-    $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes override field type indexes');
+    $this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes override field type indexes');
 
     // Check that indexes specified by the field definition add to the field
     // type indexes.
@@ -266,8 +262,9 @@ function testFieldIndexes() {
     );
     field_create_field($field_definition);
     $field = field_read_field($field_definition['field_name']);
+    $schema = $field->getSchema();
     $expected_indexes = array('value' => array('value'), 'value_2' => array('value'));
-    $this->assertEqual($field['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes');
+    $this->assertEqual($schema['indexes'], $expected_indexes, 'Field definition indexes are merged with field type indexes');
   }
 
   /**
@@ -351,17 +348,6 @@ function testDeleteField() {
     }
   }
 
-  function testUpdateNonExistentField() {
-    $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal');
-    try {
-      field_update_field($test_field);
-      $this->fail(t('Cannot update a field that does not exist.'));
-    }
-    catch (FieldException $e) {
-      $this->pass(t('Cannot update a field that does not exist.'));
-    }
-  }
-
   function testUpdateFieldType() {
     $field = array('field_name' => 'field_type', 'type' => 'number_decimal');
     $field = field_create_field($field);
@@ -384,18 +370,16 @@ function testUpdateField() {
     // respected. Since cardinality enforcement is consistent across database
     // systems, it makes a good test case.
     $cardinality = 4;
-    $field_definition = array(
+    $field = field_create_field(array(
       'field_name' => 'field_update',
       'type' => 'test_field',
       'cardinality' => $cardinality,
-    );
-    $field_definition = field_create_field($field_definition);
-    $instance = array(
+    ));
+    $instance = field_create_instance(array(
       'field_name' => 'field_update',
       'entity_type' => 'test_entity',
       'bundle' => 'test_bundle',
-    );
-    $instance = field_create_instance($instance);
+    ));
 
     do {
       // We need a unique ID for our entity. $cardinality will do.
@@ -410,14 +394,14 @@ function testUpdateField() {
       // Load back and assert there are $cardinality number of values.
       $entity = field_test_create_entity($id, $id, $instance['bundle']);
       field_attach_load('test_entity', array($id => $entity));
-      $this->assertEqual(count($entity->field_update[LANGUAGE_NOT_SPECIFIED]), $field_definition['cardinality'], 'Cardinality is kept');
+      $this->assertEqual(count($entity->field_update[LANGUAGE_NOT_SPECIFIED]), $field['cardinality'], 'Cardinality is kept');
       // Now check the values themselves.
       for ($delta = 0; $delta < $cardinality; $delta++) {
         $this->assertEqual($entity->field_update[LANGUAGE_NOT_SPECIFIED][$delta]['value'], $delta, 'Value is kept');
       }
       // Increase $cardinality and set the field cardinality to the new value.
-      $field_definition['cardinality'] = ++$cardinality;
-      field_update_field($field_definition);
+      $field['cardinality'] = ++$cardinality;
+      field_update_field($field);
     } while ($cardinality < 6);
   }
 
@@ -444,4 +428,5 @@ function testUpdateFieldForbid() {
       $this->pass(t("An unchangeable setting cannot be updated."));
     }
   }
+
 }
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
index 9ec10f30c0fc..38499c8b4733 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php
@@ -117,7 +117,7 @@ function testFieldAttachLoadMultiple() {
       $field_names[$i] = 'field_' . $i;
       $field = array('field_name' => $field_names[$i], 'type' => 'test_field');
       $field = field_create_field($field);
-      $field_ids[$i] = $field['id'];
+      $field_ids[$i] = $field['uuid'];
       foreach ($field_bundles_map[$i] as $bundle) {
         $instance = array(
           'field_name' => $field_names[$i],
@@ -242,9 +242,9 @@ function testFieldStorageDetailsAlter() {
     $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
 
     // The storage details are indexed by a storage engine type.
-    $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), 'The storage type is Drupal variables.');
+    $this->assertTrue(array_key_exists('drupal_variables', $field['storage_details']), 'The storage type is Drupal variables.');
 
-    $details = $field['storage']['details']['drupal_variables'];
+    $details = $field['storage_details']['drupal_variables'];
 
     // The field_test storage details are indexed by variable name. The details
     // are altered, so moon and mars are correct for this test.
@@ -253,7 +253,7 @@ function testFieldStorageDetailsAlter() {
 
     // Test current and revision storage details together because the columns
     // are the same.
-    foreach ((array) $field['columns'] as $column_name => $attributes) {
+    foreach ($field['columns'] as $column_name => $attributes) {
       $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]')));
       $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, format_string('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]')));
     }
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php
new file mode 100644
index 000000000000..aaf648d27711
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportChangeTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\FieldImportChangeTest.
+ */
+
+namespace Drupal\field\Tests;
+
+/**
+ * Tests updating fields and instances as part of config import.
+ */
+class FieldImportChangeTest extends FieldUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field_test_config');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Field config change tests',
+      'description' => 'Update field and instances during config change method invocation.',
+      'group' => 'Field API',
+    );
+  }
+
+  /**
+   * Tests importing an updated field instance.
+   */
+  function testImportChange() {
+    $field_id = 'field_test_import';
+    $instance_id = "test_entity.test_bundle.$field_id";
+    $instance_config_name = "field.instance.$instance_id";
+
+    // Import default config.
+    $this->installConfig(array('field_test_config'));
+
+    // Simulate config data to import:
+    // - the current manifest for field instances,
+    // - a modified version (modified label) of the instance config.
+    $manifest_name = 'manifest.field.instance';
+    $active = $this->container->get('config.storage');
+    $manifest = $active->read($manifest_name);
+    $instance = $active->read($instance_config_name);
+    $new_label = 'Test update import field';
+    $instance['label'] = $new_label;
+
+    // Save as files in the the staging directory.
+    $staging = $this->container->get('config.storage.staging');
+    $staging->write($manifest_name, $manifest);
+    $staging->write($instance_config_name, $instance);
+
+    // Import the content of the staging directory.
+    config_import();
+
+    // Check that the updated config was correctly imported.
+    $instance = entity_load('field_instance', $instance_id);
+    $this->assertEqual($instance['label'], $new_label, 'Instance label updated');
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
new file mode 100644
index 000000000000..ff21fac97bda
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportCreateTest.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\FieldImportCreateTest.
+ */
+
+namespace Drupal\field\Tests;
+
+/**
+ * Tests creating fields and instances as part of config import.
+ */
+class FieldImportCreateTest extends FieldUnitTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Field config create tests',
+      'description' => 'Create field and instances during config create method invocation.',
+      'group' => 'Field API',
+    );
+  }
+
+  /**
+   * Tests creating fields and instances during default config import.
+   */
+  function testImportCreateDefault() {
+    $field_id = 'field_test_import';
+    $instance_id = "test_entity.test_bundle.$field_id";
+
+    // Check that the field and instance do not exist yet.
+    $this->assertFalse(entity_load('field_entity', $field_id));
+    $this->assertFalse(entity_load('field_instance', $instance_id));
+
+    // Enable field_test_config module and check that the field and instance
+    // shipped in the module's default config were created.
+    module_enable(array('field_test_config'));
+    $field = entity_load('field_entity', $field_id);
+    $this->assertTrue($field, 'The field was created.');
+    $instance = entity_load('field_instance', $instance_id);
+    $this->assertTrue($instance, 'The field instance was deleted.');
+  }
+
+  /**
+   * Tests creating fields and instances during config import.
+   */
+  function testImportCreate() {
+    $field_id = 'field_test_import_staging';
+    $instance_id = "test_entity.test_bundle.$field_id";
+    $field_config_name = "field.field.$field_id";
+    $instance_config_name = "field.instance.$instance_id";
+
+    // Simulate config data to import:
+    $src_dir = drupal_get_path('module', 'field_test_config') . '/staging';
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$field_config_name.yml", "public://config_staging/$field_config_name.yml"));
+    $this->assertTrue(file_unmanaged_copy("$src_dir/$instance_config_name.yml", "public://config_staging/$instance_config_name.yml"));
+
+    // Add the coresponding entries to the current manifest data.
+    $field_manifest_name = 'manifest.field.field';
+    $instance_manifest_name = 'manifest.field.instance';
+    $active = $this->container->get('config.storage');
+    $field_manifest = $active->read($field_manifest_name);
+    $field_manifest[$field_id] = array('name' => $field_config_name);
+    $instance_manifest = $active->read($instance_manifest_name);
+    $instance_manifest[$instance_id] = array('name' => $instance_config_name);
+
+    // Save the manifests as files in the the staging directory.
+    $staging = $this->container->get('config.storage.staging');
+    $staging->write($field_manifest_name, $field_manifest);
+    $staging->write($instance_manifest_name, $instance_manifest);
+
+    // Import the content of the staging directory.
+    config_import();
+
+    // Check that the field and instance were created.
+    $field = entity_load('field_entity', $field_id);
+    $this->assertTrue($field, 'Test import field from staging exists');
+    $instance = entity_load('field_instance', $instance_id);
+    $this->assertTrue($instance, 'Test import field instance from staging exists');
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php
new file mode 100644
index 000000000000..0db6455947f6
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldImportDeleteTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\FieldImportDeleteTest.
+ */
+
+namespace Drupal\field\Tests;
+
+/**
+ * Tests deleting fields and instances as part of config import.
+ */
+class FieldImportDeleteTest extends FieldUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field_test_config');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Field config delete tests',
+      'description' => 'Delete field and instances during config delete method invocation.',
+      'group' => 'Field API',
+    );
+  }
+
+  /**
+   * Tests deleting fields and instances as part of config import.
+   */
+  function testImportDelete() {
+    $field_id = 'field_test_import';
+    $instance_id = "test_entity.test_bundle.$field_id";
+    $field_config_name = "field.field.$field_id";
+    $instance_config_name = "field.instance.$instance_id";
+
+    // Import default config.
+    $this->installConfig(array('field_test_config'));
+
+    // Check that the config was correctly imported.
+    $field = entity_load('field_entity', $field_id);
+    $this->assertTrue($field, 'The field was created.');
+    $instance = entity_load('field_instance', $instance_id);
+    $this->assertTrue($instance, 'The field instance was created.');
+
+    $field_uuid = $field->uuid;
+
+    // Simulate config data to import:
+    // - the current manifest for fields, without the entry for the field we
+    //   remove,
+    // - the current manifest for instances, without the entry for the instance
+    //   we remove.
+    $field_manifest_name = 'manifest.field.field';
+    $instance_manifest_name = 'manifest.field.instance';
+    $active = $this->container->get('config.storage');
+    $field_manifest = $active->read($field_manifest_name);
+    unset($field_manifest[$field_id]);
+    $instance_manifest = $active->read($instance_manifest_name);
+    unset($instance_manifest[$instance_id]);
+
+    // Save as files in the the staging directory.
+    $staging = $this->container->get('config.storage.staging');
+    $staging->write($field_manifest_name, $field_manifest);
+    $staging->write($instance_manifest_name, $instance_manifest);
+
+    // Import the content of the staging directory.
+    config_import();
+
+    // Check that the field and instance are gone.
+    $field = entity_load('field_entity', $field_id, TRUE);
+    $this->assertFalse($field, 'The field was deleted.');
+    $instance = entity_load('field_instance', $instance_id, TRUE);
+    $this->assertFalse($instance, 'The field instance was deleted.');
+
+    // Check that all config files are gone.
+    $active = $this->container->get('config.storage');
+    $this->assertIdentical($active->listAll($field_config_name), array());
+    $this->assertIdentical($active->listAll($instance_config_name), array());
+
+    // Check that the field definition is preserved in state.
+    $deleted_fields = \Drupal::state()->get('field.field.deleted') ?: array();
+    $this->assertTrue(isset($deleted_fields[$field_uuid]));
+
+    // Purge field data, and check that the field definition has been completely
+    // removed once the data is purged.
+    field_purge_batch(10);
+    $deleted_fields = \Drupal::state()->get('field.field.deleted') ?: array();
+    $this->assertTrue(empty($deleted_fields), 'Fields are deleted');
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
index f9a1031695ee..0ae1dc0a4b8a 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
@@ -131,27 +131,22 @@ function testFieldPrepare() {
       'field_name' => 'field',
       'type' => 'test_field',
     );
-    field_create_field($field_definition);
+    $field = field_create_field($field_definition);
 
     // Simulate a stored field definition missing a field setting (e.g. a
     // third-party module adding a new field setting has been enabled, and
     // existing fields do not know the setting yet).
-    $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField();
-    $data = unserialize($data);
-    $data['settings'] = array();
-    db_update('field_config')
-      ->fields(array('data' => serialize($data)))
-      ->condition('field_name', $field_definition['field_name'])
-      ->execute();
-
-    field_cache_clear();
+    \Drupal::config('field.field.' . $field->id())
+      ->set('settings', array())
+      ->save();
+    field_info_cache_clear();
 
     // Read the field back.
     $field = field_info_field($field_definition['field_name']);
 
     // Check that all expected settings are in place.
     $field_type = field_info_field_types($field_definition['type']);
-    $this->assertIdentical($field['settings'], $field_type['settings'], 'All expected default field settings are present.');
+    $this->assertEqual($field['settings'], $field_type['settings'], 'All expected default field settings are present.');
   }
 
   /**
@@ -168,30 +163,24 @@ function testInstancePrepare() {
       'entity_type' => 'test_entity',
       'bundle' => 'test_bundle',
     );
-    field_create_instance($instance_definition);
+    $instance = field_create_instance($instance_definition);
 
     // Simulate a stored instance definition missing various settings (e.g. a
     // third-party module adding instance or widget settings has been enabled,
     // but existing instances do not know the new settings).
-    $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField();
-    $data = unserialize($data);
-    $data['settings'] = array();
-    $data['widget']['settings'] = 'unavailable_widget';
-    $data['widget']['settings'] = array();
-    db_update('field_config_instance')
-      ->fields(array('data' => serialize($data)))
-      ->condition('field_name', $instance_definition['field_name'])
-      ->condition('bundle', $instance_definition['bundle'])
-      ->execute();
-
-    field_cache_clear();
+    \Drupal::config('field.instance.' . $instance->id())
+      ->set('settings', array())
+      ->set('widget.type', 'unavailable_widget')
+      ->set('widget.settings', array())
+      ->save();
+    field_info_cache_clear();
 
     // Read the instance back.
     $instance = field_info_instance($instance_definition['entity_type'], $instance_definition['field_name'], $instance_definition['bundle']);
 
     // Check that all expected instance settings are in place.
     $field_type = field_info_field_types($field_definition['type']);
-    $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.');
+    $this->assertEqual($instance['settings'], $field_type['instance_settings'] , 'All expected instance settings are present.');
 
     // Check that the default widget is used and expected settings are in place.
     $widget = $instance->getWidget();
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php
index ee30cf162107..2455a31db075 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldInstanceCrudTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\field\Tests;
 
 use Drupal\field\FieldException;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 class FieldInstanceCrudTest extends FieldUnitTestBase {
 
@@ -46,28 +47,25 @@ function setUp() {
    * Test the creation of a field instance.
    */
   function testCreateFieldInstance() {
-    field_create_instance($this->instance_definition);
+    $instance = field_create_instance($this->instance_definition);
 
-    // Read the raw record from the {field_config_instance} table.
-    $result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle']));
-    $record = $result->fetchAssoc();
-    $record['data'] = unserialize($record['data']);
+    // Read the configuration. Check against raw configuration data rather than
+    // the loaded ConfigEntity, to be sure we check that the defaults are
+    // applied on write.
+    $config = \Drupal::config('field.instance.' . $instance->id())->get();
 
     $field_type = field_info_field_types($this->field['type']);
     $widget_type = field_info_widget_types($field_type['default_widget']);
 
-    // Check that the ID key is filled in.
-    $this->assertIdentical($record['id'], $this->instance_definition['id'], 'The instance id is filled in');
-
     // Check that default values are set.
-    $this->assertIdentical($record['data']['required'], FALSE, 'Required defaults to false.');
-    $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], 'Label defaults to field name.');
-    $this->assertIdentical($record['data']['description'], '', 'Description defaults to empty string.');
-    $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], 'Default widget has been written.');
+    $this->assertEqual($config['required'], FALSE, 'Required defaults to false.');
+    $this->assertIdentical($config['label'], $this->instance_definition['field_name'], 'Label defaults to field name.');
+    $this->assertIdentical($config['description'], '', 'Description defaults to empty string.');
+    $this->assertIdentical($config['widget']['type'], $field_type['default_widget'], 'Default widget has been written.');
 
     // Check that default settings are set.
-    $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.');
-    $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.');
+    $this->assertEqual($config['settings'], $field_type['instance_settings'] , 'Default instance settings have been written.');
+    $this->assertIdentical($config['widget']['settings'], $widget_type['settings'] , 'Default widget settings have been written.');
 
     // Guarantee that the field/bundle combination is unique.
     try {
@@ -132,7 +130,9 @@ function testReadFieldInstance() {
 
     // Read the instance back.
     $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']);
-    $this->assertTrue($this->instance_definition == $instance, 'The field was properly read.');
+    $this->assertTrue($this->instance_definition['field_name'] == $instance['field_name'], 'The field was properly read.');
+    $this->assertTrue($this->instance_definition['entity_type'] == $instance['entity_type'], 'The field was properly read.');
+    $this->assertTrue($this->instance_definition['bundle'] == $instance['bundle'], 'The field was properly read.');
   }
 
   /**
@@ -206,7 +206,9 @@ function testDeleteFieldInstance() {
 
     // Make sure the field is deleted when its last instance is deleted.
     field_delete_instance($another_instance);
-    $field = field_read_field($another_instance['field_name'], array('include_deleted' => TRUE));
-    $this->assertTrue(!empty($field['deleted']), 'A deleted field is marked for deletion after all its instances have been marked for deletion.');
+    $deleted_fields = \Drupal::state()->get('field.field.deleted');
+    $this->assertTrue(isset($deleted_fields[$another_instance['field_id']]), 'A deleted field is marked for deletion.');
+    $field = field_read_field($another_instance['field_name']);
+    $this->assertFalse($field, 'The field marked to be deleted is not found anymore in the configuration.');
   }
 }
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php
index 94b068df2e43..3e1bf38b3ea6 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php
@@ -35,8 +35,7 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
    */
   function setUp() {
     parent::setUp();
-    $this->installSchema('system', array('sequences', 'variable'));
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
+    $this->installSchema('system', array('sequences', 'variable', 'config_snapshot'));
     $this->installSchema('entity_test', 'entity_test');
     $this->installSchema('field_test', array('test_entity', 'test_entity_revision', 'test_entity_bundle'));
 
@@ -60,7 +59,7 @@ function createFieldWithInstance($suffix = '') {
     $this->$field_name = drupal_strtolower($this->randomName() . '_field_name' . $suffix);
     $this->$field = array('field_name' => $this->$field_name, 'type' => 'test_field', 'cardinality' => 4);
     $this->$field = field_create_field($this->$field);
-    $this->$field_id = $this->{$field}['id'];
+    $this->$field_id = $this->{$field}['uuid'];
     $this->$instance = array(
       'field_name' => $this->$field_name,
       'entity_type' => 'test_entity',
diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
index 609d70fb5d74..83be37de2d89 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
@@ -43,21 +43,21 @@ function setUp() {
 
     $this->entity_type = 'test_entity';
 
-    $field = array(
+    $this->field_definition = array(
       'field_name' => $this->field_name,
       'type' => 'test_field',
       'cardinality' => 4,
       'translatable' => TRUE,
     );
-    field_create_field($field);
+    field_create_field($this->field_definition);
     $this->field = field_read_field($this->field_name);
 
-    $instance = array(
+    $this->instance_definition = array(
       'field_name' => $this->field_name,
       'entity_type' => $this->entity_type,
       'bundle' => 'test_bundle',
     );
-    field_create_instance($instance);
+    field_create_instance($this->instance_definition);
     $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle');
 
     for ($i = 0; $i < 3; ++$i) {
@@ -75,7 +75,7 @@ function setUp() {
   function testFieldAvailableLanguages() {
     // Test 'translatable' fieldable info.
     field_test_entity_info_translatable('test_entity', FALSE);
-    $field = $this->field;
+    $field = clone($this->field);
     $field['field_name'] .= '_untranslatable';
 
     // Enable field translations for the entity.
@@ -249,14 +249,15 @@ function testTranslatableFieldSaveLoad() {
 
     // Test default values.
     $field_name_default = drupal_strtolower($this->randomName() . '_field_name');
-    $field = $this->field;
-    $field['field_name'] = $field_name_default;
-    $instance = $this->instance;
-    $instance['field_name'] = $field_name_default;
-    $default = rand(1, 127);
-    $instance['default_value'] = array(array('value' => $default));
-    field_create_field($field);
-    field_create_instance($instance);
+    $field_definition = $this->field_definition;
+    $field_definition['field_name'] = $field_name_default;
+    $field = field_create_field($field_definition);
+
+    $instance_definition = $this->instance_definition;
+    $instance_definition['field_name'] = $field_name_default;
+    $instance_definition['default_value'] = array(array('value' => rand(1, 127)));
+    $instance = field_create_instance($instance_definition);
+
     $translation_langcodes = array_slice($available_langcodes, 0, 2);
     asort($translation_langcodes);
     $translation_langcodes = array_values($translation_langcodes);
diff --git a/core/modules/field/tests/modules/field_test/field_test.storage.inc b/core/modules/field/tests/modules/field_test/field_test.storage.inc
index 3d7a0f16f57d..7705392e03ff 100644
--- a/core/modules/field/tests/modules/field_test/field_test.storage.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.storage.inc
@@ -89,7 +89,7 @@ function field_test_field_storage_load($entity_type, $entities, $age, $fields, $
   foreach ($fields as $field_id => $ids) {
     $field = field_info_field_by_id($field_id);
     $field_name = $field['field_name'];
-    $field_data = $data[$field['id']];
+    $field_data = $data[$field['uuid']];
     $sub_table = $load_current ? 'current' : 'revisions';
     $delta_count = array();
     foreach ($field_data[$sub_table] as $row) {
@@ -206,7 +206,7 @@ function field_test_field_storage_delete(EntityInterface $entity, $fields) {
 function field_test_field_storage_purge(EntityInterface $entity, $field, $instance) {
   $data = _field_test_storage_data();
 
-  $field_data = &$data[$field['id']];
+  $field_data = &$data[$field['uuid']];
   foreach (array('current', 'revisions') as $sub_table) {
     foreach ($field_data[$sub_table] as $key => $row) {
       if ($row->type == $entity->entityType() && $row->entity_id == $entity->id()) {
@@ -249,7 +249,7 @@ function field_test_field_storage_query($field_id, $conditions, $count, &$cursor
   $field = field_info_field_by_id($field_id);
   $field_columns = array_keys($field['columns']);
 
-  $field_data = $data[$field['id']];
+  $field_data = $data[$field['uuid']];
   $sub_table = $load_current ? 'current' : 'revisions';
   // We need to sort records by entity type and entity id.
   usort($field_data[$sub_table], '_field_test_field_storage_query_sort_helper');
@@ -267,7 +267,7 @@ function field_test_field_storage_query($field_id, $conditions, $count, &$cursor
       break;
     }
 
-    if ($row->field_id == $field['id']) {
+    if ($row->field_id == $field['uuid']) {
       $match = TRUE;
       $condition_deleted = FALSE;
       // Add conditions.
@@ -372,7 +372,7 @@ function field_test_field_storage_create_field($field) {
 
   $data = _field_test_storage_data();
 
-  $data[$field['id']] = array(
+  $data[$field['uuid']] = array(
     'current' => array(),
     'revisions' => array(),
   );
@@ -386,7 +386,7 @@ function field_test_field_storage_create_field($field) {
 function field_test_field_storage_delete_field($field) {
   $data = _field_test_storage_data();
 
-  $field_data = &$data[$field['id']];
+  $field_data = &$data[$field['uuid']];
   foreach (array('current', 'revisions') as $sub_table) {
     foreach ($field_data[$sub_table] as &$row) {
       $row->deleted = TRUE;
@@ -403,7 +403,7 @@ function field_test_field_storage_delete_instance($instance) {
   $data = _field_test_storage_data();
 
   $field = field_info_field($instance['field_name']);
-  $field_data = &$data[$field['id']];
+  $field_data = &$data[$field['uuid']];
   foreach (array('current', 'revisions') as $sub_table) {
     foreach ($field_data[$sub_table] as &$row) {
       if ($row->bundle == $instance['bundle']) {
@@ -425,8 +425,8 @@ function field_test_entity_bundle_rename($entity_type, $bundle_old, $bundle_new)
   $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
   foreach ($instances as $field_name => $instance) {
     $field = field_info_field_by_id($instance['field_id']);
-    if ($field['storage']['type'] == 'field_test_storage') {
-      $field_data = &$data[$field['id']];
+    if ($field && $field['storage']['type'] == 'field_test_storage') {
+      $field_data = &$data[$field['uuid']];
       foreach (array('current', 'revisions') as $sub_table) {
         foreach ($field_data[$sub_table] as &$row) {
           if ($row->bundle == $bundle_old) {
@@ -448,7 +448,7 @@ function field_test_field_delete_instance($instance) {
 
   $field = field_info_field($instance['field_name']);
   if ($field['storage']['type'] == 'field_test_storage') {
-    $field_data = &$data[$field['id']];
+    $field_data = &$data[$field['uuid']];
     foreach (array('current', 'revisions') as $sub_table) {
       foreach ($field_data[$sub_table] as &$row) {
         if ($row->bundle == $instance['bundle']) {
diff --git a/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml
new file mode 100644
index 000000000000..2f8963d950c6
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/config/field.field.field_test_import.yml
@@ -0,0 +1,20 @@
+id: field_test_import
+uuid: fb38277f-1fd4-49d5-8d09-9d7037fdcce9
+langcode: und
+type: text
+settings:
+  max_length: '255'
+module: text
+active: 1
+entity_types: {  }
+storage:
+  type: field_sql_storage
+  settings: {  }
+  module: field_sql_storage
+  active: 1
+locked: '0'
+cardinality: '1'
+translatable: false
+indexes:
+  format:
+    - format
diff --git a/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml b/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml
new file mode 100644
index 000000000000..3db6847d8e9e
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/config/field.instance.test_entity.test_bundle.field_test_import.yml
@@ -0,0 +1,20 @@
+id: test_entity.test_bundle.field_test_import
+uuid: 392b4e9d-6157-412e-9603-3d622512f498
+langcode: und
+field_uuid: fb38277f-1fd4-49d5-8d09-9d7037fdcce9
+entity_type: test_entity
+bundle: test_bundle
+label: 'Test import field'
+description: ''
+required: 0
+default_value: {  }
+default_value_function: ''
+settings:
+  text_processing: '0'
+  user_register_form: false
+widget:
+  weight: '-2'
+  type: text_textfield
+  module: text
+  settings:
+    size: '60'
diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml b/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml
new file mode 100644
index 000000000000..57e0849e5fa5
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/field_test_config.info.yml
@@ -0,0 +1,6 @@
+name: 'Field API configuration tests'
+description: 'Support module for the Field API configuration tests.'
+core: 8.x
+package: Testing
+version: VERSION
+hidden: TRUE
diff --git a/core/modules/field/tests/modules/field_test_config/field_test_config.module b/core/modules/field/tests/modules/field_test_config/field_test_config.module
new file mode 100644
index 000000000000..3d7730d08e1f
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/field_test_config.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Helper module for the Field API configuration tests.
+ */
diff --git a/core/modules/field/tests/modules/field_test_config/staging/field.field.field_test_import_staging.yml b/core/modules/field/tests/modules/field_test_config/staging/field.field.field_test_import_staging.yml
new file mode 100644
index 000000000000..4226f72e1c7a
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/staging/field.field.field_test_import_staging.yml
@@ -0,0 +1,20 @@
+id: field_test_import_staging
+uuid: 0bf654cc-f14a-4881-b94c-76959e47466b
+langcode: und
+type: text
+settings:
+  max_length: '255'
+module: text
+active: '1'
+entity_types: {  }
+storage:
+  type: field_sql_storage
+  settings: {  }
+  module: field_sql_storage
+  active: '1'
+locked: '0'
+cardinality: '1'
+translatable: '0'
+indexes:
+  format:
+    - format
diff --git a/core/modules/field/tests/modules/field_test_config/staging/field.instance.test_entity.test_bundle.field_test_import_staging.yml b/core/modules/field/tests/modules/field_test_config/staging/field.instance.test_entity.test_bundle.field_test_import_staging.yml
new file mode 100644
index 000000000000..57452778de2b
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test_config/staging/field.instance.test_entity.test_bundle.field_test_import_staging.yml
@@ -0,0 +1,20 @@
+id: test_entity.test_bundle.field_test_import_staging
+uuid: ea711065-6940-47cd-813d-618f64095481
+langcode: und
+field_uuid: 0bf654cc-f14a-4881-b94c-76959e47466b
+entity_type: test_entity
+bundle: test_bundle
+label: 'Import from staging'
+description: ''
+required: '0'
+default_value: {  }
+default_value_function: ''
+settings:
+  text_processing: '0'
+  user_register_form: '0'
+widget:
+  type: text_textfield
+  weight: '-3'
+  settings:
+    size: '60'
+  module: text
diff --git a/core/modules/field_sql_storage/field_sql_storage.install b/core/modules/field_sql_storage/field_sql_storage.install
index 56f6a4f86ee0..4bf79dffb14e 100644
--- a/core/modules/field_sql_storage/field_sql_storage.install
+++ b/core/modules/field_sql_storage/field_sql_storage.install
@@ -5,22 +5,31 @@
  * Install, update, and uninstall functions for the Field SQL Storage module.
  */
 
+use Drupal\field\Plugin\Core\Entity\Field;
+
 /**
  * Implements hook_schema().
  */
 function field_sql_storage_schema() {
   $schema = array();
 
-  // Dynamic (data) tables.
-  if (db_table_exists('field_config')) {
-    $fields = field_read_fields(array(), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
-    drupal_load('module', 'field_sql_storage');
-    foreach ($fields as $field) {
-      if ($field['storage']['type'] == 'field_sql_storage') {
-        $schema += _field_sql_storage_schema($field);
-      }
+  // Loading entities within hook_schema() triggers lots of race conditions.
+  // Read definitions for raw storage instead (configuration, and state for
+  // deleted fields).
+  $fields = array();
+  foreach (config_get_storage_names_with_prefix('field.field') as $name) {
+    $fields[] = config($name)->get();
+  }
+  $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
+  $fields = array_merge($fields, $deleted_fields);
+
+  foreach ($fields as $field) {
+    if ($field['storage']['type'] == 'field_sql_storage') {
+      $field = new Field($field);
+      $schema += _field_sql_storage_schema($field);
     }
   }
+
   return $schema;
 }
 
@@ -79,31 +88,58 @@ function _update_8000_field_sql_storage_write($entity_type, $bundle, $entity_id,
 }
 
 /**
- * Changes field language into langcode.
+ * Implements hook_update_dependencies().
+ */
+function field_sql_storage_update_dependencies() {
+  // Convert storage tables after field definitions have moved to
+  // ConfigEntities.
+  $dependencies['field_sql_storage'][8000] = array(
+    'field' => 8003,
+  );
+  return $dependencies;
+}
+
+/**
+ * Renames the 'language' column to 'langcode' in field data tables.
  */
 function field_sql_storage_update_8000(&$sandbox) {
-  if (!isset($sandbox['progress'])) {
-    $sandbox['progress'] = 0;
-    $sandbox['last'] = 0;
-    $sandbox['max'] = db_query("SELECT COUNT(id) FROM {field_config} WHERE storage_type = 'field_sql_storage'")->fetchField();
+  // Get field definitions from config, and deleted fields from state().
+  $config_names = config_get_storage_names_with_prefix('field.field');
+  $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
+  // Ditch UUID keys, we will iterate through deleted fields using a numeric
+  // index.
+  $deleted_fields = array_values($deleted_fields);
+
+  if (empty($config_names) && empty($deleted_fields)) {
+    return;
+  }
+
+  if (!isset($sandbox['index'])) {
+    $sandbox['index'] = 0;
+    $sandbox['max'] = count($config_names) + count($deleted_fields);
   }
 
-  // Retrieve field data.
-  $field = db_query("SELECT id, field_name, deleted FROM {field_config} WHERE id > :id AND storage_type = 'field_sql_storage'", array(':id' => $sandbox['last']))->fetchAssoc();
-  if ($field) {
+  // Retrieve the next field definition. When the index exceeds the number of
+  // 'configuration' fields, use it to iterate on deleted fields.
+  if (isset($config_names[$sandbox['index']])) {
+    $field_config = config($config_names[$sandbox['index']])->get();
+  }
+  else {
+    $field_config = $deleted_fields[$sandbox['index'] - count($config_names)];
+  }
 
-    $sandbox['progress']++;
-    $sandbox['last'] = $field['id'];
+  if ($field_config['storage']['type'] == 'field_sql_storage') {
+    $field = new Field($field_config);
 
     // Prepare updated schema data structures.
-    $primary_key_data = array (
+    $primary_key_data = array(
       'entity_type',
       'entity_id',
       'deleted',
       'delta',
       'langcode',
     );
-    $primary_key_revision = array (
+    $primary_key_revision = array(
       'entity_type',
       'entity_id',
       'revision_id',
@@ -117,14 +153,14 @@ function field_sql_storage_update_8000(&$sandbox) {
     $field_langcode = array(
       'type' => 'varchar',
       'length' => 32,
-      'not null' => true,
+      'not null' => TRUE,
       'default' => '',
     );
 
-    $data_table = _field_sql_storage_tablename($field);
-    $revision_table = _field_sql_storage_revision_tablename($field);
-    $table_info = array($data_table => $primary_key_data, $revision_table => $primary_key_revision);
-
+    $table_info = array(
+      _field_sql_storage_tablename($field) => $primary_key_data,
+      _field_sql_storage_revision_tablename($field) => $primary_key_revision,
+    );
     foreach ($table_info as $table => $primary_key) {
       // Do not update tables which already have the langcode column,
       // created during the upgrade before this update function.
@@ -138,5 +174,6 @@ function field_sql_storage_update_8000(&$sandbox) {
     }
   }
 
-  $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
+  $sandbox['index']++;
+  $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['index'] / $sandbox['max']);
 }
diff --git a/core/modules/field_sql_storage/field_sql_storage.module b/core/modules/field_sql_storage/field_sql_storage.module
index 193df67485d9..e8afd3c8b163 100644
--- a/core/modules/field_sql_storage/field_sql_storage.module
+++ b/core/modules/field_sql_storage/field_sql_storage.module
@@ -37,6 +37,11 @@ function field_sql_storage_field_storage_info() {
 /**
  * Generates a table name for a field data table.
  *
+ * When a field is a deleted, the table is renamed to
+ * {field_deleted_data_FIELD_UUID}. To make sure we don't end up with table
+ * names longer than 64 characters, we hash the uuid and return the first 10
+ * characters so we end up with a short unique ID.
+ *
  * @param $field
  *   The field structure.
  *
@@ -45,7 +50,7 @@ function field_sql_storage_field_storage_info() {
  */
 function _field_sql_storage_tablename($field) {
   if ($field['deleted']) {
-    return "field_deleted_data_{$field['id']}";
+    return "field_deleted_data_" . substr(hash('sha256', $field['uuid']), 0, 10);
   }
   else {
     return "field_data_{$field['field_name']}";
@@ -55,6 +60,11 @@ function _field_sql_storage_tablename($field) {
 /**
  * Generates a table name for a field revision archive table.
  *
+ * When a field is a deleted, the table is renamed to
+ * {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with table
+ * names longer than 64 characters, we hash the uuid and return the first
+ * 10 characters so we end up with a short unique ID.
+ *
  * @param $name
  *   The field structure.
  *
@@ -63,7 +73,7 @@ function _field_sql_storage_tablename($field) {
  */
 function _field_sql_storage_revision_tablename($field) {
   if ($field['deleted']) {
-    return "field_deleted_revision_{$field['id']}";
+    return "field_deleted_revision_" . substr(hash('sha256', $field['uuid']), 0, 10);
   }
   else {
     return "field_revision_{$field['field_name']}";
@@ -178,15 +188,16 @@ function _field_sql_storage_schema($field) {
     ),
   );
 
-  $field += array('columns' => array(), 'indexes' => array(), 'foreign keys' => array());
+  $schema = $field->getSchema();
+
   // Add field columns.
-  foreach ($field['columns'] as $column_name => $attributes) {
+  foreach ($schema['columns'] as $column_name => $attributes) {
     $real_name = _field_sql_storage_columnname($field['field_name'], $column_name);
     $current['fields'][$real_name] = $attributes;
   }
 
   // Add indexes.
-  foreach ($field['indexes'] as $index_name => $columns) {
+  foreach ($schema['indexes'] as $index_name => $columns) {
     $real_name = _field_sql_storage_indexname($field['field_name'], $index_name);
     foreach ($columns as $column_name) {
       // Indexes can be specified as either a column name or an array with
@@ -204,7 +215,7 @@ function _field_sql_storage_schema($field) {
   }
 
   // Add foreign keys.
-  foreach ($field['foreign keys'] as $specifier => $specification) {
+  foreach ($schema['foreign keys'] as $specifier => $specification) {
     $real_name = _field_sql_storage_indexname($field['field_name'], $specifier);
     $current['foreign keys'][$real_name]['table'] = $specification['table'];
     foreach ($specification['columns'] as $column => $referenced) {
@@ -234,7 +245,18 @@ function field_sql_storage_field_storage_create_field($field) {
   foreach ($schema as $name => $table) {
     db_create_table($name, $table);
   }
-  drupal_get_schema(NULL, TRUE);
+  // Do not rebuild the schema right now, since the field definition has not
+  // been saved yet. This will be done in hook_field_create_field().
+}
+
+/**
+ * Implements hook_field_create_field().
+ */
+function field_sql_storage_field_create_field($field) {
+  // Rebuild the schema now that the field has been saved.
+  if ($field['storage']['type'] == 'field_sql_storage') {
+    drupal_get_schema(NULL, TRUE);
+  }
 }
 
 /**
@@ -294,8 +316,12 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has
     // priors that exist unchanged.
     $table = _field_sql_storage_tablename($prior_field);
     $revision_table = _field_sql_storage_revision_tablename($prior_field);
-    foreach ($prior_field['indexes'] as $name => $columns) {
-      if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) {
+
+    $schema = $field->getSchema();
+    $prior_schema = $prior_field->getSchema();
+
+    foreach ($prior_schema['indexes'] as $name => $columns) {
+      if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
         $real_name = _field_sql_storage_indexname($field['field_name'], $name);
         db_drop_index($table, $real_name);
         db_drop_index($revision_table, $real_name);
@@ -303,8 +329,8 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has
     }
     $table = _field_sql_storage_tablename($field);
     $revision_table = _field_sql_storage_revision_tablename($field);
-    foreach ($field['indexes'] as $name => $columns) {
-      if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) {
+    foreach ($schema['indexes'] as $name => $columns) {
+      if (!isset($prior_schema['indexes'][$name]) || $columns != $prior_schema['indexes'][$name]) {
         $real_name = _field_sql_storage_indexname($field['field_name'], $name);
         $real_columns = array();
         foreach ($columns as $column_name) {
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
index 5ea9c88c9844..f31c63e87776 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
@@ -406,10 +406,10 @@ function testFieldStorageDetails() {
     $instance = field_info_instance($this->instance['entity_type'], $this->instance['field_name'], $this->instance['bundle']);
 
     // The storage details are indexed by a storage engine type.
-    $this->assertTrue(array_key_exists('sql', $field['storage']['details']), 'The storage type is SQL.');
+    $this->assertTrue(array_key_exists('sql', $field['storage_details']), 'The storage type is SQL.');
 
     // The SQL details are indexed by table name.
-    $details = $field['storage']['details']['sql'];
+    $details = $field['storage_details']['sql'];
     $this->assertTrue(array_key_exists($current, $details[FIELD_LOAD_CURRENT]), 'Table name is available in the instance array.');
     $this->assertTrue(array_key_exists($revision, $details[FIELD_LOAD_REVISION]), 'Revision table name is available in the instance array.');
 
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index d58cc60e7302..d73ffacac6ce 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -5,7 +5,7 @@
  * Administrative interface for custom field type creation.
  */
 
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\field_ui\FieldOverview;
 use Drupal\field_ui\DisplayOverview;
 
@@ -609,14 +609,15 @@ function field_ui_field_settings_form_submit($form, &$form_state) {
 
   // Merge incoming form values into the existing field.
   $field = field_info_field($field_values['field_name']);
+  foreach ($field_values as $key => $value) {
+    $field[$key] = $value;
+  }
 
   $entity_type = $form['#entity_type'];
   $bundle = $form['#bundle'];
   $instance = field_info_instance($entity_type, $field['field_name'], $bundle);
 
   // Update the field.
-  $field = array_merge($field, $field_values);
-
   try {
     field_update_field($field);
     drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label'])));
@@ -863,14 +864,6 @@ function field_ui_field_edit_form($form, &$form_state, $instance) {
     '#type' => 'value',
     '#value' => $instance['widget']['type'],
   );
-  $form['instance']['widget']['module'] = array(
-    '#type' => 'value',
-    '#value' => $widget_type['module'],
-  );
-  $form['instance']['widget']['active'] = array(
-    '#type' => 'value',
-    '#value' => !empty($field['instance']['widget']['active']) ? 1 : 0,
-  );
 
   // Add additional field instance settings from the field module.
   $additions = module_invoke($field['module'], 'field_instance_settings_form', $field, $instance, $form_state);
@@ -953,7 +946,10 @@ function field_ui_default_value_widget($field, $instance, &$form, &$form_state)
 
   // Insert the widget. Since we do not use the "official" instance definition,
   // the whole flow cannot use field_invoke_method().
-  $items = (array) $instance['default_value'];
+  $items = array();
+  if (!empty($instance['default_value'])) {
+    $items = (array) $instance['default_value'];
+  }
   $element += $instance->getWidget()->form($entity, LANGUAGE_NOT_SPECIFIED, $items, $element, $form_state);
 
   return $element;
diff --git a/core/modules/file/file.install b/core/modules/file/file.install
index 27a243825bf6..36ce089d91f5 100644
--- a/core/modules/file/file.install
+++ b/core/modules/file/file.install
@@ -125,8 +125,8 @@ function file_schema() {
       ),
       'id' => array(
         'description' => 'The primary key of the object using the file.',
-        'type' => 'int',
-        'unsigned' => TRUE,
+        'type' => 'varchar',
+        'length' => 64,
         'not null' => TRUE,
         'default' => 0,
       ),
@@ -245,3 +245,17 @@ function file_update_8000() {
     'file_icon_directory'=>'icon.directory',
   ));
 }
+
+/**
+ * Convert the 'id' column in {file_usage} to accept UUIDs.
+ */
+function file_update_8001() {
+  $spec = array(
+    'description' => 'The primary key of the object using the file.',
+    'type' => 'varchar',
+    'length' => 64,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  db_change_field('file_usage', 'id', 'id', $spec);
+}
diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php
index 05b0cadd95d7..352ce4b5be04 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php
@@ -88,8 +88,14 @@ function testSingleValuedWidget() {
    */
   function testMultiValuedWidget() {
     $type_name = 'article';
-    $field_name = strtolower($this->randomName());
-    $field_name2 = strtolower($this->randomName());
+    // Use explicit names instead of random names for those fields, because of a
+    // bug in drupalPost() with multiple file uploads in one form, where the
+    // order of uploads depends on the order in which the upload elements are
+    // added to the $form (which, in the current implementation of
+    // FileStorage::listAll(), comes down to the alphabetical order on field
+    // names).
+    $field_name = 'test_file_field_1';
+    $field_name2 = 'test_file_field_2';
     $this->createFileField($field_name, $type_name, array('cardinality' => 3));
     $this->createFileField($field_name2, $type_name, array('cardinality' => 3));
 
@@ -261,6 +267,9 @@ function testPrivateFileComment() {
     $this->drupalPost(NULL, $edit, t('Save field settings'));
     $this->drupalPost(NULL, array(), t('Save settings'));
 
+    // Manually clear cache on the tester side.
+    field_info_cache_clear();
+
     // Create node.
     $text_file = $this->getTestFile('text');
     $edit = array(
diff --git a/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php b/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php
index 1e6eef506a1f..2ec9dfa21915 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FileItemTest.php
@@ -54,9 +54,6 @@ public function setUp() {
       'entity_type' => 'entity_test',
       'field_name' => 'file_test',
       'bundle' => 'entity_test',
-      'widget' => array(
-        'type' => 'options_select',
-      ),
     );
     field_create_instance($instance);
     file_put_contents('public://example.txt', $this->randomName());
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index b1e63de58174..1bc691517e7f 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -22,11 +22,6 @@ function forum_install() {
  * Implements hook_enable().
  */
 function forum_enable() {
-  // If we enable forum at the same time as taxonomy we need to call
-  // field_associate_fields() as otherwise the field won't be enabled until
-  // hook modules_enabled is called which takes place after hook_enable events.
-  field_associate_fields('taxonomy');
-
   // Create the forum vocabulary if it does not exist.
   // @todo Change Forum module so forum.settings can contain the vocabulary's
   //   machine name.
@@ -54,8 +49,10 @@ function forum_enable() {
     $config->set('vocabulary', $vocabulary->id())->save();
   }
 
-  // Create the 'taxonomy_forums' field if it doesn't already exist.
-  if (!field_info_field('taxonomy_forums')) {
+  // Create the 'taxonomy_forums' field if it doesn't already exist. If forum
+  // is being enabled at the same time as taxonomy after both modules have been
+  // enabled, the field might exist but still be marked inactive.
+  if (!field_read_field('taxonomy_forums', array('include_inactive' => TRUE))) {
     $field = array(
       'field_name' => 'taxonomy_forums',
       'type' => 'taxonomy_term_reference',
diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
index cbab209b0f44..caecabf06718 100644
--- a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
+++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
@@ -59,7 +59,6 @@ abstract class NormalizerTestBase extends DrupalUnitTestBase {
   function setUp() {
     parent::setUp();
     $this->installSchema('system', array('variable', 'url_alias'));
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     $this->installSchema('user', array('users'));
     $this->installSchema('language', array('language'));
     $this->installSchema('entity_test', array('entity_test'));
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index b963a453025d..017ce4243edd 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -383,12 +383,12 @@ function image_field_update_field($field, $prior_field, $has_data) {
     if ($file_new) {
       $file_new->status = FILE_STATUS_PERMANENT;
       $file_new->save();
-      file_usage()->add($file_new, 'image', 'default_image', $field['id']);
+      file_usage()->add($file_new, 'image', 'default_image', $field['uuid']);
     }
 
     // Is there an old file?
     if ($fid_old && ($file_old = file_load($fid_old))) {
-      file_usage()->delete($file_old, 'image', 'default_image', $field['id']);
+      file_usage()->delete($file_old, 'image', 'default_image', $field['uuid']);
     }
   }
 
@@ -451,11 +451,11 @@ function image_field_update_instance($instance, $prior_instance) {
     if ($file_new) {
       $file_new->status = FILE_STATUS_PERMANENT;
       $file_new->save();
-      file_usage()->add($file_new, 'image', 'default_image', $instance['id']);
+      file_usage()->add($file_new, 'image', 'default_image', $instance['uuid']);
     }
     // Delete the old file, if present.
     if ($fid_old && ($file_old = file_load($fid_old))) {
-      file_usage()->delete($file_old, 'image', 'default_image', $instance['id']);
+      file_usage()->delete($file_old, 'image', 'default_image', $instance['uuid']);
     }
   }
 
diff --git a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php
index b1863213fffc..c33134babed9 100644
--- a/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/Condition/NodeConditionTest.php
@@ -29,8 +29,6 @@ protected function setUp() {
     $this->installSchema('node', 'node_type');
     $this->installSchema('node', 'node');
     $this->installSchema('node', 'node_revision');
-    $this->installSchema('field', 'field_config');
-    $this->installSchema('field', 'field_config_instance');
   }
 
   /**
diff --git a/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php b/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php
index 925828cdf612..aa6c06e18a50 100644
--- a/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php
+++ b/core/modules/number/lib/Drupal/number/Tests/NumberFieldTest.php
@@ -63,11 +63,6 @@ function testNumberDecimalField() {
           'placeholder' => '0.00'
         ),
       ),
-      'display' => array(
-        'default' => array(
-          'type' => 'number_decimal',
-        ),
-      ),
     );
     field_create_instance($this->instance);
     entity_get_display('test_entity', 'test_bundle', 'default')
diff --git a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php
index ca9d605067e3..11ab663ef48a 100644
--- a/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php
+++ b/core/modules/options/lib/Drupal/options/Tests/OptionsFieldTest.php
@@ -36,7 +36,7 @@ function setUp() {
 
 
     $this->field_name = 'test_options';
-    $this->field = array(
+    $this->field_definition = array(
       'field_name' => $this->field_name,
       'type' => 'list_integer',
       'cardinality' => 1,
@@ -44,7 +44,7 @@ function setUp() {
         'allowed_values' => array(1 => 'One', 2 => 'Two', 3 => 'Three'),
       ),
     );
-    $this->field = field_create_field($this->field);
+    $this->field = field_create_field($this->field_definition);
 
     $this->instance = array(
       'field_name' => $this->field_name,
@@ -109,8 +109,7 @@ function testUpdateAllowedValues() {
     // Options are reset when a new field with the same name is created.
     field_delete_field($this->field_name);
     unset($this->field['id']);
-    $this->field['settings']['allowed_values'] = array(1 => 'One', 2 => 'Two', 3 => 'Three');
-    $this->field = field_create_field($this->field);
+    field_create_field($this->field_definition);
     $this->instance = array(
       'field_name' => $this->field_name,
       'entity_type' => 'entity_test',
@@ -119,7 +118,7 @@ function testUpdateAllowedValues() {
         'type' => 'options_buttons',
       ),
     );
-    $this->instance = field_create_instance($this->instance);
+    field_create_instance($this->instance);
     $entity = entity_create('entity_test', array());
     $form = entity_get_form($entity);
     $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), 'Option 1 exists');
diff --git a/core/modules/options/options.module b/core/modules/options/options.module
index 05cbed14f951..8572e7415b5b 100644
--- a/core/modules/options/options.module
+++ b/core/modules/options/options.module
@@ -252,7 +252,7 @@ function options_field_update_field($field, $prior_field, $has_data) {
 function options_allowed_values($field, $instance = NULL, EntityInterface $entity = NULL) {
   $allowed_values = &drupal_static(__FUNCTION__, array());
 
-  if (!isset($allowed_values[$field['id']])) {
+  if (!isset($allowed_values[$field['uuid']])) {
     $function = $field['settings']['allowed_values_function'];
     // If $cacheable is FALSE, then the allowed values are not statically
     // cached. See options_test_dynamic_values_callback() for an example of
@@ -266,14 +266,14 @@ function options_allowed_values($field, $instance = NULL, EntityInterface $entit
     }
 
     if ($cacheable) {
-      $allowed_values[$field['id']] = $values;
+      $allowed_values[$field['uuid']] = $values;
     }
     else {
       return $values;
     }
   }
 
-  return $allowed_values[$field['id']];
+  return $allowed_values[$field['uuid']];
 }
 
 /**
@@ -388,7 +388,7 @@ function options_field_update_forbid($field, $prior_field, $has_data) {
  */
 function _options_values_in_use($field, $values) {
   if ($values) {
-    $field = field_info_field_by_id($field['id']);
+    $field = field_info_field_by_id($field['uuid']);
     $factory = Drupal::service('entity.query');
     foreach ($field['bundles'] as $entity_type => $bundle) {
       $result = $factory->get($entity_type)
@@ -477,6 +477,9 @@ function options_field_widget_info() {
 function options_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
   // Abstract over the actual field columns, to allow different field types to
   // reuse those widgets.
+
+  // Reset internal pointer since we're dealing with objects now.
+  reset($field['columns']);
   $value_key = key($field['columns']);
 
   $type = str_replace('options_', '', $instance['widget']['type']);
diff --git a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
index fb7019e0bd89..33802db0ac12 100644
--- a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
+++ b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
@@ -57,7 +57,6 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     $this->installSchema('entity_test', array('entity_test_mulrev', 'entity_test_mulrev_property_revision', 'entity_test_mulrev_property_data'));
 
     // Auto-create a field for testing.
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
index ad746d596a7f..2379193eaef8 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
@@ -108,10 +108,7 @@ function testEnableModulesInstall() {
    */
   function testEnableModulesInstallContainer() {
     // Install Node module.
-    // @todo field_sql_storage and field should technically not be necessary
-    //   for an entity query.
     $this->enableModules(array('field_sql_storage', 'field', 'node'));
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
 
     $this->installSchema('node', array('node_type', 'node'));
     // Perform an entity query against node.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php
index c0f181a541b3..a997a346a10b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Database/SelectComplexTest.php
@@ -27,11 +27,6 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
-    parent::setUp();
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
-  }
-
   /**
    * Tests simple JOIN statements.
    */
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
index b747e79dcee6..c3f236b17844 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
@@ -25,7 +25,6 @@ public function setUp() {
     parent::setUp();
     $this->installSchema('user', 'users');
     $this->installSchema('system', 'sequences');
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     $this->installSchema('entity_test', 'entity_test');
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php
index 09e7a7a9e98e..5e08eed64db1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldAccessTest.php
@@ -41,8 +41,6 @@ public static function getInfo() {
 
   protected function setUp() {
     parent::setUp();
-    // Install field module schema.
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     // The users table is needed for creating dummy user accounts.
     $this->installSchema('user', array('users'));
     // Register entity_test text field.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php
index c0de21bb7299..38c57d62a79d 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php
@@ -87,4 +87,132 @@ public function testEntityDisplayUpgrade() {
     $this->assertEqual($displays['teaser']['content']['language'], $expected['teaser']);
   }
 
+  /**
+   * Tests migration of field and instance definitions to config.
+   */
+  function testFieldUpgradeToConfig() {
+    $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
+
+    $field_manifest = config('manifest.field.field')->get();
+    $instance_manifest = config('manifest.field.instance')->get();
+
+    // Check that the configuration for the 'body' field is correct.
+    $config = \Drupal::config('field.field.body')->get();
+    // We cannot predict the value of the UUID, we just check it's present.
+    $this->assertFalse(empty($config['uuid']));
+    $field_uuid = $config['uuid'];
+    unset($config['uuid']);
+    $this->assertEqual($config, array(
+      'id' => 'body',
+      'type' => 'text_with_summary',
+      'module' => 'text',
+      'active' => '1',
+      'settings' => array(),
+      'storage' => array(
+        'type' => 'field_sql_storage',
+        'module' => 'field_sql_storage',
+        'active' => '1',
+        'settings' => array(),
+      ),
+      'locked' => 0,
+      'cardinality' => 1,
+      'translatable' => 0,
+      'entity_types' => array('node'),
+      'indexes' => array(
+        'format' => array('format')
+      ),
+      'status' => 1,
+      'langcode' => 'und',
+    ));
+    // Check that an entry is present in the manifest.
+    $this->assertEqual($field_manifest['body']['name'], 'field.field.body');
+
+    // Check that the configuration for the instance on article and page nodes
+    // is correct.
+    foreach (array('article', 'page') as $node_type) {
+      $config = config("field.instance.node.$node_type.body")->get();
+      // We cannot predict the value of the UUID, we just check it's present.
+      $this->assertFalse(empty($config['uuid']));
+      unset($config['uuid']);
+      $this->assertEqual($config, array(
+        'id' => "node.$node_type.body",
+        'field_uuid' => $field_uuid,
+        'entity_type' => 'node',
+        'bundle' => $node_type,
+        'label' => 'Body',
+        'description' => '',
+        'required' => FALSE,
+        'default_value' => array(),
+        'default_value_function' => '',
+        'settings' => array(
+          'display_summary' => TRUE,
+          'text_processing' => 1,
+          'user_register_form' => FALSE,
+        ),
+        'widget' => array(
+          'type' => 'text_textarea_with_summary',
+          'module' => 'text',
+          'settings' => array(
+            'rows' => 20,
+            'summary_rows' => 5,
+          ),
+          'weight' => -4,
+        ),
+        'status' => 1,
+        'langcode' => 'und',
+      ));
+      // Check that an entry is present in the manifest.
+      $this->assertEqual($instance_manifest["node.$node_type.body"]['name'], "field.instance.node.$node_type.body");
+    }
+
+    // Check that field values in a pre-existing node are read correctly.
+    $body = node_load(1)->get('body');
+    $this->assertEqual($body->value, 'Some value');
+    $this->assertEqual($body->summary, 'Some summary');
+    $this->assertEqual($body->format, 'filtered_html');
+
+    // Check that the definition of a deleted field is stored in state rather
+    // than config.
+    $this->assertFalse(\Drupal::config('field.field.test_deleted_field')->get());
+    // The array is keyed by UUID. We cannot predict the UUID of the
+    // 'test_deleted_field' field, but assume there was only one deleted field
+    // in the test database.
+    $deleted_fields = \Drupal::state()->get('field.field.deleted');
+    $uuid_key = key($deleted_fields);
+    $deleted_field = $deleted_fields[$uuid_key];
+    $this->assertEqual($deleted_field['uuid'], $uuid_key);
+    $this->assertEqual($deleted_field['id'], 'test_deleted_field');
+
+    // Check that the definition of a deleted instance is stored in state rather
+    // than config.
+    $this->assertFalse(\Drupal::config('field.instance.node.article.test_deleted_field')->get());
+    $deleted_instances = \Drupal::state()->get('field.instance.deleted');
+    // Assume there was only one deleted instance in the test database.
+    $uuid_key = key($deleted_instances);
+    $deleted_instance = $deleted_instances[$uuid_key];
+    $this->assertEqual($deleted_instance['uuid'], $uuid_key);
+    $this->assertEqual($deleted_instance['id'], 'node.article.test_deleted_field');
+    // The deleted field uuid and deleted instance field_uuid must match.
+    $this->assertEqual($deleted_field['uuid'], $deleted_instance['field_uuid']);
+
+    // Check that pre-existing deleted field values are read correctly.
+    $entity = _field_create_entity_from_ids((object) array(
+      'entity_type' => 'node',
+      'bundle' => 'article',
+      'entity_id' => 2,
+      'revision_id' => 2,
+    ));
+    field_attach_load('node', array(2 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $deleted_field['uuid'], 'deleted' => 1));
+    $deleted_value = $entity->get('test_deleted_field');
+    $this->assertEqual($deleted_value[LANGUAGE_NOT_SPECIFIED][0]['value'], 'Some deleted value');
+
+    // Check that creation of a new node works as expected.
+    $value = $this->randomName();
+    $edit = array(
+      'title' => 'Node after CMI conversion',
+      'body[und][0][value]' => $value,
+    );
+    $this->drupalPost('node/add/article', $edit, 'Save and publish');
+    $this->assertText($value);
+  }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php
index 04070c922bba..23eb3da2335d 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php
@@ -51,7 +51,7 @@ public function testUserPictureUpgrade() {
     // Check file usage for the default image.
     $usage = file_usage()->listUsage($file);
     $field = field_info_field('user_picture');
-    $this->assertEqual(1, $usage['image']['default_image'][$field['id']]);
+    $this->assertTrue(isset($usage['image']['default_image'][$field['uuid']]));
 
     $this->assertEqual($instance['settings']['max_resolution'], '800x800', 'User picture maximum resolution has been migrated.');
     $this->assertEqual($instance['settings']['max_filesize'], '700 KB', 'User picture maximum filesize has been migrated.');
diff --git a/core/modules/system/tests/upgrade/drupal-7.field.database.php b/core/modules/system/tests/upgrade/drupal-7.field.database.php
index 0862650bb973..9434c7cf8cb7 100644
--- a/core/modules/system/tests/upgrade/drupal-7.field.database.php
+++ b/core/modules/system/tests/upgrade/drupal-7.field.database.php
@@ -49,3 +49,338 @@
     'value' => serialize($value),
   ))
   ->execute();
+
+// Add one node.
+db_insert('node')
+  ->fields(array(
+    'nid' => '1',
+    'vid' => '1',
+    'type' => 'article',
+    'language' => 'und',
+    'title' => 'node title 1 rev 1',
+    'uid' => '1',
+    'status' => '1',
+    'created' => '1262754000',
+    'changed' => '1338795201',
+    'comment' => '0',
+    'promote' => '1',
+    'sticky' => '0',
+    'tnid' => '0',
+    'translate' => '0',
+  ))
+  ->execute();
+db_insert('node_revision')
+  ->fields(array(
+    'nid' => '1',
+    'vid' => '1',
+    'uid' => '1',
+    'title' => 'node title 1 rev 1',
+    'log' => 'added 0 node',
+    'timestamp' => '1338795201',
+    'status' => '1',
+    'comment' => '0',
+    'promote' => '1',
+    'sticky' => '0',
+  ))
+  ->execute();
+
+$field_data_row = array(
+  'entity_type' => 'node',
+  'bundle' => 'article',
+  'deleted' => '0',
+  'entity_id' => '1',
+  'revision_id' => '1',
+  'language' => 'und',
+  'delta' => '0',
+  'body_value' => 'Some value',
+  'body_summary' => 'Some summary',
+  'body_format' => 'filtered_html',
+);
+db_insert('field_data_body')
+  ->fields($field_data_row)
+  ->execute();
+db_insert('field_revision_body')
+  ->fields($field_data_row)
+  ->execute();
+
+// Add a deleted field and instance.
+$field_id = db_insert('field_config')
+  ->fields(array(
+    'field_name' => 'test_deleted_field',
+    'type' => 'text',
+    'module' => 'text',
+    'active' => 1,
+    'storage_type' => 'field_sql_storage',
+    'storage_module' => 'field_sql_storage',
+    'storage_active' => 1,
+    'locked' => 0,
+    'data' => serialize(array(
+      'entity_types' => array(),
+      'settings' => array(
+        'max_length' => 255,
+      ),
+      'storage' => array(
+        'type' => 'field_sql_storage',
+        'settings' => array(),
+        'module' => 'field_sql_storage',
+        'active' => 1,
+      ),
+      'indexes' => array(
+        'format' => array(0 => 'format')
+      ),
+      'foreign keys' => array(
+        'format' => array(
+          'table' => 'filter_format',
+          'columns' => array('format' => 'format')
+        )
+      )
+    )),
+    'cardinality' => 1,
+    'translatable' => 0,
+    'deleted' => 1,
+  ))
+  ->execute();
+db_insert('field_config_instance')
+  ->fields(array(
+    'field_id' => $field_id,
+    'field_name' => 'test_deleted_field',
+    'entity_type' => 'node',
+    'bundle' => 'article',
+    'data' => serialize(array(
+      'label' => 'Long text',
+      'description' => '',
+      'required' => FALSE,
+      'widget' => array(
+        'type' => 'text_textarea',
+        'weight' => 4,
+        'module' => 'text',
+        'active' => 1,
+        'settings' => array(
+          'rows' => 7
+        ),
+      ),
+      'settings' => array(
+        'text_processing' => 0,
+        'user_register_form' => FALSE,
+      ),
+      'display' => array(
+        'default' => array(
+          'label' => 'above',
+          'type' => 'text_default',
+          'settings' => array(),
+          'module' => 'text',
+          'weight' => 10,
+        ),
+      ),
+    )),
+    'deleted' => 1
+  ))
+  ->execute();
+
+// Add data tables for the deleted field.
+db_create_table("field_deleted_data_{$field_id}", array(
+  'fields' => array(
+    'entity_type' => array(
+      'type' => 'varchar',
+      'length' => 128,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'bundle' => array(
+      'type' => 'varchar',
+      'length' => 128,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'deleted' => array(
+      'type' => 'int',
+      'size' => 'tiny',
+      'not null' => TRUE,
+      'default' => 0,
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => FALSE,
+    ),
+    'language' => array(
+      'type' => 'varchar',
+      'length' => 32,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'delta' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+    ),
+    'test_deleted_field_value' => array(
+      'type' => 'text',
+      'size' => 'big',
+      'not null' => FALSE,
+    ),
+    'test_deleted_field_format' => array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => FALSE,
+    ),
+  ),
+  'primary key' => array(
+    'entity_type',
+    'entity_id',
+    'deleted',
+    'delta',
+    'language',
+  ),
+  'indexes' => array(
+    'entity_type' => array(
+      'entity_type',
+    ),
+    'bundle' => array(
+      'bundle',
+    ),
+    'deleted' => array(
+      'deleted',
+    ),
+    'entity_id' => array(
+      'entity_id',
+    ),
+    'revision_id' => array(
+      'revision_id',
+    ),
+    'language' => array(
+      'language',
+    ),
+    'test_deleted_field_format' => array(
+      'test_deleted_field_format',
+    ),
+  ),
+  'foreign keys' => array(
+    'test_deleted_field_format' => array(
+      'table' => 'filter_format',
+      'columns' => array(
+        'test_deleted_field_format' => 'format',
+      ),
+    ),
+  ),
+  'module' => 'field_sql_storage',
+  'name' => "field_deleted_data_{$field_id}",
+));
+db_create_table("field_deleted_revision_{$field_id}", array(
+  'fields' => array(
+    'entity_type' => array(
+      'type' => 'varchar',
+      'length' => 128,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'bundle' => array(
+      'type' => 'varchar',
+      'length' => 128,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'deleted' => array(
+      'type' => 'int',
+      'size' => 'tiny',
+      'not null' => TRUE,
+      'default' => 0,
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+    ),
+    'language' => array(
+      'type' => 'varchar',
+      'length' => 32,
+      'not null' => TRUE,
+      'default' => '',
+    ),
+    'delta' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+    ),
+    'test_deleted_field_value' => array(
+      'type' => 'text',
+      'size' => 'big',
+      'not null' => FALSE,
+    ),
+    'test_deleted_field_format' => array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => FALSE,
+    ),
+  ),
+  'primary key' => array(
+    'entity_type',
+    'entity_id',
+    'revision_id',
+    'deleted',
+    'delta',
+    'language',
+  ),
+  'indexes' => array(
+    'entity_type' => array(
+      'entity_type',
+    ),
+    'bundle' => array(
+      'bundle',
+    ),
+    'deleted' => array(
+      'deleted',
+    ),
+    'entity_id' => array(
+      'entity_id',
+    ),
+    'revision_id' => array(
+      'revision_id',
+    ),
+    'language' => array(
+      'language',
+    ),
+    'test_deleted_field_format' => array(
+      'test_deleted_field_format',
+    ),
+  ),
+  'foreign keys' => array(
+    'test_deleted_field_format' => array(
+      'table' => 'filter_format',
+      'columns' => array(
+        'test_deleted_field_format' => 'format',
+      ),
+    ),
+  ),
+  'module' => 'field_sql_storage',
+  'name' => "field_deleted_revision_{$field_id}",
+));
+
+// Add some deleted field data.
+$field_data_row = array(
+  'entity_type' => 'node',
+  'bundle' => 'article',
+  'deleted' => '0',
+  'entity_id' => '2',
+  'revision_id' => '2',
+  'language' => 'und',
+  'delta' => '0',
+  'test_deleted_field_value' => 'Some deleted value',
+);
+db_insert("field_deleted_data_{$field_id}")
+  ->fields($field_data_row)
+  ->execute();
+db_insert("field_deleted_revision_{$field_id}")
+  ->fields($field_data_row)
+  ->execute();
+
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
index d73302b18daf..b25ba51b6aeb 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
@@ -175,15 +175,15 @@ function testUninstallReinstall() {
     // Fields and field instances attached to taxonomy term bundles should be
     // removed when the module is uninstalled.
     $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
-    $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4);
-    $this->field = field_create_field($this->field);
-    $this->instance = array(
+    $this->field_definition = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4);
+    field_create_field($this->field_definition);
+    $this->instance_definition = array(
       'field_name' => $this->field_name,
       'entity_type' => 'taxonomy_term',
       'bundle' => $this->vocabulary->id(),
       'label' => $this->randomName() . '_label',
     );
-    field_create_instance($this->instance);
+    field_create_instance($this->instance_definition);
 
     module_disable(array('taxonomy'));
     require_once DRUPAL_ROOT . '/core/includes/install.inc';
@@ -196,8 +196,7 @@ function testUninstallReinstall() {
     // an instance of this field on the same bundle name should be successful.
     $this->vocabulary->enforceIsNew();
     taxonomy_vocabulary_save($this->vocabulary);
-    unset($this->field['id']);
-    field_create_field($this->field);
-    field_create_instance($this->instance);
+    field_create_field($this->field_definition);
+    field_create_instance($this->instance_definition);
   }
 }
diff --git a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
index 716ead78b594..2000ffd1a0e8 100644
--- a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
+++ b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
@@ -45,9 +45,6 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    $this->installSchema('field', 'field_config');
-    $this->installSchema('field', 'field_config_instance');
-
     // @todo Add helper methods for all of the following.
 
     $this->entity_type = 'test_entity';
diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc
index c0a76fab9481..7aa91bf8294b 100644
--- a/core/modules/translation_entity/translation_entity.admin.inc
+++ b/core/modules/translation_entity/translation_entity.admin.inc
@@ -6,20 +6,21 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Returns a form element to configure field synchronization.
  *
- * @param array $field
+ * @param \Drupal\field\Plugin\Core\Entity\Field $field
  *   A field definition array.
- * @param \Drupal\Field\FieldInstance $instance
+ * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
  *   A field instance definition object.
  *
  * @return array
  *   A form element to configure field synchronization.
  */
-function translation_entity_field_sync_widget(array $field, FieldInstance $instance) {
+function translation_entity_field_sync_widget(Field $field, FieldInstance $instance) {
   $element = array();
 
   if (!empty($field['settings']['column_groups']) && count($field['settings']['column_groups']) > 1) {
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index c7fd16b67522..dca417c3ccdf 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -315,10 +315,6 @@ function user_install_picture_field() {
       'uri_scheme' => 'public',
       'default_image' => FALSE,
     ),
-    'storage' => array(
-      'type' => 'field_sql_storage',
-      'settings' => array(),
-    ),
   );
   $field = field_create_field($field);
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php
index 5d2b524d9239..172678d3e757 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/RelationshipJoinTestBase.php
@@ -27,7 +27,6 @@ abstract class RelationshipJoinTestBase extends PluginUnitTestBase {
    */
   protected function setUpFixtures() {
     $this->installSchema('user', array('users', 'users_roles', 'role_permission'));
-    $this->installSchema('field', array('field_config', 'field_config_instance'));
     $this->installConfig(array('user'));
     parent::setUpFixtures();
 
-- 
GitLab