Skip to content

Drupal Entity System — Nodes, Users, Taxonomy and Custom Entities

DodaTech Updated 2026-06-27 10 min read

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

  1. Using db_query instead of entity queries: Direct database queries bypass entity hooks, access control, and field storage. Always use entity queries for content queries.
  2. 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.
  3. Loading entities unnecessarily in loops: Loading entities inside a foreach loop creates N+1 query problems. Use loadMultiple() and entity queries with bundle loading.
  4. 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.
  5. 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

  1. What is the difference between a content entity and a configuration entity? Give three examples of each.
  2. How would you query for all published articles created in the last month with a specific taxonomy term, sorted by creation date descending?
  3. 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.
  4. 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

What is the difference between a node and an entity?

A node is a specific type of content entity. All nodes are entities, but not all entities are nodes. Other entity types include users, taxonomy terms, media items, and custom entities. Systems like Taxonomy and File use different entity types.

How do I save an entity without triggering hooks?

Use entity->save() which always triggers hooks. To bypass hooks, run direct database queries, but this breaks entity functionality. For batch operations, use the Entity API and accept the hook overhead, or use $storage->doSave() if absolutely necessary.

What is the entity type manager?

The entity type manager is a Drupal service (\\\\Drupal::entityTypeManager()) that provides access to entity storage handlers, entity type definitions, and other entity-related services. It is the central registry for all entity types.

Can I add fields to users?

Yes. Users are content entities and can have custom fields just like nodes. Navigate to Configuration > People > Manage fields to add fields to the user entity. Common user fields include Phone, Department, Job Title, and Profile Photo.

What is the maximum number of entities Drupal can handle?

Drupal can handle millions of entities with proper database indexing and caching. The node table can contain hundreds of thousands of rows. For very high volumes (millions), use database sharding or search-based storage backends.

Mini Project

Goal: Build a project management data model using Drupal's entity system.

  1. 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)
  2. 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)
  3. Write an entity query that returns all Tasks for a specific Project that are not yet completed

  4. 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