Custom REST API in Drupal 10 & 11 with REST Resource Plugin – 2025
What Is an API? And What Does REST Mean?
An API (Application Programming Interface) allows two software systems to communicate with one another. In web applications, APIs are commonly used to allow external clients—like mobile apps, JavaScript frontends (e.g., React), or other systems—to read or write data from your Drupal backend in a controlled and structured way.
A REST API is a type of API that follows web standards. It uses:
- HTTP methods like GET, POST, PUT, and DELETE
- URLs to identify the data being requested (e.g., /api/articles)
- JSON to send and receive data in a machine-readable format
For example:
- A mobile app might call /api/user/5 to get a user profile
- A frontend React app might send a POST /api/comment to create a new comment
REST APIs in Drupal
Drupal provides two major API systems:
- Core REST module – for basic read/write operations on content entities
- JSON: API module – a powerful out-of-the-box REST API following JSON: API specifications
While these tools are great for standard use, they have limitations for some use cases:
- Complex for non-entity endpoints and processing logic
- Inefficient for combining multiple data types (e.g., articles + events + user info)
This is where custom REST APIs shine — allowing you to deliver exactly what your frontend needs, efficiently and securely.
Use Case: Mobile Dashboard Endpoint
Let’s say we’re building a mobile app for a membership-based club. When a user logs in, the app needs to load a dashboard with:
- Their name and profile photo
- Latest 3 news articles
- Upcoming events
- Total unread notifications
- Membership status and expiry
Final Goal
GET /api/dashboard
Expected JSON response:
{
"user": {
"name": "John Doe",
"photo": "https://your-site.com/user-picture.jpg"
},
"news": [
{
"title": "Article one",
"date": "2025-08-01"
},
{
"title": "Article two",
"date": "2025-08-01"
}
],
"events": [
{
"title": "Event two",
"date": "2025-08-05T05:50:21"
},
{
"title": "Event one",
"date": "2025-08-20T05:50:31"
}
],
"notifications": 2,
"membership": {
"status": "Active",
"expires": "2025-12-31"
}
}
Let’s build it using a REST Resource plugin.
Step 1: Create a Custom Module
mkdir -p web/modules/custom/custom_api_rest
cd web/modules/custom/custom_api_rest
touch custom_api_rest.info.yml
custom_api_rest.info.yml:
name: 'Custom API REST Resource'
type: module
description: 'Provides a custom REST API endpoint via plugin.'
core_version_requirement: ^10 || ^11
package: Custom
Enable the module:
drush en custom_api_rest
Step 2: Create the REST Resource Plugin
mkdir -p src/Plugin/rest/resource
touch src/Plugin/rest/resource/DashboardResource.php
DashboardResource.php:
<?php
namespace Drupal\custom_api_rest\Plugin\rest\resource;
use Drupal\rest\ResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\user\Entity\User;
use Drupal\node\Entity\Node;
use Psr\Log\LoggerInterface;
/**
* Provides a Dashboard REST Resource
*
* @RestResource(
* id = "dashboard_rest_resource",
* label = @Translation("Dashboard API Resource"),
* uri_paths = {
* "canonical" = "/api/dashboard"
* }
* )
*/
class DashboardResource extends ResourceBase {
protected $currentUser;
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
array $serializer_formats,
LoggerInterface $logger,
AccountProxyInterface $current_user
) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
$this->currentUser = $current_user;
}
public static function create(ContainerInterface $container,
array $configuration, $plugin_id, $plugin_definition): self{
return new self(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('rest'),
$container->get('current_user')
);
}
public function get() {
$uid = $this->currentUser->id();
$user = User::load($uid);
$response = [
'user' => [
'name' => $user->getDisplayName(),
'photo' => $user->user_picture->entity?->createFileUrl() ?? null,
],
'news' => $this->getArticles(3),
'events' => $this->getEvents(2),
'notifications' => 2, // Replace with real logic
'membership' => [
'status' => 'Active',
'expires' => '2025-12-31',
],
];
return new ResourceResponse($response);
}
private function getArticles($limit = 3): array {
$nids = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->sort('created', 'DESC')
->range(0, $limit)
->accessCheck()
->execute();
$nodes = Node::loadMultiple($nids);
$data = [];
foreach ($nodes as $node) {
$data[] = [
'title' => $node->getTitle(),
'date' => date('Y-m-d', $node->getCreatedTime()),
];
}
return $data;
}
private function getEvents($limit = 2): array {
$nids = \Drupal::entityQuery('node')
->condition('type', 'event')
->condition('status', 1)
->sort('field_event_date', 'ASC')
->range(0, $limit)
->accessCheck()
->execute();
$nodes = Node::loadMultiple($nids);
$data = [];
foreach ($nodes as $node) {
$data[] = [
'title' => $node->getTitle(),
'date' => $node->get('field_event_date')->value ?? null,
];
}
return $data;
}
}
Step 3: Enable and Configure the REST Endpoint
- Go to Configuration → Web Services → REST.
- For this, we need to install Rest UI
- Find Dashboard API Resource.
- Click Edit
- Enable the GET method
- Select json format
- Choose auth (cookie, basic_auth, etc.)
Assign permission to the relevant role under People → Roles → Permissions → Access GET on dashboard_rest_resource resource.
If needed, clear caches:
drush cr
Conclusion
Using a REST Resource plugin in Drupal gives you:
- Strong integration with the core REST system
- Secure access control
- Reusable, structured API endpoint logic
Perfect for advanced use cases that JSON: API or Core REST can't fully handle.
Source code can be found at GitHub
Allahnoor
Sr. Drupal FE/BE Developer