Introduction to Entity API in Drupal 8

Last updated on
6 March 2024

This documentation needs review. See "Help improve this page" in the sidebar.

For the Drupal 7 Entity API: Go here.

Video Link: Entity Basics.

The Entity System is the API for entity manipulation (CRUD: create, read, update, delete). Entity validation has its own API (which could validate an Entity saved via REST, rather than a form, for example). While the Entity API is used for creating custom entity types, the Update API is used for modifying existing types.

The Drupal 8 entity system

Entities are typed classes with methods
Generic methods $entity->id()
Entity type specific methods $node->getTitle()

Both types are defined and documented in interfaces.

Configuration Entity and Content Entity

Entity types in core come in two variants.

Configuration Entity
Used by the Configuration System. Supports translations and can provide custom defaults for installations. Configuration entities are stored within the common config database table as rows.
Content Entity
Consist of configurable and base fields and can have revisions and support translations. Content entities are stored within a custom database table as rows. The table name is the same as the content entity "id", and the columns are defined by the entity's "baseFieldDefinitions" method.

Bundles

Bundles are variants of an entity type. For example, with the node entity type, the bundles are the different node types, such as 'article' and 'page'.

Typically, a bundle is represented by a Configuration Entity, though other models exist in contrib modules. So in the node example, the 'article' node type is itself a configuration entity. The  configuration stores the differences between the content entity types, such as settings and fields. When creating a new entity type that has bundle entities, you will create both a Content Entity that will manage the content's details and operations, and a Configuration Entity that will handle the differences between the content entity types.

Annotations

When creating a new entity type, you'll need to use the annotations system built into core. Annotations look like docblock comments above the class, but are parsed and cached by Drupal core. In a lot of ways annotations replace some of the older style hooks used in Drupal 7.

Annotation parser

Annotations are read and parsed at runtime by an annotation engine. Drupal 8 uses the Doctrine annotation parser, which turns it into an object that PHP can use.

Syntax - The annotation syntax is surrounded with @ClassName(), is predominantly made up of key/value pairs, and can contain arrays that use curly braces. Top level keys must not be surrounded with quotation marks, while array keys must. Each key/value pair should be on their own line, and that line should end with a comma. Certain functions can be executed on values, notably the @Translation()function.

Non-working example of annotation syntax:

/**
 * @ContentEntityType(
 *   id = "my_entity_type_id",
 *   label = @Translation("My entity type label"),
 *   example_pair = "this_examples_value",
 *   example_array = {
 *     "array_key" = "array_value",
 *     "some_other_key" = "some_other_value",
 *   },
 * )
 */

Common top level annotations

Key = "Example Value", Description Entity Variant
id = "node", The machine name for the entity type. Content & Config
label = @Translation("Node"), The human-readable name for the entity type. Content & Config
admin_permission = "administer nodes", Permission that allows administrative access to configure and manage the entity type. This is necessary if your entity does not define an "access" handler. Content & Config

bundle_label = @Translation("Content type"),

The optional human-readable name for the bundle entity type. Content
bundle_entity_type = "node_type", When creating a Content Entity that has bundles, this value should be the "id" of the Content Entity. In this case, "node_type" is a Content Entity. Content
base_table = "node", Database table name for the entity type. Content

field_ui_base_route = "entity.node_type.edit_form",

The name of the route the Field UI is attached to on a fieldable entity. Content

Handlers

Handlers are defined in the entity annotation as an array. They support the entity by mapping certain parts of its execution to other PHP classes. Those classes will "handle" the assigned parts of the entity's execution.

Storage - Handles the loading, saving and deleting of the entity. By default, Content Entities use Drupal\Core\Entity\Sql\SqlContentEntityStorage while Config Entities use Drupal\Core\Config\Entity\ConfigEntityStorage. You can define a storage handler to extend the default storage methods of your entity. You may want to do this to provide additional methods for gathering entity revision ids or determining the number of translations an entity has.
Example: "storage" = "Drupal\node\NodeStorage",

Form - In any entity's handlers annotation there are multiple form handlers that map the entity's add, edit, and delete forms to other PHP classes. 
Example: 

"form" = {
  "add" = "Drupal\block\BlockForm",
  "edit" = "Drupal\block\BlockForm",
  "delete" = "Drupal\block\Form\BlockDeleteForm",
​​​​​​​}

Alternatively, you can define a "default" form to handle both the "add" and "edit" form instead of defining them separately. Worth noting is that the "delete" form will almost always be handled by a separate class from the other forms. This is because the delete form is generally a "confirmation form" that simply asks if the user is sure they want to delete the entity.

