Skip to main content

JSON:API Extras AI Integration Guide

Drupal JSON:API Extras AI Integration Guide

Mission: Document JSON:API enhancements for AI agent REST APIs with resource customization, field aliasing, and response transformation for seamless agent integration.

Overview

JSON:API Extras is a Drupal contributed module that extends the core JSON:API module with powerful customization capabilities. It enables AI agents to work with Drupal APIs by:

  • Customizing JSON:API resource names and endpoints
  • Creating field aliases for agent-friendly naming
  • Applying field transformations and formatters
  • Disabling unnecessary fields from API responses
  • Setting default parameters for resource collections
  • Supporting AI-agent-specific response formats

This module is essential for exposing Drupal entities as clean, agent-friendly REST APIs without requiring agents to understand Drupal's internal structure.

Module Information

Why JSON:API Extras for AI Agents

Problem: Drupal's Default JSON:API

Drupal's core JSON:API module provides zero-configuration REST access to entities but exposes internal implementation details:

GET /jsonapi/node/article/123

{
  "data": {
    "type": "node--article",
    "id": "123",
    "attributes": {
      "drupal_internal__nid": 123,
      "drupal_internal__vid": 456,
      "title": "AI Agent Integration",
      "field_tags": ["JSON:API", "Drupal"],
      "field_internal_metadata": {...}  // Unwanted noise
    }
  }
}

Challenges for AI Agents:

  • Resource types use Drupal conventions (node--article)
  • Field names use Drupal internal prefixes (field_*, drupal_internal__*)
  • Multiple fields exposed that agents don't need
  • Inconsistent field naming across different content types
  • No support for custom response structures

Solution: JSON:API Extras

JSON:API Extras transforms the API into agent-friendly format:

GET /api/articles/123

{
  "data": {
    "type": "article",
    "id": "123",
    "attributes": {
      "title": "AI Agent Integration",
      "tags": ["JSON:API", "Drupal"],
      "publishedAt": "2026-01-08T00:00:00Z"
    }
  }
}

Benefits for AI Agents:

  • Clean, semantic resource names
  • Agent-friendly field aliases
  • Reduced response payload
  • Consistent naming conventions
  • Custom response formatting

Core Features

1. Resource Control

Enable or disable specific API resources to control what entities agents can access.

Configuration UI: /admin/config/services/jsonapi

# Disable article API (prevent agent access) node--article: enabled: false # Enable with custom path node--document: enabled: true path: '/api/documents'

Use Cases:

  • Hide internal content types from agents
  • Expose only production-ready resources
  • Gradual rollout of new entity types

2. Resource Aliasing

Rename resources to use semantic names instead of Drupal's entity--bundle convention.

Before:

GET /jsonapi/node/article
GET /jsonapi/node/page
GET /jsonapi/user/user

After:

GET /api/articles
GET /api/pages
GET /api/users

Configuration:

node--article: path: '/api/articles' label: 'Article' node--page: path: '/api/pages' label: 'Page' user--user: path: '/api/users' label: 'User'

3. Custom Endpoints

Define custom paths for resources to match agent API conventions.

# Standard REST endpoints node--product: path: '/api/v1/products' # Nested resource endpoints node--comment: path: '/api/v1/articles/{article_id}/comments' # Collection with filters node--event: path: '/api/events' defaults: filter[status]: 'published' page[limit]: 20

4. Field Aliasing

Create alternative field names for better semantics in agent requests.

Before:

{ "title": "Article Title", "field_tags": ["AI", "Drupal"], "field_publish_date": "2026-01-08", "field_content_summary": "Summary text" }

After (with aliases):

{ "title": "Article Title", "tags": ["AI", "Drupal"], "publishedAt": "2026-01-08", "summary": "Summary text" }

Configuration:

node--article: fields: field_tags: alias: 'tags' field_publish_date: alias: 'publishedAt' field_content_summary: alias: 'summary'

5. Field Management

Disable unwanted fields from API responses to reduce payload size and prevent exposure of internal data.

node--article: fields: drupal_internal__nid: enabled: false drupal_internal__vid: enabled: false field_internal_notes: enabled: false field_temporary_cache: enabled: false

Benefits:

  • Smaller JSON responses
  • Reduced bandwidth for agents
  • Security by omission
  • Faster parsing

6. Field Enhancement with Formatters

Apply Drupal field formatters to transform field output for agents.

node--article: fields: field_publish_date: formatter: 'datetime_custom' settings: format: 'Y-m-d\TH:i:s\Z' # ISO 8601 field_author: formatter: 'entity_reference_label' settings: link: false body: formatter: 'text_trimmed' settings: trim_length: 500

7. JSON:API Defaults Sub-Module

Specify default parameters for resource collections (include, filters, pagination).

node--article: defaults: include: 'author,tags' page[limit]: 25 page[offset]: 0 sort: '-created' filter[status]: 'published'

Installation & Setup

Step 1: Install the Module

# Via Composer composer require drupal/jsonapi_extras # Via Drush drush en jsonapi_extras -y # Optional: Enable JSON:API Defaults drush en jsonapi_extras:jsonapi_defaults -y

Step 2: Configuration

Navigate to: /admin/config/services/jsonapi

Or use Drush:

drush config:get jsonapi_extras.settings

Step 3: Verify Installation

# Check status drush status # Test basic endpoint curl https://your-site.com/api/articles | jq .

Integration with api_normalization

JSON:API Extras complements the api_normalization module by:

Separation of Duties

  • api_normalization: Handles cross-platform API normalization
  • jsonapi_extras: Handles Drupal JSON:API customization

Workflow

External API Request
      
api_normalization (normalize to internal schema)
      
Drupal Entity Processing
      
jsonapi_extras (customize JSON:API response)
      
Agent-Friendly REST API Response

Configuration Example

In api_normalization:

// Normalize third-party API responses $normalizer = \Drupal::service('api_normalization.response'); $normalized = $normalizer->transform($external_data, [ 'target_format' => 'json:api', 'include_meta' => true, ]);

In jsonapi_extras:

# Apply field aliases for agent consumption node--article: fields: title: alias: 'headline' field_abstract: alias: 'summary'

Configuration Examples

Example 1: E-Commerce API

Configure Drupal to expose products as a clean REST API for shopping agents.

File: config/jsonapi_extras.resource_overrides.yml

node--product: disabled: false path: '/api/v1/products' fields: title: disabled: false body: disabled: true # Hide full description field_price: alias: 'price' disabled: false field_inventory: alias: 'stock' disabled: false field_sku: alias: 'sku' disabled: false field_category: alias: 'category' disabled: false field_product_image: alias: 'image' disabled: false formatter: 'image_url' node--category: disabled: false path: '/api/v1/categories' fields: title: disabled: false description: disabled: true

Agent Usage:

# List all products GET /api/v1/products?page[limit]=50 # Get specific product GET /api/v1/products/456 # Filter by category GET /api/v1/products?filter[category]=electronics

Example 2: Content Management for Publishing Agents

Configure Drupal to expose content for AI publishing workflows.

node--article: disabled: false path: '/api/v1/articles' fields: title: alias: 'headline' disabled: false body: alias: 'content' disabled: false field_excerpt: alias: 'summary' disabled: false field_published_date: alias: 'publishedAt' disabled: false formatter: 'datetime_iso' field_author: alias: 'author' disabled: false field_tags: alias: 'tags' disabled: false field_featured_image: alias: 'featuredImage' disabled: false field_seo_title: disabled: false # Keep internal drupal_internal__nid: disabled: true # Hide internal defaults: include: 'author,tags' page[limit]: 20 sort: '-created' filter[status]: 'published'

Example 3: API with Conditional Fields

Expose different fields based on request context.

node--document: disabled: false path: '/api/v1/documents' fields: title: disabled: false body: disabled: false field_classification: disabled: false # Only include for authenticated requests field_internal_reference: disabled: true # Always hide from API field_download_url: disabled: false formatter: 'file_url_absolute' user--user: disabled: false path: '/api/v1/users' fields: name: disabled: false mail: disabled: false field_role: alias: 'role' disabled: false field_phone: disabled: false user_picture: disabled: false formatter: 'image_url' # Hide sensitive fields pass: disabled: true

Code Examples

Using JSON:API Extras with Agents

Example 1: PHP Agent Integration

<?php // In Drupal, use JSON:API Extras to expose clean API // Then agents consume via REST // ai_agents module example function my_agent_fetch_articles() { // This endpoint is customized by jsonapi_extras $client = \Drupal::httpClient(); $response = $client->request('GET', '/api/v1/articles', [ 'headers' => [ 'Authorization' => 'Bearer ' . $this->getToken(), ], 'query' => [ 'page[limit]' => 50, 'filter[status]' => 'published', 'sort' => '-publishedAt', ], ]); $articles = json_decode($response->getBody(), true); // Clean, aliased field names from jsonapi_extras foreach ($articles['data'] as $article) { $this->processArticle( $article['attributes']['headline'], $article['attributes']['summary'], $article['attributes']['publishedAt'] ); } }

Example 2: GraphQL Query (with openapi_jsonapi)

query { articles(first: 10, filter: {status: "published"}) { edges { node { id headline # Field alias from jsonapi_extras summary # Field alias from jsonapi_extras publishedAt # Field alias from jsonapi_extras tags { id name } } } } }

Example 3: Direct REST API Client (Curl)

#!/bin/bash # Fetch articles using jsonapi_extras custom path curl -X GET \ 'https://example.com/api/v1/articles?filter[status]=published&page[limit]=20' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Accept: application/vnd.api+json'

Example 4: Agent SDK Integration

from drupal_agent_sdk import DrupalAPI # Initialize with jsonapi_extras custom endpoint api = DrupalAPI( site_url='https://example.com', endpoint='/api/v1', token='YOUR_TOKEN' ) # Use clean, aliased field names articles = api.articles.list( filter={'status': 'published'}, limit=50, sort='-publishedAt' ) for article in articles: print(f"Title: {article['headline']}") print(f"Summary: {article['summary']}") print(f"Published: {article['publishedAt']}")

Programmatic Configuration

Configure JSON:API Extras via code:

<?php // In module install hook or via configuration management // Get the configuration $config = \Drupal::configFactory() ->getEditable('jsonapi_extras.settings'); // Add resource override $overrides = $config->get('resource_overrides') ?? []; $overrides['node--article'] = [ 'disabled' => false, 'path' => '/api/v1/articles', 'fields' => [ 'title' => [ 'disabled' => false, ], 'body' => [ 'disabled' => false, 'alias' => 'content', ], 'field_tags' => [ 'disabled' => false, 'alias' => 'tags', ], 'drupal_internal__nid' => [ 'disabled' => true, ], ], ]; $config->set('resource_overrides', $overrides)->save();

Event Hooks for Custom Processing

<?php /** * Implements hook_jsonapi_resource_config_presave(). * * Customize JSON:API Extras configuration before saving. */ function my_module_jsonapi_resource_config_presave(&$config) { // Apply custom defaults to all resources if (!isset($config['defaults'])) { $config['defaults'] = [ 'page[limit]' => 25, 'page[offset]' => 0, ]; } }

AI Agent Use Cases

Use Case 1: Content Publishing Agent

Agent that publishes articles to Drupal via clean JSON:API.

API Configuration:

node--article: path: '/api/v1/articles' fields: title: null # All default body: null field_category: alias: 'category' field_tags: alias: 'tags' field_featured_image: alias: 'featuredImage'

Agent Workflow:

1. Agent generates article content
2. POST /api/v1/articles with clean field names
3. Drupal creates node with jsonapi_extras aliases
4. Field mapping automatic
5. Content published

Use Case 2: E-Commerce Search Agent

Agent that searches products for customers.

node--product: path: '/api/v1/products' fields: title: alias: 'name' body: alias: 'description' field_price: alias: 'price' formatter: 'price_default' field_inventory: alias: 'inStock' field_category: alias: 'category' defaults: include: 'category' page[limit]: 50

Agent Queries:

GET /api/v1/products?filter[inStock]=true&sort=price
GET /api/v1/products?filter[category]=electronics&page[limit]=20

Use Case 3: Data Synchronization Agent

Agent that syncs Drupal data to external systems.

node--sync_source: path: '/api/v1/sync-sources' fields: title: null field_external_id: alias: 'externalId' field_sync_status: alias: 'status' field_last_sync: alias: 'lastSync' formatter: 'datetime_iso' field_sync_error: alias: 'error' disabled: true # Only sync successful records

Response Format Examples

Article Response

With jsonapi_extras configuration:

{ "data": { "type": "article", "id": "456", "attributes": { "headline": "Building AI-Driven Applications", "summary": "Learn how to integrate AI agents with Drupal.", "publishedAt": "2026-01-08T15:30:00Z", "tags": ["AI", "Drupal", "Agents"], "author": { "data": { "type": "user", "id": "123" } } }, "relationships": { "author": { "data": { "type": "user", "id": "123" } } } }, "included": [ { "type": "user", "id": "123", "attributes": { "name": "John Doe" } } ] }

Product Response

{ "data": [ { "type": "product", "id": "789", "attributes": { "name": "Smart Device", "description": "Revolutionary smart home device", "price": 199.99, "inStock": true, "sku": "SD-001", "category": "electronics" } } ], "meta": { "count": 1, "total_count": 234 } }

Performance Optimization

Caching Strategies

# Enable caching for API responses cache: enabled: true ttl: 3600 # 1 hour # Cache warming for popular articles cache_warming: resources: - 'node--article' frequency: 'daily' filters: featured: true

Pagination for Agents

# Set sensible defaults for agent pagination defaults: page[limit]: 50 page[offset]: 0 # Maximum limit limits: page[limit].max: 1000

Security Considerations

Access Control

# Only expose published content node--article: defaults: filter[status]: 'published' # Hide sensitive fields user--user: fields: pass: disabled: true mail: disabled: true # Expose only for authenticated users

Rate Limiting

Combine with Rate Limiting modules:

# Limit API calls per agent drush pm:install rate_limit

Authentication

Require tokens for API access:

// In resource configuration headers: required: - 'Authorization: Bearer token'

Troubleshooting

Fields Not Aliasing

Problem: Field aliases not appearing in response

Solution:

  1. Clear Drupal cache: drush cache:rebuild
  2. Verify configuration in /admin/config/services/jsonapi
  3. Check field exists in content type
  4. Verify correct syntax in configuration

API Returning Wrong Content

Problem: Getting old field names or full responses

Solution:

# Verify configuration is saved drush config:list jsonapi_extras # Export current config drush config:get jsonapi_extras.settings # Clear caches drush cache:rebuild drush cache:clear

Agents Can't Access Resources

Problem: 403 Forbidden or resource not found

Solution:

  1. Check resource is not disabled in jsonapi_extras
  2. Verify authentication token
  3. Check user permissions
  4. Verify endpoint path matches configuration

Testing

Unit Tests

<?php namespace Drupal\Tests\my_module\Unit; use Drupal\Tests\UnitTestCase; class JsonApiExtrasConfigTest extends UnitTestCase { public function testArticleResourceAlias() { $config = [ 'node--article' => [ 'path' => '/api/v1/articles', 'fields' => [ 'field_tags' => ['alias' => 'tags'], ], ], ]; $this->assertEquals('/api/v1/articles', $config['node--article']['path']); $this->assertEquals('tags', $config['node--article']['fields']['field_tags']['alias']); } }

Functional Tests

<?php namespace Drupal\Tests\my_module\Functional; use Drupal\Tests\BrowserTestBase; class JsonApiAgentTest extends BrowserTestBase { public function testAgentCanFetchArticles() { // Create article $article = $this->createNode([ 'type' => 'article', 'title' => 'Test Article', 'field_tags' => ['AI', 'Drupal'], ]); // Call API (with jsonapi_extras aliases) $response = $this->drupalGet('/api/v1/articles/' . $article->id()); $data = json_decode($response); // Check aliased field $this->assertEquals('Test Article', $data->data->attributes->headline); $this->assertEquals(['AI', 'Drupal'], $data->data->attributes->tags); } }
  • jsonapi: Drupal core JSON:API module
  • api_normalization: API normalization and transformation
  • openapi_ui_redoc: Interactive API documentation
  • graphql_compose: GraphQL integration with JSON:API
  • rest: Drupal REST API module
  • jwt: JWT authentication for APIs

Resources

See Also