diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
index 57e4243..d014890 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
@@ -35,6 +35,8 @@
  *   'article'. Entity IDs may contain dots/periods. The entire remaining string
  *   after the config_prefix in a config name forms the entity ID. Additional or
  *   custom suffixes are not possible.
+ *
+ * @ingroup entity_api
  */
 class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
 
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
index b050ba0..044fa26 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
@@ -35,6 +35,8 @@
  * the defined base fields. Entity types can override
  * ContentEntityDatabaseStorage::getSchema() to customize the generated
  * schema; e.g., to add additional indexes.
+ *
+ * @ingroup entity_api
  */
 class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface {
 
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php
index 0406a5e..c4ce1bd 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php
@@ -17,6 +17,8 @@
  * Most simple, SQL-based entity controllers will do better by extending
  * Drupal\Core\Entity\ContentEntityDatabaseStorage instead of implementing this
  * interface directly.
+ *
+ * @ingroup entity_api
  */
 interface EntityStorageInterface {
 
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
index 32067a4..89812fb 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
@@ -12,6 +12,8 @@
 
 /**
  * Defines a common interface for entity view controller classes.
+ *
+ * @ingroup entity_api
  */
 interface EntityViewBuilderInterface {
 
diff --git a/core/modules/block/block.api.php b/core/modules/block/block.api.php
index a0f79bf..5b465ae 100644
--- a/core/modules/block/block.api.php
+++ b/core/modules/block/block.api.php
@@ -86,6 +86,8 @@
  *
  * @see hook_block_view_BASE_BLOCK_ID_alter()
  * @ingroup block_api
+ *
+ * @ingroup entity_hooks
  */
 function hook_block_view_alter(array &$build, \Drupal\block\BlockPluginInterface $block) {
   // Remove the contextual links on all blocks that provide them.
@@ -114,6 +116,8 @@ function hook_block_view_alter(array &$build, \Drupal\block\BlockPluginInterface
  *
  * @see hook_block_view_alter()
  * @ingroup block_api
+ *
+ * @ingroup entity_hooks
  */
 function hook_block_view_BASE_BLOCK_ID_alter(array &$build, \Drupal\block\BlockPluginInterface $block) {
   // Change the title of the specific block.
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index e8fb142..7eb69d1 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -254,6 +254,8 @@ function hook_field_info_max_weight($entity_type, $bundle, $context, $context_mo
  *   The field as it will be post-update.
  * @param \Drupal\field\FieldConfigInterface $prior_field
  *   The field as it is pre-update.
+ *
+ * @ingroup entity_hooks
  */
 function hook_field_config_update_forbid(\Drupal\field\FieldConfigInterface $field, \Drupal\field\FieldConfigInterface $prior_field) {
   // A 'list' field stores integer keys mapped to display values. If
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 35ac533..c738e0c 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -6,105 +6,7 @@
 
 /**
  * @file
- * Hooks provided by the Node module.
- */
-
-/**
- * @defgroup node_api_hooks Node API Hooks
- * @{
- * Functions to define and modify content types.
- *
- * Each content type is maintained by a primary module, which is either
- * node.module (for content types created in the user interface) or the module
- * that defines the content type by providing configuration file.
- *
- * During node operations (create, insert, update, view, delete, etc.), there
- * are several sets of hooks that get invoked to allow modules to modify the
- * base node operation:
- * - All-module hooks: This set of hooks is invoked on all implementing modules,
- *   to allow other modules to modify what the primary node module is doing. For
- *   example, hook_node_insert() is invoked on all modules when creating a forum
- *   node.
- * - Field hooks: Hooks related to the fields attached to the node. These are
- *   invoked from the field operations functions described below, and can be
- *   either field-type-specific or all-module hooks.
- * - Entity hooks: Generic hooks for "entity" operations. These are always
- *   invoked on all modules.
- *
- * Here is a list of the node and entity hooks that are invoked, and other
- * steps that take place during node operations:
- * - Instantiating a new node:
- *   - hook_node_create() (all)
- *   - hook_entity_create() (all)
- * - Creating a new node (calling $node->save() on a new node):
- *   - hook_node_presave() (all)
- *   - hook_entity_presave() (all)
- *   - Node and revision records are written to the database
- *   - hook_node_insert() (all)
- *   - hook_entity_insert() (all)
- *   - hook_node_access_records() (all)
- *   - hook_node_access_records_alter() (all)
- * - Updating an existing node (calling $node->save() on an existing node):
- *   - hook_node_presave() (all)
- *   - hook_entity_presave() (all)
- *   - Node and revision records are written to the database
- *   - hook_node_update() (all)
- *   - hook_entity_update() (all)
- *   - hook_node_access_records() (all)
- *   - hook_node_access_records_alter() (all)
- * - Loading a node (calling node_load(), node_load_multiple(), entity_load(),
- *   or entity_load_multiple() with $entity_type of 'node'):
- *   - Node and revision information is read from database.
- *   - hook_entity_load() (all)
- *   - hook_node_load() (all)
- * - Viewing a single node (calling node_view() - note that the input to
- *   node_view() is a loaded node, so the Loading steps above are already done):
- *   - hook_entity_prepare_view() (all)
- *   - hook_entity_display_build_alter() (all)
- *   - hook_node_view() (all)
- *   - hook_entity_view() (all)
- *   - hook_node_view_alter() (all)
- *   - hook_entity_view_alter() (all)
- * - Viewing multiple nodes (calling node_view_multiple() - note that the input
- *   to node_view_multiple() is a set of loaded nodes, so the Loading steps
- *   above are already done):
- *   - hook_entity_prepare_view() (all)
- *   - hook_entity_display_build_alter() (all)
- *   - hook_node_view() (all)
- *   - hook_entity_view() (all)
- *   - hook_node_view_alter() (all)
- *   - hook_entity_view_alter() (all)
- * - Deleting a node (calling $node->delete() or entity_delete_multiple()):
- *   - Node is loaded (see Loading section above)
- *   - hook_node_predelete() (all)
- *   - hook_entity_predelete() (all)
- *   - Node and revision information are deleted from database
- *   - hook_node_delete() (all)
- *   - hook_entity_delete() (all)
- * - Deleting a node revision (calling node_revision_delete()):
- *   - Node is loaded (see Loading section above)
- *   - Revision information is deleted from database
- *   - hook_node_revision_delete() (all)
- * - Preparing a node for editing (calling node_form() - note that if it is an
- *   existing node, it will already be loaded; see the Loading section above):
- *   - hook_node_prepare_form() (all)
- *   - hook_entity_prepare_form() (all)
- *   - @todo hook for EntityFormDisplay::buildForm()
- * - Validating a node during editing form submit (calling
- *   node_form_validate()):
- *   - hook_node_validate() (all)
- * - Searching (using the 'node_search' plugin):
- *   - hook_ranking() (all)
- *   - Query is executed to find matching nodes
- *   - Resulting node is loaded (see Loading section above)
- *   - Resulting node is prepared for viewing (see Viewing a single node above)
- *   - comment_node_update_index() is called (this adds "N comments" text)
- *   - hook_node_search_result() (all)
- * - Search indexing (calling updateIndex() on the 'node_search' plugin):
- *   - Node is loaded (see Loading section above)
- *   - Node is prepared for viewing (see Viewing a single node above)
- *   - hook_node_update_index() (all)
- * @}
+ * Hooks specific to the Node module.
  */
 
 /**
@@ -381,147 +283,6 @@ function hook_node_grants_alter(&$grants, \Drupal\Core\Session\AccountInterface
 }
 
 /**
- * Act before node deletion.
- *
- * This hook is invoked from entity_delete_multiple() before
- * hook_entity_predelete() is called and field values are deleted, and before
- * the node is removed from the node table in the database.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that is about to be deleted.
- *
- * @see hook_node_predelete()
- * @see entity_delete_multiple()
- * @ingroup node_api_hooks
- */
-function hook_node_predelete(\Drupal\node\NodeInterface $node) {
-  db_delete('mytable')
-    ->condition('nid', $node->id())
-    ->execute();
-}
-
-/**
- * Respond to node deletion.
- *
- * This hook is invoked from entity_delete_multiple() after field values are
- * deleted and after the node has been removed from the database.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that has been deleted.
- *
- * @see hook_node_predelete()
- * @see entity_delete_multiple()
- * @ingroup node_api_hooks
- */
-function hook_node_delete(\Drupal\node\NodeInterface $node) {
-  drupal_set_message(t('Node: @title has been deleted', array('@title' => $node->label())));
-}
-
-/**
- * Respond to deletion of a node revision.
- *
- * This hook is invoked from node_revision_delete() after the revision has been
- * removed from the node_revision table, and before field values are deleted.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node revision (node object) that is being deleted.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_revision_delete(\Drupal\node\NodeInterface $node) {
-  db_delete('mytable')
-    ->condition('vid', $node->getRevisionId())
-    ->execute();
-}
-
-/**
- * Respond to creation of a new node.
- *
- * This hook is invoked from $node->save() after the database query that will
- * insert the node into the node table is scheduled for execution, and after
- * field values are saved.
- *
- * Note that when this hook is invoked, the changes have not yet been written to
- * the database, because a database transaction is still in progress. The
- * transaction is not finalized until the save operation is entirely completed
- * and $node->save() goes out of scope. You should not rely on data in the
- * database at this time as it is not updated yet. You should also note that any
- * write/update database queries executed from this hook are also not committed
- * immediately. Check $node->save() and db_transaction() for more info.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that is being created.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_insert(\Drupal\node\NodeInterface $node) {
-  db_insert('mytable')
-    ->fields(array(
-      'nid' => $node->id(),
-      'extra' => $node->extra,
-    ))
-    ->execute();
-}
-
-/**
- * Act on a newly created node.
- *
- * This hook runs after a new node object has just been instantiated. It can be
- * used to set initial values, e.g. to provide defaults.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node object.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_create(\Drupal\node\NodeInterface $node) {
-  if (!isset($node->foo)) {
-    $node->foo = 'some_initial_value';
-  }
-}
-
-/**
- * Act on arbitrary nodes being loaded from the database.
- *
- * This hook should be used to add information that is not in the node or node
- * revisions table, not to replace information that is in these tables (which
- * could interfere with the entity cache). For performance reasons, information
- * for all available nodes should be loaded in a single query where possible.
- *
- * This hook is invoked during node loading, which is handled by entity_load(),
- * via classes Drupal\node\NodeStorage and
- * Drupal\Core\Entity\ContentEntityDatabaseStorage. After the node information
- * and field values are read from the database or the entity cache,
- * hook_entity_load() is invoked on all implementing modules, and finally
- * hook_node_load() is invoked on all implementing modules.
- *
- * @param $nodes
- *   An array of the nodes being loaded, keyed by nid.
- *
- * For a detailed usage example, see nodeapi_example.module.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_load($nodes) {
-  // Decide whether any of $types are relevant to our purposes.
-  $types_we_want_to_process = \Drupal::config('my_types')->get('types');
-  $nids = array();
-  foreach ($nodes as $node) {
-    if (in_array($node->bundle(), $types_we_want_to_process)) {
-      $nids = $node->id();
-    }
-  }
-  if ($nids) {
-    // Gather our extra data for each of these nodes.
-    $result = db_query('SELECT nid, foo FROM {mytable} WHERE nid IN(:nids)', array(':nids' => $nids));
-    // Add our extra data to the node objects.
-    foreach ($result as $record) {
-      $nodes[$record->nid]->foo = $record->foo;
-    }
-  }
-}
-
-/**
  * Controls access to a node.
  *
  * Modules may implement this hook if they want to have a say in whether or not
@@ -588,27 +349,6 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang
   return NODE_ACCESS_IGNORE;
 }
 
-
-/**
- * Act on a node object about to be shown on the add/edit form.
- *
- * This hook is invoked from NodeForm::prepareEntity().
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that is about to be shown on the form.
- * @param $operation
- *   The current operation.
- * @param array $form_state
- *   An associative array containing the current state of the form.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $operation, array &$form_state) {
-  if (!isset($node->my_rating)) {
-    $node->my_rating = \Drupal::config("my_rating_{$node->bundle()}")->get('enabled');
-  }
-}
-
 /**
  * Act on a node being displayed as a search result.
  *
@@ -629,7 +369,7 @@ function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $operation, ar
  * @see template_preprocess_search_result()
  * @see search-result.html.twig
  *
- * @ingroup node_api_hooks
+ * @ingroup entity_hooks
  */
 function hook_node_search_result(\Drupal\node\NodeInterface $node, $langcode) {
   $rating = db_query('SELECT SUM(points) FROM {my_rating} WHERE nid = :nid', array('nid' => $node->id()))->fetchField();
@@ -637,53 +377,6 @@ function hook_node_search_result(\Drupal\node\NodeInterface $node, $langcode) {
 }
 
 /**
- * Act on a node being inserted or updated.
- *
- * This hook is invoked from $node->save() before the node is saved to the
- * database.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that is being inserted or updated.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_presave(\Drupal\node\NodeInterface $node) {
-  if ($node->id() && $node->moderate) {
-    // Reset votes when node is updated:
-    $node->score = 0;
-    $node->users = '';
-    $node->votes = 0;
-  }
-}
-
-/**
- * Respond to updates to a node.
- *
- * This hook is invoked from $node->save() after the database query that will
- * update node in the node table is scheduled for execution, and after field
- * values are saved.
- *
- * Note that when this hook is invoked, the changes have not yet been written to
- * the database, because a database transaction is still in progress. The
- * transaction is not finalized until the save operation is entirely completed
- * and $node->save() goes out of scope. You should not rely on data in the
- * database at this time as it is not updated yet. You should also note that any
- * write/update database queries executed from this hook are also not committed
- * immediately. Check $node->save() and db_transaction() for more info.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node that is being updated.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_update(\Drupal\node\NodeInterface $node) {
-  db_update('mytable')
-    ->fields(array('extra' => $node->extra))
-    ->condition('nid', $node->id())
-    ->execute();
-}
-
-/**
  * Act on a node being indexed for searching.
  *
  * This hook is invoked during search indexing, after loading, and after the
@@ -697,7 +390,7 @@ function hook_node_update(\Drupal\node\NodeInterface $node) {
  * @return string
  *   Additional node information to be indexed.
  *
- * @ingroup node_api_hooks
+ * @ingroup entity_hooks
  */
 function hook_node_update_index(\Drupal\node\NodeInterface $node, $langcode) {
   $text = '';
@@ -709,140 +402,6 @@ function hook_node_update_index(\Drupal\node\NodeInterface $node, $langcode) {
 }
 
 /**
- * Perform node validation before a node is created or updated.
- *
- * This hook is invoked from NodeForm::validate(), after a user has
- * finished editing the node and is previewing or submitting it. It is invoked
- * at the end of all the standard validation steps.
- *
- * To indicate a validation error, use form_set_error().
- *
- * Note: Changes made to the $node object within your hook implementation will
- * have no effect.  The preferred method to change a node's content is to use
- * hook_node_presave() instead. If it is really necessary to change the node at
- * the validate stage, you can use form_set_value().
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node being validated.
- * @param $form
- *   The form being used to edit the node.
- * @param $form_state
- *   The form state array.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_validate(\Drupal\node\NodeInterface $node, $form, &$form_state) {
-  if (isset($node->end) && isset($node->start)) {
-    if ($node->start > $node->end) {
-      form_set_error('time', $form_state, t('An event may not end before it starts.'));
-    }
-  }
-}
-
-/**
- * Act on a node after validated form values have been copied to it.
- *
- * This hook is invoked when a node form is submitted with either the "Save" or
- * "Preview" button, after form values have been copied to the form state's node
- * object, but before the node is saved or previewed. It is a chance for modules
- * to adjust the node's properties from what they are simply after a copy from
- * $form_state['values']. This hook is intended for adjusting non-field-related
- * properties.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node entity being updated in response to a form submission.
- * @param $form
- *   The form being used to edit the node.
- * @param $form_state
- *   The form state array.
- *
- * @ingroup node_api_hooks
- */
-function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state) {
-  // Decompose the selected menu parent option into 'menu_name' and 'plid', if
-  // the form used the default parent selection widget.
-  if (!empty($form_state['values']['menu']['parent'])) {
-    list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
-  }
-}
-
-/**
- * Act on a node that is being assembled before rendering.
- *
- * The module may add elements to a node's renderable array array prior to
- * rendering.
- *
- * When $view_mode is 'rss', modules can also add extra RSS elements and
- * namespaces to $node->rss_elements and $node->rss_namespaces respectively for
- * the RSS item generated for this node.
- * For details on how this is used, see node_feed().
- *
- * @param array &$build
- *   A renderable array representing the node content.
- * @param \Drupal\node\NodeInterface $node
- *   The node that is being assembled for rendering.
- * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
- *   The entity view display holding the display options configured for the node
- *   components.
- * @param string $view_mode
- *   The $view_mode parameter from node_view().
- * @param string $langcode
- *   The language code used for rendering.
- *
- * @see forum_node_view()
- * @see hook_entity_view()
- *
- * @ingroup node_api_hooks
- */
-function hook_node_view(array &$build, \Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
-  // Only do the extra work if the component is configured to be displayed.
-  // This assumes a 'mymodule_addition' extra field has been defined for the
-  // node type in hook_entity_extra_field_info().
-  if ($display->getComponent('mymodule_addition')) {
-    $build['mymodule_addition'] = array(
-      '#markup' => mymodule_addition($node),
-      '#theme' => 'mymodule_my_additional_field',
-    );
-  }
-}
-
-/**
- * Alter the results of node_view().
- *
- * This hook is called after the content has been assembled in a structured
- * array and may be used for doing processing which requires that the complete
- * node content structure has been built.
- *
- * If the module wishes to act on the rendered HTML of the node rather than the
- * structured content array, it may use this hook to add a #post_render
- * callback.  Alternatively, it could also implement hook_preprocess_HOOK() for
- * node.html.twig. See drupal_render() and _theme() documentation respectively
- * for details.
- *
- * @param &$build
- *   A renderable array representing the node content.
- * @param \Drupal\node\NodeInterface $node
- *   The node being rendered.
- * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
- *   The entity view display holding the display options configured for the node
- *   components.
- *
- * @see node_view()
- * @see hook_entity_view_alter()
- *
- * @ingroup node_api_hooks
- */
-function hook_node_view_alter(array &$build, \Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
-  if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
-    // Change its weight.
-    $build['an_additional_field']['#weight'] = -10;
-  }
-
-  // Add a #post_render callback to act on the rendered HTML of the node.
-  $build['#post_render'][] = 'my_module_node_post_render';
-}
-
-/**
  * Provide additional methods of scoring for core search results for nodes.
  *
  * A node's search score is used to rank it among other nodes matched by the
@@ -883,7 +442,7 @@ function hook_node_view_alter(array &$build, \Drupal\node\NodeInterface $node, \
  *   - arguments: (optional) If any arguments are required for the score, they
  *     can be specified in an array here.
  *
- * @ingroup node_api_hooks
+ * @ingroup entity_hooks
  */
 function hook_ranking() {
   // If voting is disabled, we can avoid returning the array, no hard feelings.
@@ -905,38 +464,6 @@ function hook_ranking() {
 }
 
 /**
- * Respond to node type creation.
- *
- * @param \Drupal\node\NodeTypeInterface $type
- *   The node type entity that was created.
- */
-function hook_node_type_insert(\Drupal\node\NodeTypeInterface $type) {
-  drupal_set_message(t('You have just created a content type with a machine name %type.', array('%type' => $type->id())));
-}
-
-/**
- * Respond to node type updates.
- *
- * @param \Drupal\node\NodeTypeInterface $type
- *   The node type entity that was updated.
- */
-function hook_node_type_update(\Drupal\node\NodeTypeInterface $type) {
-  if ($type->original->id() != $type->id()) {
-    drupal_set_message(t('You have just changed the machine name of a content type from %old_type to %type.', array('%old_type' => $type->original->id(), '%type' => $type->id())));
-  }
-}
-
-/**
- * Respond to node type deletion.
- *
- * @param \Drupal\node\NodeTypeInterface $type
- *   The node type entity that was deleted.
- */
-function hook_node_type_delete(\Drupal\node\NodeTypeInterface $type) {
-  drupal_set_message(t('You have just deleted a content type with the machine name %type.', array('%type' => $type->id())));
-}
-
-/**
  * Alter the links of a node.
  *
  * @param array &$links
@@ -951,6 +478,8 @@ function hook_node_type_delete(\Drupal\node\NodeTypeInterface $type) {
  *
  * @see \Drupal\node\NodeViewBuilder::renderLinks()
  * @see \Drupal\node\NodeViewBuilder::buildLinks()
+ *
+ * @ingroup entity_hooks
  */
 function hook_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
   $links['mymodule'] = array(
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 28c826f..803e815 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1151,10 +1151,12 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
  * default home page at path 'node', an RSS feed, a recent content block, etc.),
  * the process above is followed except that hook_node_access() is not called on
  * each node for performance reasons and for proper functioning of the pager
- * system. When adding a node listing to your module, be sure to use a dynamic
- * entity query and add a tag of "node_access". This will allow modules dealing
+ * system. When adding a node listing to your module, be sure to use an entity
+ * query, which will add a tag of "node_access". This will allow modules dealing
  * with node access to ensure only nodes to which the user has access are
- * retrieved, through the use of hook_query_TAG_alter().
+ * retrieved, through the use of hook_query_TAG_alter(). See the
+ * @link entity_api Entity API topic @endlink for more information on entity
+ * queries.
  *
  * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
  * will block access to the node. Therefore, implementers should take care to
diff --git a/core/modules/system/core.api.php b/core/modules/system/core.api.php
index 0db3805..f8972bb 100644
--- a/core/modules/system/core.api.php
+++ b/core/modules/system/core.api.php
@@ -32,16 +32,16 @@
  * @section store_retrieve Storing and retrieving data
  *
  * - @link entity_api Entities @endlink
+ * - @link field Fields @endlink
  * - @link config_api Configuration API @endlink
  * - @link state_api State API @endlink
- * - @link field Fields @endlink
- * - @link node_overview Node system @endlink
  * - @link views_overview Views @endlink
  * - @link database Database abstraction layer @endlink
  *
  * @section other_essentials Other essential APIs
  *
  * - @link plugin_api Plugins @endlink
+ * - @link container Services and the Dependency Injection Container @endlink
  * - @link i18n Internationalization @endlink
  * - @link cache Caching @endlink
  * - @link utility Utility classes and functions @endlink
@@ -53,7 +53,6 @@
  *
  * - @link batch Batch API @endlink
  * - @link queue Queue API @endlink
- * - @link container Services and the Dependency Injection Container @endlink
  * - @link typed_data Typed Data @endlink
  * - @link testing Automated tests @endlink
  * - @link third_party Integrating third-party applications @endlink
@@ -292,8 +291,9 @@
  *   - delete form: Define a class that extends
  *     \Drupal\Core\Entity\EntityConfirmFormBase to provide a delete
  *     confirmation form for your entities.
- *   - view_buider: For content entities, define a class that extends
- *     \Drupal\Core\Entity\EntityViewBuilder, to display a single entity.
+ *   - view_builder: For content entities, define a class that implements
+ *     \Drupal\Core\Entity\EntityViewBuilderInterface (usually extending
+ *     \Drupal\Core\Entity\EntityViewBuilder), to display a single entity.
  *   - translation: For translatable content entities (if the 'translatable'
  *     annotation has value TRUE), define a class that extends
  *     \Drupal\content_translation\ContentTranslationHandler, to translate
@@ -303,6 +303,11 @@
  *     need an access controller, implementing
  *     \Drupal\Core\Entity\EntityAccessControllerInterface, but most entities
  *     can just use the 'admin_permission' annotation instead.
+ *   - storage: A class implementing
+ *     \Drupal\Core\Entity\EntityStorageInterface. If not specified, content
+ *     entities will use \Drupal\Core\Entity\ContentEntityDatabaseStorage, and
+ *     config entities will use \Drupal\Core\Config\Entity\ConfigEntityStorage.
+ *     You can extend one of these classes to provide custom behavior.
  * - For content entities, the annotation will refer to a number of database
  *   tables and their fields. These annotation properties, such as 'base_table',
  *   'data_table', 'entity_keys', etc., are documented on
@@ -339,7 +344,25 @@
  *   (configuration). These annotations are documented on
  *   \Drupal\Core\Entity\EntityType.
  *
- * @section load_query Loading and querying entities
+ * @section bundle Defining a content entity bundle
+ * For content entity types that use bundles, such as Node (bundles are content
+ * types) and Taxonomy (bundles are vocabularies), modules and install profiles
+ * can define bundles by supplying default configuration in their config/install
+ * directories. (See the @link config_api Configuration API topic @endlink for
+ * general information about configuration.)
+ *
+ * There are several good examples of this in Drupal Core:
+ * - The Forum module defines a content type in node.type.forum.yml and a
+ *   vocabulary in taxonomy.vocabulary.forums.yml
+ * - The Book module defines a content type in node.type.book.yml
+ * - The Standard install profile defines Page and Article content types in
+ *   node.type.page.yml and node.type.article.yml, a Tags vocabulary in
+ *   taxonomy.vocabulary.tags.yml, and a Node comment type in
+ *   comment.type.comment.yml. This profile's configuration is especially
+ *   instructive, because it also adds several fields to the Article type, and
+ *   it sets up view and form display modes for the node types.
+ *
+ * @section load_query Loading, querying, and rendering entities
  * To load entities, use the entity storage manager, which is an object
  * implementing \Drupal\Core\Entity\EntityStorageInterface that you can
  * retrieve with:
@@ -389,6 +412,25 @@
  * $files = $storage->loadMultiple($fids);
  * @endcode
  *
+ * To render or view a content entity, use the entity view manager, which is an
+ * object implementing \Drupal\Core\Entity\EntityViewBuilderInteface that
+ * you can retrive with:
+ * @code
+ * $viewer = \Drupal::entityManager()->getViewBuilder('your_entity_type');
+ * // Or if you have a $container variable:
+ * $viewer = $container->get('entity.manager')->getViewBuilder('your_entity_type');
+ * @endcode
+ *
+ * Once you have a loaded entity and a viewer, you can render the entity
+ * in a particular view mode by calling:
+ * @code
+ * // If the entity supports translation, load the translation.
+ * $entity = $entity->getTranslation($language->id);
+ * $build = $renderer->view($entity, 'view_mode_name', $language->id);
+ * // $build is a render array.
+ * $rendered = drupal_render($build);
+ * @endcode
+ *
  * @section sec_access Access checking on entities
  * Entity types define their access permission scheme in their annotation.
  * Access permissions can be quite complex, so you should not assume any
@@ -402,25 +444,17 @@
  * The interface related to access checking in entities and fields is
  * \Drupal\Core\Access\AccessibleInterface.
  *
- * @see i18n
- * @}
- */
-
-/**
- * @defgroup node_overview Nodes Overview
- * @{
- * Overview of how to interact with the Node system
+ * The default entity access controller invokes two hooks while checking
+ * access on a single entity: hook_entity_access() is invoked first, and
+ * then hook_ENTITY_TYPE_access() (where ENTITY_TYPE is the machine name
+ * of the entity type).
  *
- * @todo write this
+ * The Node entity type has a complex permission system, which developers can
+ * interact with. This is described in the
+ * @link node_access Node access topic. @endlink
  *
- * Additional documentation paragraphs need to be written, and functions,
- * classes, and interfaces need to be added to this topic. This topic should
- * describe node access, the node classes/interfaces, and the node hooks that a
- * developer would need to know about, at a high level, and link to more
- * detailed information.
- *
- * @see node_access
- * @see node_api_hooks
+ * @see i18n
+ * @see entity_hooks
  * @}
  */
 
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index da1d53e..9a858c8 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -11,6 +11,175 @@
 use Drupal\Core\Render\Element;
 
 /**
+ * @defgroup entity_hooks Entity CRUD and view hooks
+ * @{
+ * Hooks used in entity view, create, read, update, and delete operations.
+ *
+ * Entity create, read, update, and delete (CRUD) operations are performed by
+ * entity storage controllers; see the
+ * @link entity_api Entity API topic @endlink for more information. Most
+ * entities use or extend the default storage controllers:
+ * \Drupal\Core\Entity\ContentEntityDatabaseStorage for content entities, and
+ * \Drupal\Core\Config\Entity\ConfigEntityStorage for configuration entities.
+ * For these entities, there is a set of hooks that is invoked for each
+ * CRUD operation, which module developers can implement to affect these
+ * operations; these hooks are actually invoked from methods on
+ * \Drupal\Core\Entity\EntityStorageBase().
+ *
+ * For content entities, viewing and rendering are handled by a view builder
+ * controller; see the @link entity_api Entity API topic @endlink for more
+ * information. Most view builders extend or use the default class
+ * \Drupal\Core\Entity\EntityViewBuilder.
+ *
+ * This topic lists all of the entity CRUD and view operations, and the hooks
+ * and other operations that are invoked (in order) for each operation. Some
+ * notes:
+ * - Whenever an entity hook is invoked, there is both a type-specific entity
+ *   hook, and a generic entity hook. For instance, during a create operation on
+ *   a node, first hook_node_create() and then hook_entity_create() would be
+ *   invoked. The entity-type-specific hooks are represented in the list below
+ *   as hook_ENTITY_TYPE_... (hook_ENTITY_TYPE_create() in this example).
+ * - For the most part, the generic entity hooks have the same parameters:
+ *   - An array of entity objects.
+ *   - The machine name of the entity type.
+ * - The type-specific entity hook corresponding to a given generic entity
+ *   hook will have the same paramters, except it will not have the entity type
+ *   machine name as a parameter. Some of the type-specific hooks are not
+ *   documented separately.
+ * - $storage in the code examples is assumed to be an entity storage
+ *   controller. See the @link entity_api Entity API topic @endlink for
+ *   information on how to instantiate the correct storage controller for an
+ *   entity type.
+ * - $viewer in the code examples is assumed to be an entity view builder
+ *   controller. See the @link entity_api Entity API topic @endlink for
+ *   information on how to instantiate the correct view builder controller for
+ *   an entity type.
+ * - During many operations, static methods are called on the entity class,
+ *   which implements \Drupal\Entity\EntityInterface.
+ *
+ * @section create Create operations
+ * To create an entity:
+ * @code
+ * $entity = $storage->create();
+ * // Set up the entity properly.
+ * $entity->save();
+ * @endcode
+ *
+ * Hooks invoked during the create operation:
+ * - hook_ENTITY_TYPE_create()
+ * - hook_entity_create()
+ *
+ * See @ref save below for the save portion of the operation.
+ *
+ * @section load Read/Load operations
+ * To load (read) a single entity:
+ * @code
+ * $entity = $storage->load($id);
+ * @endcode
+ * To load multiple entities:
+ * @code
+ * $entities = $storage->loadMultiple($ids);
+ * @endcode
+ * Since load() calls loadMultiple(), these are really the same operation.
+ * Here is the order of hooks and other operations that take place during
+ * entity loading:
+ * - Entity and fields are loaded from storage.
+ * - postLoad() is called on the entity class, passing in all of the loaded
+ *   entities.
+ * - hook_entity_load()
+ * - hook_ENTITY_TYPE_load()
+ *
+ * @section update Update operations
+ * To update an entity, load it (see @ref load), modify its values, and
+ * save it (see @ref save).
+ *
+ * @section save Save operations
+ * Both create and update operations invoke an entity save. Here is the order
+ * of hooks and other events that happen during an entity save:
+ * - preSave() is called on the entity object.
+ * - hook_ENTITY_TYPE_presave()
+ * - hook_entity_presave()
+ * - The base entity information and fields are saved to storage.
+ * - For updates on content entities, if there is a translation added that
+ *   was not previously present:
+ *   - hook_ENTITY_TYPE_translation_insert()
+ *   - hook_entity_translation_insert()
+ * - For updates on content entities, if there was a translation removed:
+ *   - hook_ENTITY_TYPE_translation_delete()
+ *   - hook_entity_translation_delete()
+ * - postSave() is called on the entity object.
+ * - hook_ENTITY_TYPE_insert() (new) or hook_ENTITY_TYPE_update() (update)
+ * - hook_entity_insert() (new) or hook_entity_update() (update)
+ *
+ * Some specific entity types invoke hooks during preSave() or postSave()
+ * operations. Examples:
+ * - Field configuration preSave(): hook_field_config_update_forbid()
+ * - Node postSave(): hook_node_access_records() and
+ *   hook_node_access_records_alter()
+ * - Entity bundle postSave(): hook_entity_bundle_create() or
+ *   hook_entity_bundle_rename() as appropriate
+ *
+ * @section delete Delete operations
+ * To delete one or more entities, load them and then delete them:
+ * @code
+ * $entities = $storage->loadMultiple($ids);
+ * $storage->delete($entities);
+ * @endcode
+ *
+ * During the delete operation, the following hooks and other events happen:
+ * - preDelete() is called on the entity class.
+ * - hook_ENTITY_TYPE_predelete()
+ * - hook_entity_predelete()
+ * - Entity and field information is removed from storage.
+ * - postDelete() is called on the entity class.
+ * - hook_ENTITY_TYPE_delete()
+ * - hook_entity_delete()
+ *
+ * Some specific entity types invoke hooks during postDelete(). Examples:
+ * - Entity bundle postDelete(): hook_entity_bundle_delete()
+ *
+ * @section view View/render operations
+ * To make a render array for a loaded entity:
+ * @code
+ * // If the entity supports translation, load the translation.
+ * $entity = $entity->getTranslation($language->id);
+ * $build = $renderer->view($entity, 'view_mode_name', $language->id);
+ * @endcode
+ * You can also use the viewMultiple() method to view multiple entities.
+ *
+ * Hooks invoked during the operation of building a render array:
+ * - hook_entity_view_mode_alter()
+ * - hook_ENTITY_TYPE_build_defaults_alter()
+ * - hook_entity_build_defaults_alter()
+ *
+ * View builders for some types override these hooks, notably:
+ * - The Tour view builder does not call any hooks.
+ * - The Block view builder calls hook_block_view_alter() and
+ *    hook_block_view_BASE_BLOCK_ID_alter().
+ * - The Node view builder invokes hook_node_links_alter() when links are
+ *   rendered in a node.
+ *
+ * @section misc Other entity hooks
+ * Some types of entities invoke hooks for specific operations:
+ * - Searching nodes:
+ *   - hook_ranking()
+ *   - Query is executed to find matching nodes
+ *   - Resulting node is loaded
+ *   - Node render array is built
+ *   - comment_node_update_index() is called (this adds "N comments" text)
+ *   - hook_node_search_result()
+ * - Search indexing nodes:
+ *   - Node is loaded
+ *   - Node render array is built
+ *   - hook_node_update_index()
+ *
+ * @todo There are some hooks in this file that may or may not really exist
+ *   (in the sense of being invoked sometime), such as revision hooks and
+ *    hook_entity_view(). Figure this out.
+ * @}
+ */
+
+/**
  * @addtogroup hooks
  * @{
  */
@@ -32,6 +201,8 @@
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
+ *
+ * @ingroup entity_api
  */
 function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -54,6 +225,8 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
+ *
+ * @ingroup entity_api
  */
 function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -90,6 +263,8 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
+ *
+ * @ingroup entity_hooks
  */
 function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -198,6 +373,8 @@ function hook_entity_bundle_info_alter(&$bundles) {
  *   The type of $entity; e.g. 'node' or 'user'.
  * @param string $bundle
  *   The name of the bundle.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_bundle_create($entity_type_id, $bundle) {
   // When a new bundle is created, the menu needs to be rebuilt to add the
@@ -216,6 +393,8 @@ function hook_entity_bundle_create($entity_type_id, $bundle) {
  *   The previous name of the bundle.
  * @param string $bundle_new
  *   The new name of the bundle.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
   // Update the settings associated with the bundle in my_module.settings.
@@ -237,6 +416,8 @@ function hook_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
  *   The type of entity; for example, 'node' or 'user'.
  * @param string $bundle
  *   The bundle that was just deleted.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_bundle_delete($entity_type_id, $bundle) {
   // Remove the settings associated with the bundle in my_module.settings.
@@ -256,6 +437,8 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
   if ($entity instanceof ContentEntityInterface && !$entity->foo->value) {
@@ -273,6 +456,8 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
  *   The entities keyed by entity ID.
  * @param string $entity_type_id
  *   The type of entities being loaded (i.e. node, user, comment).
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_load($entities, $entity_type_id) {
   foreach ($entities as $entity) {
@@ -285,6 +470,8 @@ function hook_entity_load($entities, $entity_type_id) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
   $entity->changed = REQUEST_TIME;
@@ -298,6 +485,8 @@ function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
   // Insert the new entity into a fictional table of all entities.
@@ -319,6 +508,8 @@ function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
   // Update the entity's entry in a fictional table of all entities.
@@ -339,6 +530,8 @@ function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $translation
  *   The entity object of the translation just stored.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) {
   $variables = array(
@@ -355,6 +548,8 @@ function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $tra
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The original entity object.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $translation) {
   $languages = \Drupal::languageManager()->getLanguages();
@@ -370,6 +565,8 @@ function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $tra
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object for the entity that is about to be deleted.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
   // Count references to this entity in a custom table before they are removed
@@ -397,6 +594,8 @@ function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object for the entity that has been deleted.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
   // Delete the entity's entry from a fictional table of all entities.
@@ -554,6 +753,8 @@ function hook_entity_prepare_view($entity_type_id, array $entities, array $displ
  * @param array $context
  *   Array with additional context information, currently only contains the
  *   langcode the entity is viewed in.
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInterface $entity, $context) {
   // For nodes, change the view mode when it is teaser.
@@ -582,6 +783,8 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
  *
  * @see drupal_render()
  * @see \Drupal\Core\Entity\EntityViewBuilder
+ *
+ * @ingroup entity_hooks
  */
 function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
 
@@ -605,6 +808,8 @@ function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entit
  *
  * @see drupal_render()
  * @see \Drupal\Core\Entity\EntityViewBuilder
+ *
+ * @ingroup entity_hooks
  */
 function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
 
