Skip to main content
Drupal is a registered trademark of Dries Buytaert
Release: Cms 2.2.0 — Update released for Drupal core (2.2.0)! Release: CKEditor Anchor Link 3.0.6 — Minor update available for module anchor_link (3.0.6). Release: Drupal CMS Helper 2.2.0 — Minor update available for module drupal_cms_helper (2.2.0). Release: REST menu items 3.1.0 — Minor update available for module rest_menu_items (3.1.0). Release: Drupal Canvas 1.12.0 — Minor update available for module canvas (1.12.0). Release: Search API Pantheon 8.5.1 — Minor update available for module search_api_pantheon (8.5.1). Release: Varbase Editor 9.2.18 — Minor update available for module varbase_editor (9.2.18). Release: Varbase Editor 10.1.9 — Minor update available for module varbase_editor (10.1.9). Module Revived: Protect Views Flood Control 1.0.3 — Module protect_views_flood_control updated after 7 months of inactivity (1.0.3). Security Coverage: CKEditor5 Pullquote — Module ckeditor5_pullquote now has official Drupal security advisory coverage.

Entity JSON Store

No security coverage

Part of the Entity ecosystem · 232 projects

View on drupal.org

Entity JSON Store keeps a ready-made JSON copy of each entity in the database and serves it directly, so decoupled front ends get their data in milliseconds even when Drupal's caches are empty: after a deploy, a cache clear or an eviction.

It works with the JSON your front end already uses, whether that is JSON:API or Entity Display JSON. Stored copies stay correct as content changes, they survive cache clears and deploys, and whenever the store cannot answer, the request is built live as usual. The site gets faster, never wrong.

Performance at a glance

Measured on a local development copy of a real multilingual site: Drupal 11.4, PHP 8.3, MySQL 8.0, Redis (1 GB) as Drupal's cache backend, Apple M1 Pro. Ten real content pages with rich, paragraph-heavy layouts (325 KB of JSON each on average) were requested through Entity Display JSON with an OAuth token, the way a Next.js front end requests them. Times are medians of the full HTTP response. Your numbers will differ with hardware and content; the proportions are what matter.

Response time by scenario

Scenario Without Entity JSON Store With Entity JSON Store Effect First request after a deploy or drush cr (page stored earlier) 658 ms
full build: Drupal's caches are empty 45 ms
HIT: the stored copy survives cache clears About 15× faster Drupal's caches evicted under memory pressure Same as a full build Same as a stored copy About 15× faster (same mechanism as above; not measured separately) Page requested for the first time (empty store, cold caches) 658 ms 639 ms
MISS: built as usual, then stored No measurable extra cost Repeat requests while Drupal's dynamic page cache is warm 37 ms 38 ms
HIT Same speed: Drupal's own cache is already fast here First request after the page, or something it shows, is edited 293 ms
Drupal rebuilds using its warm render cache 692 ms
REBUILT Slower in this test: stored copies are served without rendering, so after a cache clear Drupal's render cache stays cold, and the first rebuild after an edit costs about as much as a full build. Later requests are stored copies again. Requests the store does not answer (another display of a stored page, unselected types, logged-in JSON:API requests) 28 ms 32 ms
LIVE About 4 ms of overhead Request report switched on (optional) n/a 36 ms, against 37 ms with it off No measurable difference: it is written after the response is sent Store switched off, or the settings.php kill switch n/a Built live as usual Same as without the module Anonymous JSON:API reads (/jsonapi/{type}/{bundle}/{uuid}) Same mechanism as above, but not benchmarked on this site, whose JSON:API is closed to anonymous users.

Costs

What Cost Notes Saving an entity About 0.3 ms (at most 11 ms measured) One indexed lookup marks dependent copies stale. Nothing is rebuilt during the save. Storage About 325 KB per stored page in this test (largest 848 KB), plus a few hundred small cache-tag rows One copy per entity and language, and only pages that are requested (or queued) are stored. 10,000 stored pages of this size take about 3.2 GB. Documents over the configured maximum size are never stored. Database load on a stored copy Two indexed queries No routing to the controller, entity loads, access checks on referenced entities or serialization. Background work Queue items on cron JSON:API documents are rebuilt in the queue. Stored Entity Display JSON responses are rebuilt by their next request instead.

When it helps most, and when it does not

  • Helps most: sites that deploy or clear caches often, sites whose caches are evicted under memory pressure, large sites with a long tail of pages that are rarely in cache, front-end builds and crawlers that request many pages at once, and heavy pages that take hundreds of milliseconds or more to build.
  • Little or no gain: pages that are requested often enough to stay in Drupal's dynamic page cache, and pages that already build quickly.
  • Can be slower: the first request after an edit to frequently changed content, as shown above. For content edited many times a day, compare with and without the store before enabling it for that type.

The problem it solves

In a decoupled site (Next.js, Gatsby, a mobile app), every page view needs a JSON document from Drupal. Building one is expensive: Drupal routes the request, loads the entity and everything it references (paragraphs, media, terms, authors), checks access on each of them, and serializes the result. Rich pages easily take one to three seconds to build.

Drupal's caches help, but they are caches: a deploy, drush cr, a bulk translation import or memory pressure empties them, and the next visitors pay the full build cost again. Caches in front of Drupal (page cache, CDN, the front end's own cache) do not help authenticated API requests, and they all fall back to Drupal on a miss.

Entity JSON Store adds a durable layer underneath: a table holding the finished JSON of each entity and language. It is rebuilt only when that content actually changes, not when caches are cleared.

Benefits

  • Fast even when caches are cold. A stored document is returned with two indexed database queries. Routing to the controller, entity loading, access checks on referenced entities and serialization are all skipped. After a cache clear, stored pages were about 15 times faster than a full build in the measurements above.
  • Steady performance after deploys and cache clears. The store is a table, not a cache, so drush cr, deploys and cache evictions do not empty it. No cold-cache slowdown, no warming scripts needed.
  • No change to your front end. The module can answer the JSON:API and Entity Display JSON endpoints your front end already calls, and return the same data they would. Switch it on or off with a checkbox.
  • Always correct. Stored copies are invalidated through Drupal's own cache tags, so editing an entity, a referenced term or a media item marks every dependent copy stale. Unpublishing takes effect immediately.
  • Safe by default. Only content that is published, and visible to anonymous users where applicable, is stored. Anything the store cannot answer safely is passed to Drupal as normal.
  • Less load on the database and PHP. Traffic spikes, crawlers and front-end rebuilds hit indexed lookups instead of full entity builds.
  • Visible. Every response says whether it came from the store, and an optional report lists the requests of the last 7 days with the reason for each one that was not served from the store.

How it works

  1. Build. The first time an entity is requested (or when the queue processes it), the module builds its JSON exactly as the normal endpoint would and stores it, together with the cache tags it depends on.
  2. Serve. Later requests for the same entity and language are answered from the table before Drupal runs the normal controller.
  3. Invalidate. When Drupal invalidates a cache tag (an entity saved, a term renamed, a field changed), every stored copy depending on it is marked stale.
  4. Rebuild. Stale copies are rebuilt by a queue on cron, or on their next request. Until then they are never served (unless you opt in to serving stale copies).
  5. Fall back. A missing or stale copy, a database error, an unknown request, or the store switched off: the request is simply built live by Drupal, and the result is stored for next time.

The store keeps one copy per entity and language, so its size stays predictable: the number of entities times the number of languages.

Features

Serving

  • JSON:API override: answers anonymous GET /jsonapi/{type}/{bundle}/{uuid} requests from the store when the query string matches a configured variant. Collections, filters, authenticated requests and anything not stored still go to JSON:API.
  • Entity Display JSON override: answers /ejson/entity?path=… from the store, including OAuth-authenticated front-end requests, with one shared copy per entity and language. The first response is captured exactly as Entity Display JSON returned it.
  • Its own endpoints: /api/json-store/{entity_type}/{uuid}, /api/json-store/by-path?path=/about-us, and /api/json-store/search/{index} for listings, where Search API finds and sorts the results and the store supplies each item.
  • Response header X-Entity-Json-Store: HIT, STALE, MISS, REBUILT, LIVE or DISABLED, so you can see at a glance how each response was produced.
  • Responses carry the stored cache tags, so Drupal's page cache and tag-based purging work with them.

Freshness and correctness

  • Dependency tracking through Drupal cache tags, including referenced entities and configuration.
  • Unpublishing and deleting take effect immediately, during the save, not after the queue runs.
  • Only the default (published) revision is stored; drafts and forward revisions are never exposed.
  • Configuration changes that affect output (fields, image styles, anonymous permissions) and core updates outdate stored copies automatically.
  • A reconcile job finds entities changed without hooks (migrations, direct SQL) and rebuilds them.
  • Optional maximum age for time-based output.
  • Race protection: a slow build never overwrites a newer change.
  • Unchanged documents are not rewritten, which keeps bulk imports and translation jobs cheap.

