Retrieving content programmatically is a fundamental skill for any Drupal developer. Whether you are building a custom block, generating a report, or creating a custom API endpoint, you will frequently find yourself needing to fetch a list of nodes belonging to a specific content type.

While Drupal provides the powerful Views module for site builders, custom module development often requires a more direct, code-based approach. In this guide, we will explore the most efficient ways to get nodes by content type in Drupal, ensuring your code is both performant and follows modern API standards.

Understanding the Drupal Entity API

In modern Drupal (versions 8, 9, 10, and 11), everything is an entity. Nodes are part of the node entity type, and their specific configurations (like 'Article' or 'Basic Page') are known as bundles. To fetch these bundles efficiently, we avoid loading every node and filtering them in PHP. Instead, we query the database directly using the Entity API.

There are two primary ways to retrieve nodes programmatically: 1. Entity Queries: Best for complex filtering and large datasets. 2. Loading by Properties: Best for simple, direct matches.

Method 1: Using entityQuery for Precision

The entityQuery service is the most flexible way to find content. It allows you to build complex queries with conditions, sorting, and range limits. This is the preferred method when you need to filter by multiple fields or handle large amounts of data.

Here is the standard implementation to get node IDs of a specific type:

// 1. Initiate the query for node entities.
$query = \Drupal::entityQuery('node')
  ->condition('type', 'my_custom_type')
  ->accessCheck(FALSE); // Crucial for Drupal 9.2+ and 10

// 2. Execute the query to get an array of node IDs (nids).
$nids = $query->execute();

// 3. Load the actual node entities using the IDs.
$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);

Why use accessCheck(FALSE)?

In recent versions of Drupal, calling accessCheck() is mandatory. If you are running a background task or a cron job where the current user's permissions shouldn't restrict the results, set this to FALSE. If you only want to return content the current user is allowed to see, set it to TRUE.

Method 2: Using loadByProperties for Simplicity

If you don't need complex logic or sorting, the loadByProperties method is much more concise. It combines the query and the loading process into a single step. This is ideal for simple lookups in custom modules.

// Define the properties you want to match.
$values = [
  'type' => 'article',
  'status' => 1, // Only get published nodes
];

// Get the node entities directly.
$nodes = \Drupal::entityTypeManager()
  ->getStorage('node')
  ->loadByProperties($values);

This method returns an array of fully loaded node objects. While convenient, use it cautiously: loading hundreds of nodes at once can consume significant memory. If you only need the IDs, stick with entityQuery.

Method 3: Filtering by Published Status

In most real-world scenarios, you only want to retrieve nodes that are actually published. You can easily add this condition to either method. In an entity query, it looks like this:

$nids = \Drupal::entityQuery('node')
  ->condition('status', 1)
  ->condition('type', 'event')
  ->accessCheck(TRUE)
  ->execute();

$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);

By adding ->condition('status', 1), you ensure that your code ignores drafts or archived content, which is vital for front-facing website features.

Best Practices: Dependency Injection

While the examples above use static calls like \Drupal::entityQuery(), this is generally discouraged inside classes (like Controllers or Services). Instead, you should use Dependency Injection (DI) to inject the entity_type.manager service.

Using DI makes your code more testable and follows Drupal's architectural standards. Here is how you would access node storage within a service:

// Inside your class method, assuming $this->entityTypeManager is injected.
$storage = $this->entityTypeManager->getStorage('node');

// Option A: Using getQuery
$nids = $storage->getQuery()
  ->condition('type', 'article')
  ->accessCheck(FALSE)
  ->execute();
$nodes = $storage->loadMultiple($nids);

// Option B: Using loadByProperties
$nodes = $storage->loadByProperties([
  'type' => 'article',
  'status' => 1,
]);

Performance Considerations

Loading a large number of nodes can significantly impact your site's performance. If you are dealing with a content type that has thousands of entries, follow these tips:

  1. Use Range Limits: Use ->range(0, 10) in your entity query to implement pagination or limit the result set.
  2. Only Load What You Need: If you only need the node titles, don't load the full node objects. Instead, query the database tables directly or use a lighter approach.
  3. Caching: Always wrap your logic in Drupal's Cache API if the data doesn't change frequently. This prevents the database from being hit on every page load.

Frequently Asked Questions

How do I get nodes of multiple types?

You can pass an array to the condition method in an entity query: ->condition('type', ['article', 'page'], 'IN'). This will return nodes that belong to either bundle.

Can I sort the results by date?

Yes, using entityQuery, you can add ->sort('created', 'DESC') to get the most recent nodes first.

What is the difference between entityQuery and loadByProperties?

entityQuery returns only the IDs (integers), which is very fast. loadByProperties returns the full node objects, which is slower but more convenient for immediate use.

Wrapping Up

Retrieving nodes by content type in Drupal is a straightforward process once you understand the Entity API. For simple tasks, loadByProperties offers a clean, readable syntax. For more robust applications requiring filters, sorting, and scalability, entityQuery is the industry standard.

By following these patterns and utilizing Dependency Injection, you ensure your Drupal site remains fast, maintainable, and up to date with the latest development standards.