Loading entities with entity_type.managerfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Every node, user, taxonomy term, and comment you've ever seen on a Drupal site is an entity — and every one of them is loaded, saved, and deleted through exactly one service: entity_type.manager. Think of it as the front desk of a huge warehouse: you never walk into the storage aisles yourself and grab a node off a shelf. You go to the front desk, say "I need node 42," and the desk clerk (the storage handler) fetches it correctly, whether it's stored in the database, cached, or needs assembling from several tables. This lesson is about learning to talk to that front desk.

What you'll learn in this lesson

  • What an entity type, a bundle, and a storage handler actually are, and how they relate
  • How to get an entity's storage handler and load one or many entities by ID
  • How to query for entities that match conditions, without writing raw SQL
  • Why you should almost never call \Drupal::entityTypeManager() from inside a class that has a constructor

Entities, bundles, and storage — the vocabulary

An entity type is a category of content with its own storage and its own PHP class — node, user, and taxonomy_term are all separate entity types. Many entity types are further divided into bundles: a node entity type might have article and page bundles, each with its own fields, but all sharing the same underlying table structure and PHP class. The storage handler is the object that actually knows how to read and write a given entity type's data — you almost never touch the database directly; you ask the storage handler to do it.

Getting a storage handler

$storage = \Drupal::entityTypeManager()->getStorage('node');

getStorage() takes the entity type ID as a string and returns the correct storage object for it — a different concrete class per entity type, but always implementing the same EntityStorageInterface, so the methods you call next look the same no matter what you're loading.

Loading one entity, or several

// Load a single entity by ID.
$node = $storage->load(42);

// Load several entities at once — one query instead of several.
$nodes = $storage->loadMultiple([42, 43, 44]);

// Load every entity of this type (use with real caution on large sites).
$all_nodes = $storage->loadMultiple();

load() returns the entity object or NULL if nothing matches that ID — always check for NULL before calling a method on the result. loadMultiple() is the one to reach for whenever you already know several IDs up front: it's a single database round trip instead of N separate ones, which matters a lot once N gets past a handful.

Finding entities you don't have the ID for yet

$query = \Drupal::entityQuery('node')
  ->condition('type', 'article')
  ->condition('status', 1)
  ->accessCheck(TRUE)
  ->range(0, 10);
$nids = $query->execute();
$nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);

An entity query returns matching IDs, not the entities themselves — you still load them afterward. This two-step dance (query for IDs, then load) exists because the ID list is cheap to compute and cache, while loading full entities is comparatively expensive; keeping them separate lets Drupal optimize each half independently.

Don't skip accessCheck(). Since Drupal 9.3, every entity query requires you to explicitly call accessCheck(TRUE) or accessCheck(FALSE) — there's no silent default. Passing TRUE means the query respects the current user's permissions and quietly excludes anything they can't see; passing FALSE means you're deliberately bypassing that (fine for admin/cron contexts, dangerous if the results ever reach an anonymous visitor).

Quick check: you need to load 50 specific nodes by ID inside a loop that runs once per node. Should you call $storage->load($id) 50 times inside the loop, or once before the loop with loadMultiple($ids)? Loading before the loop wins — it's one query instead of 50, and Drupal's static entity cache means the 50 individual load() calls wouldn't even save you anything once you already have them in memory.

Key takeaways

  • \Drupal::entityTypeManager()->getStorage($entity_type_id) is the front door for loading, saving, and deleting any entity type.
  • load() fetches one entity by ID (or NULL); loadMultiple() fetches several in a single query — always prefer it in loops.
  • \Drupal::entityQuery($entity_type_id) finds IDs matching conditions; it does not return entities directly — load them as a second step.
  • accessCheck(TRUE) or accessCheck(FALSE) is mandatory on every entity query — there's no default, and getting it wrong is a real access-bypass risk.
  • Inject the entity_type.manager service via the constructor in services/plugins/forms rather than calling the static \Drupal::entityTypeManager() facade, which only belongs in procedural code (hooks, .module files) with no constructor to inject into.

Coming up next

Loading entities that already exist is one half of the picture. Next, we build a brand-new entity type from scratch — its own storage table, its own PHP class, its own admin UI — in Creating Custom Content Entities.