api-platform/json-api
JSON:API component for the API Platform framework. Adds JSON:API-compliant request/response handling and content negotiation for building standardized JSON APIs. Read-only split of api-platform/core; issues and PRs belong in the core repository.
Install API Platform Core (this package is a subcomponent):
composer require api-platform/core
(Note: This is a read-only split; use the core package directly.)
Configure Laravel Integration Install the Laravel adapter and enable the JSON:API extension:
composer require api-platform/laravel
Update config/app.php to include:
ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Annotate a Resource
Create a simple entity with #[ApiResource]:
// src/Entity/Post.php
use ApiPlatform\Core\Annotation\ApiResource;
#[ApiResource]
class Post {
public string $title;
public string $content;
}
Test the Endpoint
Visit /api/posts to see JSON:API formatted output:
{
"data": [
{
"type": "posts",
"id": "1",
"attributes": {
"title": "Hello World",
"content": "..."
}
}
]
}
Enable JSON:API Content Negotiation
Add this to config/packages/api_platform.yaml:
api_platform:
formats:
jsonapi: ['application/vnd.api+json']
patch_formats:
jsonapi: ['application/vnd.api+json']
Request only specific fields via query parameters:
GET /api/posts?fields[posts]=title
Response:
{
"data": [
{
"type": "posts",
"id": "1",
"attributes": {
"title": "Hello World"
}
}
]
}
Define operations and serialization groups in #[ApiResource]:
#[ApiResource(
collectionOperations: ['get' => ['method' => 'GET', 'path' => '/posts']],
itemOperations: ['get', 'put' => ['method' => 'PATCH']],
normalizationContext: ['groups' => ['post:read']],
denormalizationContext: ['groups' => ['post:write']]
)]
class Post { ... }
Use #[ApiProperty] and #[ApiRelation] for nested resources:
use ApiPlatform\Core\Annotation\ApiRelation;
#[ApiResource]
class Post {
#[ApiRelation(
attribute: 'author',
collection: false,
embedded: true,
normalizationContext: ['groups' => ['author:read']]
)]
public ?User $author;
}
Override default serialization via middleware or event listeners:
// src/EventListener/AddCustomContext.php
use ApiPlatform\Core\EventListener\JsonLdContextBuilder;
class AddCustomContext implements JsonLdContextBuilder {
public function __invoke($context, UrlGeneratorInterface $urlGenerator) {
$context['@context'] = '/api/contexts/Post';
return $context;
}
}
Leverage built-in pagination (default: 30 items):
# config/packages/api_platform.yaml
api_platform:
pagination_enabled: true
pagination_client_items_per_page: true
Request with custom page size:
GET /api/posts?page[size]=10
Use query parameters for dynamic filtering:
GET /api/posts?filter[title][contains]=Hello&order[title]=ASC
JSON:API errors are automatically formatted per RFC 7807:
{
"errors": [
{
"title": "Validation Failed",
"detail": "The title field is required.",
"source": { "pointer": "/data/attributes/title" },
"code": "422"
}
]
}
Use dependency injection to access the JSON:API serializer:
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\Serializer\SerializerInterface;
class PostController {
public function __construct(
private SerializerInterface $serializer,
private SerializerContextBuilderInterface $contextBuilder
) {}
public function show(Post $post) {
$context = $this->contextBuilder->createFromRequest(null, false);
return $this->serializer->serialize($post, 'jsonapi', $context);
}
}
Use Laravel’s HTTP tests with JSON:API assertions:
public function testJsonApiResponse() {
$response = $this->getJson('/api/posts');
$response->assertStatus(200)
->assertJsonStructure([
'data' => [
['type', 'id', 'attributes' => ['title', 'content']]
]
]);
}
Symfony Dependency Conflicts
autowiring.config/services.php:
ApiPlatform\Core\Serializer\SerializerContextBuilderInterface::class => \App\CustomContextBuilder::class,
Missing JSON:API Headers
Content-Type: application/vnd.api+json.Accept header is set or configure default format:
# config/packages/api_platform.yaml
api_platform:
formats:
jsonapi: ['application/vnd.api+json']
default_formats: ['jsonapi']
Relationship Serialization Issues
embedded: true and ensure proper #[Groups]:
#[ApiRelation(embedded: true, normalizationContext: ['groups' => ['author:read']])]
Pagination Quirks
page[size] is ignored.api_platform:
pagination_client_items_per_page: true
Circular References
#[MaxDepth] or customize the serializer:
$context = $this->contextBuilder->createFromRequest(null, false, ['max_depth' => 2]);
Inspect Serialization Context Dump the context to debug field inclusion:
$context = $this->contextBuilder->createFromRequest($request, false);
dd($context);
Enable API Platform Debug Toolbar Install the Symfony Profiler bundle for serialization insights:
composer require symfony/profiler-pack
Validate JSON:API Output Use jsonapi.tools to validate responses.
Check for Deprecated Attributes
Replace #[ApiProperty] with #[ApiResource] operations where applicable.
Default Pagination
Override in config/packages/api_platform.yaml:
api_platform:
pagination_items_per_page: 20
Custom Context Paths Define global context files:
api_platform:
jsonld_context:
paths:
- '%kernel.project_dir%/config/api_context.jsonld'
Disable JSON:API for Specific Resources
Use denormalizationContext to exclude:
#[ApiResource(
normalizationContext: ['enable_max_depth' => false]
)]
Custom Serializer
Extend JsonApiSerializer for domain-specific logic:
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
class CustomJsonApiSerializer extends JsonApiSerializer {
public function serialize($data, string $format, array $context = []) {
// Custom logic here
return parent::serialize($data, $format, $context);
}
}
Event Listeners
Modify responses via events (e.g., ApiPlatform\EventListener\JsonLdResponseContextListener):
use ApiPlatform\Core\EventListener\JsonLdResponseContextListener;
class CustomContextListener extends JsonLdResponseContextListener {
public function __invoke($event, $format, array $context = []) {
$context['custom_key'] = 'value';
return $context;
}
}
Dynamic Metadata Add runtime metadata to responses:
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
class DynamicMetadataFactory implements
How can I help you explore Laravel packages today?