As Drupal continues to evolve, version 11.3.0 brings a significant shift in how developers interact with node revisions. The NodeStorage class, once a go-to for revision-related operations, has seen most of its methods deprecated. This change is part of a broader effort to streamline the API and encourage the use of entity queries.
Let’s break down what’s changed, why it matters, and how to adapt your code.
🔧 What’s Deprecated?
The following methods in Drupal\node\NodeStorage are now deprecated:
revisionIds($node)userRevisionIds($account)countDefaultLanguageRevisions()
Only clearRevisionsLanguage() remains active.
🆕 Recommended Replacements
1. revisionIds($node) → Use Entity Query
Before:
$revision_ids = $node_storage->revisionIds($node);
After:
$query = \Drupal::entityQuery('node')
->allRevisions()
->condition('nid', $node->id())
->accessCheck(FALSE);
$revision_ids = array_keys($query->execute());
This approach uses the entity query system to fetch all revision IDs for a given node.
2. userRevisionIds($account) → Use Entity Query
Before:
$revision_ids = $node_storage->userRevisionIds($account);
After:
$query = \Drupal::entityQuery('node')
->allRevisions()
->accessCheck(FALSE)
->condition('uid', $account->id());
$revision_ids = array_keys($query->execute());
This retrieves all revisions authored by a specific user.
3. countDefaultLanguageRevisions() → Removed
This method has been completely removed with no replacement, as it was unused in Drupal core. If your module relied on it, you’ll need to implement custom logic or reconsider its necessity.
🧠 Why the Change?
Drupal is moving toward a more consistent and flexible architecture. Entity queries offer:
- Better performance
- Improved access control
- Greater flexibility for custom conditions
By deprecating tightly coupled storage methods, Drupal encourages developers to write cleaner, more maintainable code.
🛠️ Migration Tips
- Audit your custom modules for usage of deprecated methods.
- Replace them with entity queries as shown above.
- Use
accessCheck(FALSE)cautiously—only when you're sure access control isn't needed. - Test thoroughly, especially if revisions are critical to your workflow.
📌 Final Thoughts
While deprecations can feel disruptive, they’re often a sign of progress. Drupal’s push toward entity queries aligns with modern development practices and opens the door to more powerful, scalable solutions.
If you’re working on a module or site that relies heavily on node revisions, now’s the time to refactor. Need help rewriting a specific snippet or debugging a migration? Drop it in—I’ve got your back.
📦 Example
Custom Drupal module that exposes filtered node revisions via a JSON API. This is perfect for building admin dashboards, REST integrations, or AJAX-powered interfaces.
📦 Step 1: Create a Custom Module — custom_revision_api
Folder structure:
custom_revision_api/
├── custom_revision_api.info.yml
├── custom_revision_api.routing.yml
├── src/
│ └── Controller/
│ └── RevisionController.php
📄 custom_revision_api.info.yml
name: 'Custom Revision API'
type: module
description: 'Provides filtered node revision data via JSON.'
core_version_requirement: ^11
package: Custom
🌐 custom_revision_api.routing.yml
custom_revision_api.revisions:
path: '/custom-revisions'
defaults:
_controller: '\Drupal\custom_revision_api\Controller\RevisionController::getRevisions'
_title: 'Filtered Revisions'
requirements:
_permission: 'access content'
🧠 RevisionController.php
namespace Drupal\custom_revision_api\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\user\Entity\User;
class RevisionController extends ControllerBase {
public function getRevisions(Request $request): JsonResponse {
$type = $request->query->get('type', 'article');
$uid = $request->query->get('uid');
$langcode = $request->query->get('langcode');
$created = $request->query->get('created');
$limit = (int) $request->query->get('limit', 10);
$page = (int) $request->query->get('page', 1);
$author = $uid ? User::load($uid) : NULL;
$created_timestamp = $created ? strtotime($created) : NULL;
$query = \Drupal::entityQuery('node')
->allRevisions()
->accessCheck(FALSE)
->condition('type', $type)
->sort('revision_timestamp', 'DESC')
->range(($page - 1) * $limit, $limit);
if ($author) {
$query->condition('uid', $author->id());
}
if ($langcode) {
$query->condition('langcode', $langcode);
}
if ($created_timestamp) {
$query->condition('created', $created_timestamp, '>');
}
$revision_ids = array_keys($query->execute());
$revisions = \Drupal::entityTypeManager()
->getStorage('node')
->loadMultipleRevisions($revision_ids);
$data = [];
foreach ($revisions as $rev) {
$data[] = [
'nid' => $rev->id(),
'title' => $rev->label(),
'uid' => $rev->getOwnerId(),
'langcode' => $rev->language()->getId(),
'created' => date('Y-m-d H:i:s', $rev->getCreatedTime()),
'revision_id' => $rev->getRevisionId(),
];
}
return new JsonResponse($data);
}
}
🔗 Example API Call
GET /custom-revisions?type=article&uid=1&langcode=uk&created=2025-01-01&limit=5&page=1
Returns a JSON array of node revisions matching the filters.
Comments