- Since content entity bundles are configuration entities, we will need to define our configuration entity schema. Create a config/schema directory and mymodule.schema.yml file that will contain the configuration entity's schema:
mymodule.message_type.*:
type: config_entity
label: 'Message type settings'
mapping:
id:
type: string
label: 'Machine-readable name'
uuid:
type: string
label: 'UUID'
label:
type: label
label: 'Label'
langcode:
type: string
label: 'Default language'
We will define the configuration entity's config prefix as message_type, which we will provide to Drupal in the entity's annotation block. We will tell Drupal that this is a config_entity and provide a label for the schema.
With the mapping array, we provide the attributes that make up our entity and the data that will be stored.
- In our module's src/Entity directory, let's create an interface for our bundle by creating a MessageTypeInterface.php file. The MessageTypeInterface will extend the \Drupal\Core\Config\Entity\ConfigEntityInterface:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
interface MessageTypeInterface extends ConfigEntityInterface {
// Empty for future enhancements.
}
This will be implemented by our entity and will provide the method requirements. It is best practice to provide an interface for entities. This allows you to provide required methods if another developer extends your entity or if you are doing advanced testing and need to mock an object.
We will be implementing a very basic bundle. It is still wise to provide an interface in the event of future enhancements and mocking ability in tests.
- Create a MessageType.php file in src/Entity. This will hold the MessageType class, which will extend \Drupal\Core\Config\Entity\ConfigEntityBundleBase and implement our bundle's interface:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
class MessageType extends ConfigEntityBundleBase implements MessageTypeInterface {
}
In most use cases, the bundle entity class can be an empty class that does not provide any properties or methods. If a bundle provides additional attributes in its schema definition, they would also be provided here, like any other configuration entity.
- Entities need to be annotated. Create a base annotation for the id, label, entity keys, and config_export keys:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* )
*/
class MessageType extends ConfigEntityBundleBase implements MessageTypeInterface {
}
The annotation document block tells Drupal that this is an instance of the ConfigEntityType plugin. The id is the internal machine name identifier for the entity type and the label is its human-readable version. The config_prefix matches how we defined our schema using mymodule.message_type. The entity keys definition tells Drupal which attributes represent our identifiers and labels.
When specifying config_export, we are telling the configuration management system what properties are to be exported when exporting our entity.
- We will then add handlers, which will interact with our entity:
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageTypeListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* },
* },
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* )
*/
The handlers array specifies classes that provide the interaction functionality with our entity. The list builder class will be created to show you a table of our entities. The form array provides classes for forms to be used when creating, editing, or deleting our configuration entity.
- An additional handler, the route_provider, can be added to dynamically generate our canonical (view), edit, delete, and collection (list) routes:
/**
* Defines the message type entity class.
*
* @ConfigEntityType(
* id = "message_type",
* label = @Translation("Message type"),
* handlers = {
* "list_builder" = "Drupal\mymodule\MessageTypeListBuilder",
* "form" = {
* "default" = "Drupal\Core\Entity\EntityForm",
* "add" = "Drupal\Core\Entity\EntityForm",
* "edit" = "Drupal\Core\Entity\EntityForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* config_prefix = "message_type",
* bundle_of = "message",
* entity_keys = {
* "id" = "id",
* "label" = "label"
* },
* config_export = {
* "id",
* "label",
* },
* links = {
* "add-form" = "/admin/structure/message-types/add",
* "delete-form" = "/admin/structure/message-types/{message_type}/delete",
* "edit-form" = "/admin/structure/message-types/{message_type}",
* "admin-form" = "/admin/structure/message-types/{message_type}",
* "collection" = "/admin/structure/message-types"
* }
* )
*/
There is a routing service for entities that will automatically provide Drupal a route with the proper controllers based on this annotation. The add form route is not yet supported and needs to be manually added.
- We will need to modify our content entity to use the bundle configuration entity that we defined. Edit the src/Entity/Message.php file and adjust the entity annotation:
/**
* Defines the message entity class.
*
* @ContentEntityType(
* id = "message",
* label = @Translation("Message"),
* handlers = {...},
* base_table = "message",
* fieldable = TRUE,
* bundle_entity_type = "message_type",
* field_ui_base_route = "entity.message_type.edit_form",
* entity_keys = {
* "id" = "message_id",
* "label" = "title",
* "langcode" = "langcode",
* "bundle" = "type",
* "uuid" = "uuid"
* },
* links = {...},
* )
*/
The bundle_entity_type key specifies the entity type used as the bundle. The plugin validates this as an actual entity type and marks it for configuration dependencies. With the field_ui_base_route key pointed to the bundle's main edit form, it will generate the Manage Fields, Manage Form Display, and Manage Display tabs on the bundles. Finally, the bundle entity key instructs Drupal on the field definition to be used to identify the entity's bundle, which is created in the next step.
With the bundle entity key added, the ContentEntityBase class will automatically add an entity reference base field called type to our entity, referencing the bundle configuration entity type.
- Create the MessageTypeListBuilder class defined in our list_builder handler in a MessageTypeListBuilder.php file and extend \Drupal\Core\Config\Entity\ConfigEntityListBuilder. We will need to override the default implementation to display our configuration entity properties:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
class MessageTypeListBuilder extends EntityListBuilder {
public function buildHeader() {
$header['label'] = t('Label');
return $header + parent::buildHeader();
}
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
return $row + parent::buildRow($entity);
}
}
- In our list builder handler, we will override the buildHeader and builderRow methods so that we can add our configuration entity's properties to the table:

- Our module's structure should resemble the following screenshot:
