Creating a custom content entity

Last updated on
5 May 2022

Audience

This documentation is primarily for developers with experience programming with object-oriented PHP, Drupal 6 or Drupal 7 development, and who are looking to learn Drupal 8 principles.

The documentation for creating a content entity type in Drupal 8 includes a comprehensive list of available options.

Building a bundle-less content entity type in Drupal 8.

In this case we create a Drupal 8 content entity that does not have any bundles.

The entity does not implement the Field API so it remains in code throughout. Nevertheless it can be a useful skeleton for building out Content Entities as we import more complex data later.

Finally, where there are some OOP concepts, I will refer to the relevant docs.

Background.

Our module is called advertiser.
Our content entity type is called advertiser.

Our new Drupal 8 advertiser content entity will have basetable fields:
- UUID
- ID

Defining a Content Entity in Drupal 8.

Inside our custom entity the file structure that we will end up with looks like this:

modules/custom/advertiser$
├── advertiser.info.yml
└── src
    └── Entity
         └── Advertiser.php

For reference, you can review the finished Advertiser entity along with added extras like tests and constraint plugins, but let's keep things simple for the time being.

Info file

We start by defining our custom module in module_name.info.yml. This is self explanatory:

name: Advertiser
type: module
description: 'Barebones advertiser entity'
package: custom
core: 8.x
core_version_requirement: ^8 || ^9

Entity skeleton

Meanwhile the basic Advertiser entity class and associated schema is defined in src/Entity/Advertiser.php.

The first thing we do is define a namespace for our Advertiser Entity Class. This will come in handy whenever we want to use our classes.
namespace Drupal\advertiser\Entity;

Now it is time to define our entity, which we do in an annotation.

This is the actual definition of the entity type it is read and cached so be sure to clear the cache after any changes.

<?php
/**
 * Defines the Advertiser entity.
 *
 * @ingroup advertiser
 *
 * @ContentEntityType(
 *   id = "advertiser",
 *   label = @Translation("Advertiser"),
 *   base_table = "advertiser",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 * )
 */

Because this is a barebones entity we only use a few properties and no handlers like the Access module.

We have a functional module that defines our custom content entity but we will see that the 'advertiser' table has not been created in the database.

$ drush sql-cli
mysql> SHOW TABLES;

This is because our class doesn't have any methods that explicitly interact with the database. Furthermore we need a description of the bare minimum of methods needed for an entity to interface satisfactorily with the Database.

ContentEntityBase

Generally we can add classes by adding something like use Drupal\Core\Entity\ContentEntityBase; after our namespace definition at the top of our script. This makes these methods available to our own class, which can extend them or in the case of Interfaces, implement them.

We do two things, we extend an existing ContentEntityBase class that already has the necessary methods to interact with the DB, and implement an ContentEntityInterface to describe...

the methods that we need to access our database. It does NOT describe in any way HOW we achieve that. That's what the IMPLEMENTing class does. We can IMPLEMENT this interface as many times as we need in as many different ways as we need. We can then switch between implementations of the interface without impact to our code because the interface defines how we will use it regardless of how it actually works. - https://secure.php.net/manual/en/language.oop5.interfaces.php

All this means is that we end up with the following: Tip Remember to add any new classes through a use statement at the top of our script:

class Advertiser extends ContentEntityBase implements ContentEntityInterface {

But we still need to use these new useful methods to put something in the database, we start with the basic fields for our entity.

baseFieldDefinitions

The method baseFieldDefinitions comes from ContentEntityBase class that we are extending.
It takes one parameter:

The entity type definition. Useful when a single class is used for multiple, possibly dynamic entity types.

And it returns

An array of base field definitions for the entity type, keyed by field name.

So we implement it like this:

<?php
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
      
    // Standard field, used as unique if primary index.
    $fields['id'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('ID'))
      ->setDescription(t('The ID of the Advertiser entity.'))
      ->setReadOnly(TRUE);

    // Standard field, unique outside of the scope of the current project.
    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The UUID of the Advertiser entity.'))
      ->setReadOnly(TRUE);
      
    return $fields;
  }

It's worth noting that:

BaseFieldDefinitions

"Provides base field definitions for an entity type."

- It is a public static method from the FieldableEntityInterface.

BaseFieldDefinition

"A class for defining entity fields."

- All the methods we need to create fields, add constraints, etc...

Complete entity

So altogether we have this:

<?php

namespace Drupal\advertiser\Entity;

use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\ContentEntityInterface;

/**
 * Defines the advertiser entity.
 *
 * @ingroup advertiser
 *
 * @ContentEntityType(
 *   id = "advertiser",
 *   label = @Translation("advertiser"),
 *   base_table = "advertiser",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 * )
 */

class Advertiser extends ContentEntityBase implements ContentEntityInterface {

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

    // Standard field, used as unique if primary index.
    $fields['id'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('ID'))
      ->setDescription(t('The ID of the Advertiser entity.'))
      ->setReadOnly(TRUE);

    // Standard field, unique outside of the scope of the current project.
    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The UUID of the Advertiser entity.'))
      ->setReadOnly(TRUE);

    return $fields;
  }
}

After installing your module you should see that the 'advertiser' table has been added to the database!

$ drush sql-cli
mysql> SHOW TABLES;

or

drush sqlq "show tables like 'advertiser'"
drush sqlq "describe advertiser"

If your module was already installed you will need to run entity updates. 

In #2976035: Entity type CRUD operations must use the last installed entity type and field storage definitions the ability to run drush entup was removed, see the related change record for more details.

Please see the module Devel Entity Updates.

Help improve this page

Page status: No known problems

You can: