Expose Drupal Content to JavaScript Apps with Core JSON:API
Use Drupal core's JSON:API module to serve entities to React or mobile frontends without writing custom REST controllers.
06 Jul 2026, 21:25 UTC

The problem: theming is the wrong tool for data delivery
When a product team asks for article listings in a React application or a mobile app, the reflexive Drupal answer is often to build a custom theme or a set of controllers. That works, but it means maintaining serialization logic, access checks, and cache invalidation by hand. For pure data delivery, Drupal's traditional rendering layer is over-engineered.
The useful takeaway: Drupal core includes JSON:API, a supported module that exposes entities as REST-like resources following the JSON:API specification. You can enable it, assign permissions, and start consuming content from a frontend without writing custom endpoints. This assumes Drupal 9.2 or later; JSON:API has been part of core since 8.8.x.
What core JSON:API exposes
JSON:API maps Drupal entity bundles to resource types. A content type named Article becomes available at paths such as /jsonapi/node/article. The module handles:
- Serialization of fields, relationships, and meta data into JSON:API documents
- Drupal's existing access control, so unauthenticated requests respect view permissions
- Cache tags and contexts tied to the underlying entities
- Filtering, sorting, and pagination through query parameters
JSON:API is a specification for building APIs in JSON. A "resource" is the API representation of an entity, such as a node or a taxonomy term.
Worked example: fetch articles and render them in React
Run this from a terminal with network access to your Drupal site. Replace https://example.com with your site's domain.
curl -i "https://example.com/jsonapi/node/article?filter[title][value]=Drupal&page[limit]=5"
A successful response returns HTTP 200 and a JSON:API document. The top-level object includes jsonapi, links, and data. Each item in data has a type such as node--article, an id, and an attributes object holding fields like title and body.
In a React component, you can fetch the same URL and map over response.data:
import { useEffect, useState } from 'react';
export default function ArticleList() {
const [articles, setArticles] = useState([]);
useEffect(() => {
fetch('https://example.com/jsonapi/node/article?page[limit]=5')
.then((res) => res.json())
.then((doc) => setArticles(doc.data || []))
.catch((err) => console.error(err));
}, []);
return (
{articles.map((article) => (
-
{article.attributes?.title}
))}
);
}
This example assumes the Article content type is published and viewable by the role making the request. If the request is anonymous, the anonymous role needs the access content permission, or JSON:API will return 403.
Trade-offs and how to check them
JSON:API documents include links, meta, and relationship objects that custom endpoints can omit. That makes responses larger than a hand-rolled endpoint returning only titles. You can reduce payload size with sparse fieldsets, for example fields[node--article]=title,created, or with the contributed JSON:API Extras module.
Complex relational queries are another limit. Filtering articles by title works well, but fetching paragraphs or custom entities with nested conditions may require contributed modules or custom normalizers.
To verify behavior after enabling the module:
- Request
/jsonapi/node/articleand confirm the response containsdataas an array. - Log out or switch to a role without
access contentand confirm the same request returns 403. - Check response headers for cache tags such as
X-Drupal-Cache-Tagsto confirm entity cache invalidation is active.
Enable it and verify access
Run this in your Drupal project root:
drush en jsonapi -y
drush cr
Where to run: the directory containing drush.yml or your Drupal root. Required permissions: file system access to enable modules and write to the cache directory. If you prefer the UI, go to Extend, search for "JSON:API", and install it.
Risk: enabling the module exposes entity data over HTTP. Review role permissions under People > Permissions before exposing the endpoint publicly.
Test with curl:
curl -i "https://example.com/jsonapi/node/article?_format=json"
Expected checks: HTTP 200, Content-Type includes application/vnd.api+json, and the body includes jsonapi and data keys. If you see 403, check the requesting role's content access permissions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.