View builder - This handler provides a class that will handle the output of the entity when viewed by the end user. For example, when visiting a node on a Drupal 8 site, entity's output is handled by the NodeViewBuilder class.
Example: "view_builder" = "Drupal\node\NodeViewBuilder",

List builder - The list builder class will handle the list of entities for administrative purposes. This class will define the contents of the headers, rows, and operations when visiting the entities management page for the entity. For example, when visiting the /admin/content URI of your Drupal site, the table contents are provided by the Node entity's list builder class.
Example: "list_builder" = "Drupal\node\NodeListBuilder",

Route provider - An optional handler that, if implemented, generates routes for your entity management. Implementing this handler can replace the need for entity routes defined in the routing.yml file of your module. Note, the route_provider works in conjunction with the Links defined on your entity (see example below Links section). The route_provider annotation is an array.
Example: 

"route_provider" = {
  "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
}

Access - The access handler can be used to dynamically check permissions for your entity. It is a mapping to a class that implements the EntityAccessControlHandlerInterface. Core provides an implementation of this interface as EntityAccessControlHandler, but for reliable control over your entity you will want to extend that class with your own.
Example: "access" = "NodeAccessControlHandler",

Views data - The views_data handler allows an entity to extend the Views module with custom data provided by your entity. This can be for adding your entity's baseFieldDefinitions as Views fields, joining tables on entity relationships, or other Views related data alterations.
​​​​​​​Example: "views_data" = "Drupal\node\NodeViewsData",

Storage schema - The storage_schema handler can be implemented to further alter the entity's database storage settings. For instance, adding additional table indexes.
Example: "storage_schema" = "Drupal\node\NodeStorageSchema", 

Translation - The translation handler can be used to alter the way in which your entity forms interact with translations.
Example: "translation" = "Drupal\node\NodeTranslationHandler", 

Full handlers example:

Drupal core provides handlers that you can use out of the box, but in many cases you'll want to extend these classes with your own for greater control and customization of your entity. This example shows a more-complete handlers annotation, using the core classes you can extend.

handlers = {
  "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
  "access" = "Drupal\Core\Entity\EntityAccessControlHandler",
  "views_data" = "Drupal\views\EntityViewsData",
  "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
  "storage_schema" = "Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema",
  "translation" = "Drupal\content_translation\ContentTranslationHandler",
  "form" = {
    "default" = "Drupal\Core\Entity\ContentEntityForm",
    "add" = "Drupal\Core\Entity\ContentEntityForm",
    "edit" = "Drupal\Core\Entity\ContentEntityForm",
    "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  },
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
  },
},

Links are defined in the entity annotation with the array syntax. Links have a specific set of keys whose value are URIs where the entity type or single entities of that type can be managed. Both Content and Configuration Entities can have these links defined.

Example:

id = "node",
handlers = {
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider"
  }
},
links = {
  "canonical" = "/node/{node}",
  "add-page" = "/node/add",
  "add-form" = "/node/add/{node_type}",
  "edit-form" = "/node/{node}/edit",
  "delete-form" = "/node/{node}/delete",
  "collection" = "/admin/content",
},

Note, this is not taken verbatim from the Node module, it is only an example.

Creating these links does not automatically create the routes for those URIs. To make these links accessible, your module will need to implement its own routing.yml file, or use a "route_provider" handler in the entity annotation.

The above links working together with a "route_provider" will make the following named routes available to Drupal.
 

Links Key Route Name Route Example URI Description
canonical entity.node.canonical /node/1 View a specific node
add-page entity.node.add_page /node/add Select bundle of node to be added
add-form entity.node.add_form /node/add/article Add a node (of a specific bundle)
edit-form entity.node.edit_form /node/1/edit Edit form for a specific node
delete-form entity.node.delete_form /node/1/delete Delete form for a specific node
collection entity.node.collection /admin/content View all nodes as a list

These links can be accessed with an entity's toUrl() method:

$view_url_object = $entity->toUrl();  // Default is 'canonical'
$edit_url_string = $entity->toUrl('edit-form')->toString();

References:


This page is still a stub, documentation is being written while the Entity API is finalized for Drupal 8.

TODO/Planned:
- Handlers
- - Storage
- - Storage Schema
- - Access
- - Forms
- - View Builder
- - Views Data
- - List Builder
- Bundles
- Configuration entities
- Content entities
- - Revisions
- - Translations
- - Fields
- UUID support
- Entity query
- Routing

Drupal 8 Entity API issues being worked on:

Help improve this page

Page status: Needs review

You can: