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.
What and why
Section titled “What and why”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.
Mental model
Section titled “Mental model”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
nodeentity type has bundles likearticleandpage. Some entity types (likeuser) 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
valueof a text field or thetarget_idof a reference.
The invariant worth memorizing: an entity type owns behavior, a bundle owns field configuration, a field owns data.
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 entities | Configuration entities | |
|---|---|---|
| Examples | nodes, users, media, comments | content types, roles, views, image styles |
| Defined with | ContentEntityType attribute | ConfigEntityType attribute |
| Stored in | database tables (base_table, data_table, …) | config storage, exportable as YAML |
| Data model | fields built on typed data | plain class properties listed in config_export |
| Revisions | supported | no |
| Translations | per field, through Content Translation | per translatable schema value, through Configuration Translation |
| Deployed between environments | no (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.
Fields are item lists, not values
Section titled “Fields are item lists, not values”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.
Typed data underneath
Section titled “Typed data underneath”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 incondition('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.
Bundle classes
Section titled “Bundle classes”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.
How core uses it
Section titled “How core uses it”- The Node module is the canonical content entity.
Drupal\node\Entity\Nodecarries the full#[ContentEntityType]attribute (storage tables, revision tables, entity keys, handlers, link templates), andDrupal\node\Entity\NodeTypeis the matching#[ConfigEntityType]bundle entity. Reading those two files teaches more than any tutorial. - Users show the single-bundle case.
Drupal\user\Entity\Userdefines nobundle_entity_type; all of its fields are base fields or configurable fields on the one implicit bundle. - The entity type manager treats entity types as plugin definitions.
\Drupal\Core\Entity\EntityTypeManagerextends\Drupal\Core\Plugin\DefaultPluginManager, caches the discovered definitions, and lets modules adjust them withhook_entity_type_build()andhook_entity_type_alter(). - Views and JSON:API are pure consumers. Neither contains per-entity-type storage code; both read entity type definitions and field metadata at runtime. That is the shared-contract payoff made visible.
Common misconceptions
Section titled “Common misconceptions”- “A content type is an entity type.” A content type (
article) is a bundle of thenodeentity type, represented by anode_typeconfig 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)oraccessCheck(FALSE)explicitly. - “create() saves the entity.”
$storage->create([...])builds an object in memory; nothing exists in the database until$entity->save().
Where to go next
Section titled “Where to go next”- Create, load, and query entities: the working-code guide to entity CRUD and entity queries.
- Define a custom entity type: write your own content entity with the attribute, handlers, and base fields.
- Config, content, state, and sessions: where each family of entity data lives and how it deploys.
- Access: users, roles, and permissions: how entity access handlers decide who can view and edit.
Entity API topic on api.drupal.org, withTyped Data APIfor the layer underneath.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.