Security

  • JSON:API documents are built as the anonymous user, so node access and field access apply as for any visitor.
  • Only published content is stored, and logged-in JSON:API requests are always built live.
  • A configurable list of blocked attributes (by default mail, init, pass) prevents personal data from ever being stored.
  • Only content entity types that JSON:API exposes and editors publish are offered for storage; users, files, OAuth tokens, form submissions and sub-entities such as paragraphs are left out.
  • An emergency kill switch in settings.php stops serving from the store without a deploy.

Environments and storage

  • URLs are stored without the site host and restored when served, so a production database copied to staging does not serve production URLs.
  • Pluggable storage backends: the database table (default), Redis (through the Redis module) or files on any stream wrapper, including Amazon S3 through S3 File System. Metadata always stays in the database.
  • Pluggable builders: JSON:API (default), core's serializer, or your own plugin, for example a custom field mapping.
  • The store tables can live on a separate database connection.

Administration and monitoring

  • A settings page with collapsible sections and an entity type and bundle picker: store all bundles of a type, including ones added later, or only selected bundles.
  • A status page: stored and stale rows, queue size, oldest stale row, and requests per day with the hit rate.
  • An optional requests report (off by default): every request of the last 7 days, whether it was served from the store, and why not when it was not. URLs are logged without tokens or keys.
  • A Drupal status report entry and a Warmer plugin for rebuilding after deploys.

Requirements

  • Drupal 11.1 or later.
  • Core modules: JSON:API, Path alias and Serialization.

Optional integrations

Installation

composer require drupal/entity_json_store
drush en entity_json_store

Then, in Configuration » Web services » Entity JSON Store:

  1. Under Content to store, tick the entity types to store and choose all bundles or only some.
  2. Under Status, tick Override JSON:API read operations to answer your existing JSON:API and Entity Display JSON requests from the store.
  3. Grant Access the entity JSON store API if you use the module's own /api/json-store endpoints.
  4. Optionally build everything up front instead of on first request:
    drush entity-json-store:enqueue
    drush queue:run entity_json_store_rebuild

Drush commands

  • drush ejs-enqueue [entity_type] [--bundle=]: queue entities for a rebuild.
  • drush ejs-rebuild node 12 [--langcode=]: rebuild one entity now.
  • drush ejs-status: stored and stale rows, queue size and hit rate.
  • drush ejs-reconcile: find entities changed without hooks.
  • drush ejs-queue-outdated: queue rows outdated by configuration changes or expiry.
  • drush ejs-invalidate: outdate every row, for example after rebuilding node access grants.
  • drush ejs-purge: empty the store.

Kill switch

To stop serving from the store immediately, add this to settings.php:

$settings['entity_json_store.enabled'] = FALSE;

Every request is then built live. Stored copies keep being invalidated, so removing the line is safe.

What is never stored

  • Unpublished content, drafts and forward revisions.
  • Logged-in JSON:API requests (they are always built live for the user).
  • Responses marked uncacheable, larger than the configured maximum size, or containing blocked attributes.
  • Not-found and failing requests.
  • Entity types and bundles you have not selected.

Similar projects

  • JSON:API Boost warms JSON:API responses into Drupal's cache after entities are saved. Entity JSON Store stores documents in a table instead, so they survive cache clears, deploys and evictions, can be looked up directly, and can serve Entity Display JSON as well as JSON:API.
  • Warmer refills caches after they are cleared. Entity JSON Store integrates with it, but does not depend on caches staying full.
  • Drupal core's JSON:API normalization cache speeds up building each document. Entity JSON Store skips building altogether when a stored copy exists.

Support and contributing

Please report bugs and ideas in the issue queue. Patches and merge requests are welcome, especially new builder and storage backend plugins.

Depends on

Dependencies of the latest stable release

No dependencies recorded for this project.

Required by

Tracked projects that depend on this one

No tracked projects depend on this one yet.

Activity

Tracked releases
2
Tracked since
Sep 2026
Latest release
5 hours ago
Releases (12 mo)
2 ▲ from 0
Maintenance
Active

Releases

Version Type Core Release date
1.0.0 Stable Sep 26, 2026
1.0.x-dev Dev Sep 26, 2026