Skip to main content

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

  1. Go to Configuration → Web Services → REST.
    • For this, we need to install Rest UI
  2. Find Dashboard API Resource.
  3. 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

Allahnoor

Sr. Drupal FE/BE Developer

Add new comment