Creating Custom REST Endpoints
A custom endpoint is an external contract. Its status codes, authentication, validation, cache headers, and error format deserve the same design attention as its successful payload.
Start with the contract
Document method, route, authentication, request schema, response schema, errors, and versioning. Prefer core entity APIs when they already expose the required contract.
Enforce access at multiple layers
Route permissions establish the first gate; entity access and field access still apply. Validate content types, sizes, and allowed values before invoking domain behavior. Never trust a client-supplied entity identifier by itself.
Return deliberate responses
Use JSON responses with appropriate status codes and cacheability. Avoid leaking exception details. For writes, consider idempotency and concurrency behavior.
Working example
public function status(int $job): CacheableJsonResponse {
$result = $this->jobs->status($job);
$response = new CacheableJsonResponse(['data' => $result]);
$response->addCacheableDependency($result);
return $response;
}Use a custom endpoint only when the contract is custom
Before creating another controller, check whether Drupal's existing entity and API capabilities already represent the required operation. A custom endpoint is justified when the consumer needs an application-specific workflow, aggregate response, command, or security contract rather than ordinary entity access.
Keeping the custom API surface small reduces the amount of authentication, documentation, testing, and backwards compatibility the application must own.
Validate before mutating state
Authentication answers who the client is. Authorization answers what that client may do. Input validation answers whether the request is meaningful. These are separate checks, and all three should happen before the endpoint performs irreversible work.
When an endpoint operates on Drupal entities, entity access and field-level rules still matter even if the route itself has a permission requirement.
Design failure responses as carefully as success
Clients need stable status codes and response shapes for expected failures. Validation errors, missing resources, conflicts, and authorization failures should not all collapse into an uncaught exception or an HTML error page.
For cacheable reads, return cacheability that reflects the underlying data. For writes, document retry and concurrency expectations so clients know whether repeating a request is safe.
Key Takeaways
- Design the HTTP contract before the controller.
- Check route, entity, and field access.
- Return intentional errors and cache policy.