Skip to content

ConceptFor module developersDrupal 11

Entities, bundles, and fields

Almost every piece of data in a Drupal site, from an article to an image style, is an entity. This page gives you the model behind that statement: what entity types, bundles, and fields are, how content entities differ from configuration entities, and where the typed data layer fits underneath.

An entity is a typed, identifiable object that Drupal can load, save, validate, render, translate, and control access to through one shared API. Nodes (content items), users, taxonomy terms (categories), media items, comments, and files are entities. So are content types, user roles, views, and image styles.

The payoff of one shared model is enormous. Because the Entity API defines a common contract, a single entity query API works across all of them, Views can list any of them, JSON:API can expose all of them without per-type code, and the translation, revision, and access systems are written once. When you define a custom entity type, your data inherits all of that for free.

Without entities, every module would invent its own tables, loaders, and permission checks, and nothing would compose. That was largely the situation before Drupal 8; the Entity API is the answer to it.

Hold on to four words: type, bundle, field, property.

  • An entity type defines a kind of thing: its PHP class, where it is stored, and which handlers (storage, access, forms, lists) manage it. Examples: node, user, media.
  • A bundle is a variant of an entity type with its own field configuration. The node entity type has bundles like article and page. Some entity types (like user) have a single implicit bundle.
  • A field attaches typed, possibly multi-valued data to a content entity, per bundle.
  • A property is one primitive value inside a field item, like the value of a text field or the target_id of a reference.

The invariant worth memorizing: an entity type owns behavior, a bundle owns field configuration, a field owns data.

Type, bundle, field

The node entity type has many bundles, each a node_type config entity such as article. node declares bundle_entity_type: node_type and node_type declares bundle_of: node. Each bundle has many fields, and each field holds typed data. A content type is a bundle, not an entity type.

Content entities vs configuration entities

Section titled “Content entities vs configuration entities”

Every entity type is one of two families, and the distinction runs through everything you do in Drupal (see config, content, state, and sessions for the storage side):

Content entitiesConfiguration entities
Examplesnodes, users, media, commentscontent types, roles, views, image styles
Defined withContentEntityType attributeConfigEntityType attribute
Stored indatabase tables (base_table, data_table, …)config storage, exportable as YAML
Data modelfields built on typed dataplain class properties listed in config_export
Revisionssupportedno
Translationsper field, through Content Translationper translatable schema value, through Configuration Translation
Deployed between environmentsno (it is content)yes (it is configuration)

The two families meet in one elegant place: the bundles of a content entity type are usually themselves config entities. The article content type is a node_type config entity; deploying it to production is a config export, while the articles themselves stay content. The link is declared on the entity type: node sets bundle_entity_type: 'node_type', and node_type sets bundle_of: 'node'.

Entity type definitions are attributes on the class

Section titled “Entity type definitions are attributes on the class”

An entity type is defined by putting a PHP attribute on its class. The attribute carries the machine name, storage tables, entity keys (which field is the ID, the label, the bundle), handlers, and link templates. Abbreviated from the attribute on core/modules/node/src/Entity/Node.php:

#[ContentEntityType(
id: 'node',
entity_keys: ['id' => 'nid', 'bundle' => 'type', 'label' => 'title'],
base_table: 'node',
)]

The real attribute on the Node class also declares its label, handlers (storage, access control, forms, views data), revision and data tables, link templates, and bundle_entity_type: 'node_type'. Open the file; it reads as a complete inventory of what an entity type can configure.

Entity types are discovered and managed by \Drupal\Core\Entity\EntityTypeManager, which is a plugin manager: entity types are plugins, discovered by scanning for these attributes. The entity type manager is also your gateway to working with entities in code. You never instantiate storage yourself; you ask for the handler:

// Fragment: $entityTypeManager is the injected entity_type.manager service.
$storage = $this->entityTypeManager->getStorage('node');
$node = $storage->load(42);

The create, load, and query entities guide covers the full create, read, update, delete (CRUD) and query API with working code.

Reading $node->body does not give you a string. Every field on a content entity is a FieldItemList: an ordered list of field items, where each item holds one or more properties. A three-value tags field is one list with three items, each item carrying a target_id property and a computed entity property.

// Fragment: $node is a loaded node entity.
$text = $node->get('body')->value; // First item's "value" property.
$tag = $node->get('field_tags')->entity; // First item's referenced entity.
$all = $node->get('field_tags')->referencedEntities(); // All of them.
$node->get('field_tags')->appendItem(['target_id' => 7]);

Field definitions come in two flavors that mirror the type/bundle split: base fields are defined in code on the entity class (in baseFieldDefinitions()) and exist for every bundle; configurable fields are config entities (field_storage_config and field_config) attached to specific bundles, which is what you create at Administer → Structure → Content types → Manage fields/admin/structure/types.

The Field API sits on the Typed Data API, managed by \Drupal\Core\TypedData\TypedDataManager. Every property of every field item is a typed data object with a data type (string, integer, entity_reference, …), constraints, and metadata. Two things you will meet that come from this layer rather than the Field API:

  • Validation. $entity->validate() runs constraint validation defined at the typed data level. Constraints in entity type attributes and field definitions all resolve here.
  • Data addressing. Anything that needs to point into structured data uses typed data property paths, like field_tags.0.target_id (field, delta, property). Entity query conditions use the same dotted vocabulary, as in condition('field_tags.target_id', $tid).

You rarely use the typed data manager directly, but knowing the layer exists explains why fields, validation, and entity queries share one vocabulary.

By default, loading any node gives you Drupal\node\Entity\Node regardless of bundle. A bundle class lets a specific bundle get its own PHP class, so an article node can expose methods like $article->isBreakingNews() instead of scattering that logic across services. The class must extend the entity type’s class.

In Drupal 11.3 you register a bundle class by altering the bundle info (hooks and events explains the #[Hook] syntax):

#[Hook('entity_bundle_info_alter')]
public function entityBundleInfoAlter(array &$bundles): void {
$bundles['node']['article']['class'] = Article::class;
}

Bundle classes by attribute Added in 11.4

Section titled “Bundle classes by attribute ”

From Drupal 11.4, the Bundle attribute replaces that boilerplate: place it on the bundle class itself and discovery does the rest (see the change record). Put the class in your module’s src/Entity/ directory (for example Drupal\mymodule\Entity\Article); discovery scans only that namespace.

#[Bundle(entityType: 'node', bundle: 'article')]
class Article extends Node {
// Bundle-specific methods live here.
}

The attribute can also define bundles outright (with a label) for entity types whose bundles are not backed by a bundle config entity.

  • “A content type is an entity type.” A content type (article) is a bundle of the node entity type, represented by a node_type config entity. “Entity type” always means the top-level kind: node, user, media.
  • “A field is a database column.” A field is a list of items with typed properties; storage is derived from the definition (single-value base fields share the entity’s table; every configurable field and every multi-value base field gets its own table). Write code against the field API, not against assumed table layouts.
  • “Reading a field gives me its value.” $node->get('body') returns an item list. You reach values through properties: ->value, ->target_id, ->entity. Code that treats a field as a scalar breaks on multi-value fields.
  • “Config entities have fields too.” They do not. Config entities use plain class properties described by config schema. The Field API, typed-data-backed fields, revisions, and field translation apply only to content entities.
  • “Entity queries return entities.” They return entity IDs; load them with the storage handler afterwards. Every entity query must also declare accessCheck(TRUE) or accessCheck(FALSE) explicitly.
  • “create() saves the entity.” $storage->create([...]) builds an object in memory; nothing exists in the database until $entity->save().

Was this page helpful?

Drupal is a registered trademark of Dries Buytaert.