Drupal Entity System — Nodes, Users, Taxonomy and Custom Entities
In this tutorial, you'll learn Drupal's entity system including content entities versus configuration entities, the node, user, taxonomy term, and file entity types, entity API functions for CRUD operations, entity queries, and how to create custom entities.
What You'll Learn
- What entities are and how Drupal uses them as content containers
- Content entities vs configuration entities and when each is used
- Core entity types: Node, User, Taxonomy Term, File, Block Content, Comment
- Entity API functions for creating, reading, updating, and deleting entities
- Entity queries using the entity query API
- Entity fields: base fields vs bundle fields
- Entity references for linking entities together
Why It Matters
The entity system is the architectural heart of Drupal. Every piece of content, every user account, every file upload, and every taxonomy term is an entity. Understanding entities unlocks the ability to create complex data models, query content programmatically, build custom modules, and integrate with external systems through the entity API.
Real-World Use
A membership organization builds a Drupal site with entities representing Members (custom content entity), Memberships (config entity with term durations), Events (node), and Payments (custom content entity). Entity references link Members to their Memberships and Payments. A Views display shows each member's payment history. The entity API handles all CRUD operations, and the entity query API generates membership renewal reports.
Learning Path
flowchart LR A[Taxonomy] --> B[Entity System] B --> C[Views] C --> D[Media] D --> E[Revisions] E --> F[Blocks]
What are Entities
Entities are the fundamental building blocks of content in Drupal. An entity is an object that represents a piece of content or configuration. Unlike simple database records, entities are full objects with:
- Fields: Attached data (title, body, images, etc.)
- Typed data: Strongly typed values (strings, integers, dates)
- Validation: Built-in data validation
- Access control: Entity-level and field-level permissions
- Revisions: Track changes over time
- Translation: Multilingual support
- REST API: Exposed via REST and JSON:API
Content Entities vs Configuration Entities
Content Entities
Content entities contain user-generated content stored in the database. They are typically created and edited through the UI. Each content entity has an ID and can be revised and translated.
| Entity Type | Description | Example |
|---|---|---|
| node | Main content items | Article, Page, Event |
| user | User accounts | admin, editor, john |
| taxonomy_term | Taxonomy categories | Engineering, Technology |
| file | Managed files | Uploaded images, documents |
| block_content | Custom block content | Header banner, Footer info |
| comment | Comments on content | User comment on article |
| media | Media entities | Image media, Video media |
Configuration Entities
Configuration entities contain site configuration stored in YAML files. They are exported and imported as part of Configuration Management.
| Entity Type | Description | Example |
|---|---|---|
| node_type | Content type definition | article, page, event |
| field_config | Field definition | field_event_date |
| view | View configuration | frontpage, taxonomy_term |
| image_style | Image style | thumbnail, medium, large |
| user_role | User role | administrator, editor |
| menu | Menu definition | main, footer |
Node Entity
The node entity is the primary content entity in Drupal. All content types are bundles of the node entity.
<?php
// Load a node by ID
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load(42);
// Access node properties
$title = $node->getTitle();
$body = $node->body->value;
$nid = $node->id();
$type = $node->bundle();
$created = $node->getCreatedTime();
$changed = $node->getChangedTime();
$published = $node->isPublished();
// Create a new node
$node = \Drupal::entityTypeManager()
->getStorage('node')
->create([
'type' => 'article',
'title' => 'Hello World',
'body' => 'This is the body content.',
'status' => 1,
]);
$node->save();
// Update a node
$node->setTitle('Updated Title');
$node->body->value = 'Updated body content.';
$node->save();
// Delete a node
$node->delete();
User Entity
The user entity represents user accounts.
<?php
// Load current user
$current_user = \Drupal::currentUser();
$uid = $current_user->id();
// Load user by ID
$user = \Drupal::entityTypeManager()
->getStorage('user')
->load($uid);
// Access user properties
$username = $user->getAccountName();
$email = $user->getEmail();
$roles = $user->getRoles();
$display_name = $user->getDisplayName();
// Create a new user
$user = \Drupal::entityTypeManager()
->getStorage('user')
->create([
'name' => 'johndoe',
'mail' => 'john@example.com',
'pass' => 'securepassword',
'status' => 1,
'roles' => ['editor'],
]);
$user->save();
// Check access
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load(42);
if ($node->access('update', $user)) {
$node->setTitle('Edited by John');
$node->save();
}
Taxonomy Term Entity
Taxonomy terms are entities, not simple strings. This allows fields and complex queries.
<?php
// Load a term
$term = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->load(5);
// Access term properties
$name = $term->getName();
$vid = $term->bundle();
$description = $term->getDescription();
$parent_tid = $term->getParentId();
// Load parent term
if ($parent_tid) {
$parent = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->load($parent_tid);
}
// Load all children of a term
$children = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadChildren($term->id());
File Entity
Files are managed entities in Drupal. The file system tracks file usage, preventing orphaned files.
<?php
// Create a managed file
$file = \Drupal::entityTypeManager()
->getStorage('file')
->create([
'uri' => 'public://images/header.jpg',
'filename' => 'header.jpg',
'filemime' => 'image/jpeg',
'status' => 1,
]);
$file->save();
// Track file usage
\Drupal::service('file.usage')
->add($file, 'node', 'node', $node->id());
// Load files by field
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load(42);
$files = $node->get('field_image')->referencedEntities();
foreach ($files as $file) {
$url = \Drupal::service('file_url_generator')
->generateAbsoluteString($file->getFileUri());
}
Entity API Functions
Drupal provides the entity type manager service for all entity operations.
<?php
// Entity type manager service
$entity_type_manager = \Drupal::entityTypeManager();
// Storage operations
$storage = $entity_type_manager->getStorage('node');
// Load single entity
$node = $storage->load(42);
// Load multiple entities
$nodes = $storage->loadMultiple([1, 2, 3, 42]);
// Load entities by conditions
$nodes = $storage->loadByProperties([
'type' => 'article',
'status' => 1,
]);
// Access definition
$definition = $entity_type_manager->getDefinition('node');
$entity_keys = $definition->getKeys();
Entity Queries
Use entity queries for complex, performant queries with conditions, sorting, and pagination.
<?php
// Basic entity query
$ids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->condition('created', strtotime('-7 days'), '>=')
->sort('created', 'DESC')
->range(0, 10)
->accessCheck(TRUE)
->execute();
// Load the results
$articles = \Drupal::entityTypeManager()
->getStorage('node')
->loadMultiple($ids);
// Query with taxonomy condition
$ids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('field_tags', 5)
->condition('status', 1)
->sort('created', 'DESC')
->accessCheck(TRUE)
->execute();
// Query with OR condition group
$ids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->condition(
$query->orConditionGroup()
->condition('field_tags', 5)
->condition('field_tags', 7)
)
->sort('created', 'DESC')
->accessCheck(TRUE)
->execute();
// Aggregate query
$query = \Drupal::entityQueryAggregate('node')
->condition('type', 'article')
->condition('status', 1)
->groupBy('uid')
->aggregate('uid', 'COUNT');
Entity Fields: Base vs Bundle Fields
Base Fields
Fields defined by the entity type itself, present on all bundles:
<?php
// Node base fields
$title = $node->title->value;
$uid = $node->uid->target_id;
$created = $node->created->value;
$changed = $node->changed->value;
$status = $node->status->value;
$promote = $node->promote->value;
$sticky = $node->sticky->value;
Bundle Fields
Fields created through the UI and attached to specific bundles:
<?php
// Node bundle field access
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load(42);
// Check if field exists on this bundle
if ($node->hasField('field_event_date')) {
$event_date = $node->field_event_date->value;
}
// Field item list iteration
$field_tags = $node->get('field_tags');
foreach ($field_tags as $item) {
$term = $item->entity;
if ($term) {
$term_name = $term->getName();
}
}
Entity References
Entity references link entities together, creating relationships.
<?php
// Create entity reference field value
$node->field_speaker = [
'target_id' => 42, // User entity ID
];
$node->save();
// Load referenced entity
$speaker = $node->field_speaker->entity;
$speaker_name = $speaker->getDisplayName();
// Load multiple references
$tags = $node->field_tags->referencedEntities();
foreach ($tags as $tag) {
$tag_name = $tag->getName();
}
Custom Entity Types
For complex applications, create custom entity types:
<?php
// src/Entity/Member.php
namespace Drupal\members\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines the Member entity.
*
* @ContentEntityType(
* id = "member",
* label = @Translation("Member"),
* base_table = "member",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* },
* )
*/
class Member extends ContentEntityBase {
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setRequired(TRUE);
$fields['member_id'] = BaseFieldDefinition::create('string')
->setLabel(t('Member ID'))
->setRequired(TRUE);
$fields['join_date'] = BaseFieldDefinition::create('datetime')
->setLabel(t('Join Date'))
->setRequired(TRUE);
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Active'))
->setDefaultValue(TRUE);
return $fields;
}
}
Common Mistakes
- Using
db_queryinstead of entity queries: Direct database queries bypass entity hooks, access control, and field storage. Always use entity queries for content queries. - Not using access checks in entity queries: Entity queries without
accessCheck(TRUE)may return content the current user should not see. Always set the access check. - Loading entities unnecessarily in loops: Loading entities inside a foreach loop creates N+1 query problems. Use
loadMultiple()and entity queries with bundle loading. - Treating config entities like content entities: Configuration entities are stored in YAML files, not the database. They behave differently and should not be used for user-generated content.
- Not using referencedEntities() for entity references: Accessing entity reference fields through entity properties instead of
referencedEntities()misses the actual entity object and its methods.
Practice Questions
- What is the difference between a content entity and a configuration entity? Give three examples of each.
- How would you query for all published articles created in the last month with a specific taxonomy term, sorted by creation date descending?
- Write PHP code to create a new user with the role "editor", link them as the author of an existing article, and set a field reference to a taxonomy term.
- Challenge: Create a custom content entity type for "Invoice" with fields for Invoice Number (string), Amount (decimal), Due Date (date time), Client (entity reference to User), Status (list: Paid, Unpaid, Overdue), and Items (text long, unlimited). Implement the entity class with base field definitions, create a route for the entity listing page, and write a Drush command to generate monthly invoice reports as CSV.
FAQ
Mini Project
Goal: Build a project management data model using Drupal's entity system.
Create a custom content entity type called "Project" with fields:
- Project Name (string)
- Description (text long, formatted)
- Start Date (datetime)
- End Date (datetime)
- Budget (decimal)
- Status (list: Planning, Active, On Hold, Completed, Cancelled)
- Team Members (entity reference to User, unlimited)
- Client (entity reference to User)
Create a custom content entity type called "Task" with fields:
- Task Title (string)
- Description (text long)
- Due Date (datetime)
- Priority (list: Low, Medium, High, Critical)
- Status (list: To Do, In Progress, Review, Done)
- Assigned To (entity reference to User)
- Project (entity reference to Project)
Write an entity query that returns all Tasks for a specific Project that are not yet completed
Create a Drush command that generates a project status report showing each project's name, status, and count of tasks by status group
What's Next
With the entity system understood, learn how to create dynamic content listings with the Views module. Then explore media and image styles for managing rich media content.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro