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
- Name: JSON:API Extras
- Machine Name:
jsonapi_extras - Package: API Development
- Drupal Compatibility: 9.5, 10.x, 11.x
- PHP: >=7.4
- Maintainers: High (19,148+ sites actively using)
- Status: Stable & Maintained
- Source: https://www.drupal.org/project/jsonapi_extras
- Repository: https://git.drupalcode.org/project/jsonapi_extras
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:
- Clear Drupal cache:
drush cache:rebuild - Verify configuration in
/admin/config/services/jsonapi - Check field exists in content type
- 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:
- Check resource is not disabled in jsonapi_extras
- Verify authentication token
- Check user permissions
- 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); } }
Related Modules
- 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
- Project Page: https://www.drupal.org/project/jsonapi_extras
- Documentation: https://www.drupal.org/docs/contributed-modules/jsonapi-extras
- Issue Queue: https://www.drupal.org/project/issues/jsonapi_extras
- Repository: https://git.drupalcode.org/project/jsonapi_extras
- JSON:API Spec: https://jsonapi.org/
- Drupal JSON:API Docs: https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module