Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll
12/04/2025, by Ivan

Audience

This documentation is primarily intended for developers experienced in object-oriented PHP, Drupal 6 or Drupal 7, and for those looking to explore the principles of Drupal 8.

The guide to creating a content entity type in Drupal 8 contains a full list of available options.

Building a Bundle-less Content Type in Drupal 8

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

The entity does not implement the Field API, so it remains in code only. Nevertheless, it can serve as a useful framework for building content entities, especially as we import more complex data later.

Where applicable, I’ll reference relevant OOP concepts and documentation.

Background

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

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

Defining the Content Entity in Drupal 8

First, all custom modules now reside in modules/custom instead of sites/all/modules/custom.

Inside our custom module, the file structure will look like this:

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

For reference, you can check out a complete Advertiser entity implementation including tests and constraint plugins, though we’ll keep it simpler here.

Info File

We start by defining our module in advertiser.info.yml:

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

Entity Skeleton

The basic Advertiser entity class and its schema are defined in src/Entity/Advertiser.php.

First, we define the namespace:

namespace Drupal\advertiser\Entity;

Then we define the entity using annotations:

This is the actual definition of the entity type. Drupal reads and caches this, so remember to clear the cache after any changes.
/**
 * Defines the Advertiser entity.
 *
 * @ingroup advertiser
 *
 * @ContentEntityType(
 *   id = "advertiser",
 *   label = @Translation("Advertiser"),
 *   base_table = "advertiser",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 * )
 */

Since this is a minimal entity, we only use a few properties, without handlers like AccessControlHandler.

We now have a functional module defining a custom content entity, but you’ll notice that the advertiser table hasn’t yet been created in the database.

$ drush sql-cli
mysql> SHOW TABLES;

This is because our class doesn’t yet include methods that interact with the database directly. We must define the minimum methods required for proper entity-database interaction.

ContentEntityBase

We import ContentEntityBase to gain essential DB-related methods by adding the following use statement:

use Drupal\Core\Entity\ContentEntityBase;

Then, we extend ContentEntityBase and implement ContentEntityInterface:

class Advertiser extends ContentEntityBase implements ContentEntityInterface {

Now we need to use some built-in methods to store data. Let’s define the basic fields for our entity.

baseFieldDefinitions

The baseFieldDefinitions method comes from ContentEntityBase and expects one argument:

Entity type definition. Useful when a class is reused for multiple (possibly dynamic) entity types.

It returns:

An array of base field definitions keyed by field name.

Our implementation might look like this:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  $fields['id'] = BaseFieldDefinition::create('integer')
    ->setLabel(t('ID'))
    ->setDescription(t('The ID of the Advertiser entity.'))
    ->setReadOnly(TRUE);

  $fields['uuid'] = BaseFieldDefinition::create('uuid')
    ->setLabel(t('UUID'))
    ->setDescription(t('The UUID of the Advertiser entity.'))
    ->setReadOnly(TRUE);

  return $fields;
}

Recap: Full Entity Class

setLabel(t('ID'))
      ->setDescription(t('The ID of the Advertiser entity.'))
      ->setReadOnly(TRUE);

    $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 the advertiser table added to the database:

drush sql-cli
mysql> SHOW TABLES;

or:

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

If your module is already installed, you’ll need to run an entity update.

In issue #2976035: CRUD operations should use latest entity type and field storage definitions, remote update ability was removed. See the corresponding change record for details.

Also consider using the Devel Entity Updates module.

?>

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.