PHPackages                             ark4ne/laravel-json-api - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. ark4ne/laravel-json-api

ActiveLibrary[API Development](/categories/api)

ark4ne/laravel-json-api
=======================

A Lightweight JSON:API Resource for Laravel

v1.9.1(3mo ago)721.5k↓41.1%2[2 issues](https://github.com/Ark4ne/laravel-json-api/issues)1MITPHPPHP ^8.1CI passing

Since Apr 12Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/Ark4ne/laravel-json-api)[ Packagist](https://packagist.org/packages/ark4ne/laravel-json-api)[ RSS](/packages/ark4ne-laravel-json-api/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (10)Dependencies (12)Versions (61)Used By (1)

JsonApi - Laravel Resource
==========================

[](#jsonapi---laravel-resource)

A Lightweight [{JSON:API}](https://jsonapi.org/) Resource for Laravel.

[![example branch parameter](https://github.com/Ark4ne/laravel-json-api/actions/workflows/php.yml/badge.svg)](https://github.com/Ark4ne/laravel-json-api/actions/workflows/php.yml/badge.svg)[![codecov](https://camo.githubusercontent.com/2375585ff171702f205a807cc0209d667f325e38b7337682955a0279b1f71667/68747470733a2f2f636f6465636f762e696f2f67682f41726b346e652f6c61726176656c2d6a736f6e2d6170692f6272616e63682f6d61737465722f67726170682f62616467652e7376673f746f6b656e3d463758424c4147544450)](https://codecov.io/gh/Ark4ne/laravel-json-api)

Installation
============

[](#installation)

```
composer require ark4ne/laravel-json-api
```

Config
======

[](#config)

PathTypeDescription`describer.nullable``bool`For describer notation, defined if a value is nullable by default.`describer.date``string` datetime formatFor describer notation, defined default date time format.`describer.precision``int` \\ `null`For describer notation, decimal precision for float value. `null` for disable rounding.`describer.when-has``bool` \\ `string[]`For describer notation, Apply automatically whenHas condition on attributes.`relationship.when-included``bool`Allow to disabled by default the loading of relationship data.Usage
=====

[](#usage)

This package is a specialisation of Laravel's `JsonResource` class. All the underlying API's are still there, thus in your controller you can still interact with `JsonApiResource` classes as you would with the base `JsonResource` class

Request
-------

[](#request)

This package allows the reading and dynamic inclusion of resources that will be requested in the requests via the "include" parameter.
**@see** *[{json:api} fetching-includes](https://jsonapi.org/format/#fetching-includes)*

Resource attributes will also be filtered according to the "fields" parameter.
**@see** *[{json:api} fetching-fields](https://jsonapi.org/format/#fetching-sparse-fieldsets)*

You can also very simply validate your requests for a given resource via the rules `Rules\Includes` and `Rules\Fields`.

### Include validation

[](#include-validation)

```
use \Ark4ne\JsonApi\Requests\Rules\Includes;
use \Illuminate\Foundation\Http\FormRequest;

class UserFetchRequest extends FormRequest
{
    public function rules()
    {
        return [
            'include' => [new Includes(UserResource::class)],
        ]
    }
}
```

`Rules\Includes` will validate the `include` to exactly match the UserResource schema (determined by the relationships).

### Fields validation

[](#fields-validation)

```
use \Ark4ne\JsonApi\Requests\Rules\Fields;
use \Illuminate\Foundation\Http\FormRequest;

class UserFetchRequest extends FormRequest
{
    public function rules()
    {
        return [
            'fields' => [new Fields(UserResource::class)],
        ]
    }
}
```

`Rules\Fields` will validate the `fields` to exactly match the UserResource schema (determined by the attributes and relationships).

### Customize validation message

[](#customize-validation-message)

Trans keydefault`validation.custom.jsonapi.fields.invalid`The selected :attribute is invalid.`validation.custom.jsonapi.fields.invalid_fields`":resource" doesn ' t have fields ":fields".`validation.custom.jsonapi.fields.invalid_resource`":resource" doesn ' t exists.`validation.custom.jsonapi.includes.invalid`The selected :attribute is invalid.`validation.custom.jsonapi.includes.invalid_includes`":include" doesn ' t have relationship ":relation".Resource
--------

[](#resource)

**@see** *[{json:api} resource-type](https://jsonapi.org/format/#document-resource-objects)*

Implementable methods :

```
protected function toType(Request $request): string;

protected function toIdentifier(Request $request): int|string;

public function toAttributes(Request $request): iterable;

protected function toRelationships(Request $request): iterable;

protected function toResourceMeta(Request $request): ?iterable;

protected function toMeta(Request $request): ?iterable;
```

Example:

```
use Ark4ne\JsonApi\Resources\JsonApiResource;
use Illuminate\Http\Request;

class UserResource extends JsonApiResource
{
    public function toAttributes(Request $request): iterable
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }

    protected function toResourceMeta(Request $request): ?iterable
    {
        return [
            'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
            'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
        ];
    }

    protected function toRelationships(Request $request): iterable
    {
        return [
            'posts' => PostResource::relationship(fn() => $this->posts, fn() => [
                'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
                'related' => "https://api.example.com/user/{$this->id}/posts",
            ]),
            'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments')),
        ];
    }
}
```

### toType

[](#totype)

***@see** [{json:api} resource-type](https://jsonapi.org/format/#document-resource-object-identification)*

Returns resource type.

```
protected function toType(Request $request): string
{
    return 'user';
}
```

Default returns model class in kebab case : `App\Models\MyPost` =&gt; `my-post`

### toIdentifier

[](#toidentifier)

***@see** [{json:api} resource-identifier](https://jsonapi.org/format/#document-resource-object-identification)*

Returns resource identifier.

```
protected function toIdentifier(Request $request): int|string
{
    return $this->id;
}
```

Default returns model id.

### toAttributes

[](#toattributes)

***@see** [{json:api} resource-attributes](https://jsonapi.org/format/#document-resource-object-attributes)*

Returns resource attributes.

```
public function toAttributes(Request $request): iterable
{
    return [
        'name' => $this->name,
        'email' => $this->email,
    ];
}
```

#### Laravel conditional attributes

[](#laravel-conditional-attributes)

***@see** [laravel: eloquent-conditional-attributes](https://laravel.com/docs/9.x/eloquent-resources#conditional-attributes)*

Support laravel conditional attributes.

```
public function toAttributes(Request $request): array
{
    return [
        'name' => $this->name,
        'email' => $this->email,
        // with lazy evaluation
        'hash64' => fn() => base64_encode("{$this->id}-{$this->email}"),
        // Conditional attribute
        'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
        // Merging Conditional Attributes
        // use applyWhen insteadof mergeWhen for keep fields
        // useful for fields request rules validation
        $this->applyWhen($request->user()->isAdmin(), [
            'first-secret' => 123,
            'second-secret' => 456.789,
        ]),
    ];
}
```

#### Described attributes

[](#described-attributes)

***@see** [described notation](#described-notation)*

```
public function toAttributes(Request $request): array
{
    return [
        'name' => $this->string(),
        // pass key to describer
        $this->string('email'),
        // with lazy evaluation
        'hash64' => $this->string(fn() => base64_encode("{$this->id}-{$this->email}")),
        // Conditional attribute
        $this->string('secret')->when($request->user()->isAdmin(), 'secret-value'),
        // Merging Conditional Attributes
        $this->applyWhen($request->user()->isAdmin(), [
            'first-secret' => $this->integer(fn() => 123),
            'second-secret' => $this->float(fn() => 456.789),
        ]),
    ];
}
```

### toRelationships

[](#torelationships)

***@see** [{json:api} resources-relationships](https://jsonapi.org/format/#document-resource-object-relationships)*

Returns resource relationships.

All relationships **must** be created with `ModelResource::relationship`. This allows the generation of the schema representing the resource and thus the validation of request includes.

If your relation should have been a collection created via the `::collection(...)` method, you can simply use `->asCollection()`.

If you want the relation data to be loaded only when it is present in the request include, you can use the `->whenIncluded()` method.

```
protected function toRelationships(Request $request): array
{
    return [
        'avatar' => AvatarResource::relationship($this->avatar),
        // with conditional relationship
        'administrator' => $this->when($request->user()->isAdmin(), UserResource::relationship(fn() => $this->administrator),
        // as collection, with conditional value
        'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
        // with relationship (allow to include links and meta on relation)
        'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
            'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
            'related' => "https://api.example.com/user/{$this->id}/posts",
        ])->asCollection(),
    ];
}
```

`toRelationships` must returns an array, keyed by string, of `JsonApiResource` or `JsonApiCollection`.

#### Laravel conditional relationships

[](#laravel-conditional-relationships)

***@see** [laravel: eloquent-conditional-relationships](https://laravel.com/docs/9.x/eloquent-resources#conditional-relationships)*

Support laravel conditional relationships.

```
protected function toRelationships(Request $request): array
{
    return [
        'avatar' => AvatarResource::relationship($this->avatar),
        // as collection, with condition
        'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
        // with relationship (allow to include links and meta on relation)
        'posts' => PostResource::relationship(fn() => $this->posts)
                ->asCollection(),
    ];
}
```

#### Described attributes

[](#described-attributes-1)

***@see** [described notation](#described-notation)*

```
protected function toRelationships(Request $request): array
{
    return [
        'avatar' => $this->one(AvatarResource::class),
        // custom relation name
        'my-avatar' => $this->one(AvatarResource::class, 'avatar'),
        // as collection, with condition
        'comments' => $this->many(CommentResource::class)
                           ->whenLoaded(),
        // with relationship (allow to include links and meta on relation)
        'posts' => $this->many(PostResource::class)
                ->links(fn() => [
                    'self' => "https://api.example.com/posts/{$this->resource->id}/relationships/posts",
                    'related' => "https://api.example.com/posts/{$this->resource->id}/posts",
                ])
                ->meta(fn() => [
                    'total' => $this->integer(fn() => $this->resource->posts()->count()),
                ]),
    ];
}
```

#### Relation links and meta

[](#relation-links-and-meta)

***@see** [{json:api}: relation-linkage](https://jsonapi.org/format/#document-resource-object-related-resource-links)*
***@see** [{json:api}: relation-meta](https://jsonapi.org/format/#document-resource-object-relationships)*

Returns links and meta for a relation.

```
protected function toRelationships(Request $request): array
{
    return [
        'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
            // links
            'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
            'related' => "https://api.example.com/user/{$this->id}/posts",
        ])->withMeta(fn() => [
            // meta
            'creator' => $this->name,
        ])
        ->asCollection(),
    ];
}
```

### toLinks

[](#tolinks)

***@see** [{json:api}: resource-linkage](https://jsonapi.org/format/#document-resource-object-links)*

Returns resource links.

```
protected function toLinks(Request $request): ?array
{
    return [
        'self' => route('api.user.show', ['id' => $this->id]),
    ];
}
```

### toResourceMeta

[](#toresourcemeta)

***@see** [{json:api}: resource-meta](https://jsonapi.org/format/#document-resource-objects)*
***@see** [{json:api}: document-meta](https://jsonapi.org/format/#document-meta)*

Returns resource meta.

```
protected function toResourceMeta(Request $request): ?iterable
{
    return [
        'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
        'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
    ];
}
```

### toMeta

[](#tometa)

***@see** [{json:api}: document-meta](https://jsonapi.org/format/#document-meta)*

Returns document meta.

```
protected function toMeta(Request $request): ?iterable
{
    return [
        "copyright": "Copyright 2022 My Awesome Api",
    ];
}
```

Collection
----------

[](#collection)

***@see** [laravel: resource-collection](https://laravel.com/docs/9.x/eloquent-resources#resource-collections)*

Collection are implemented in `JsonApiCollection`.

Usage is the same as laravel collections.

```
UserResource::collection(User::all()); // => JsonApiCollection
```

Described notation
------------------

[](#described-notation)

### Value methods

[](#value-methods)

MethodDescription`bool`Cast to boolean`integer`Cast to integer`float`Cast to float`string`Cast to string`date`Cast to date, allow to use custom format`array`Cast to array, supports typed arrays with `->of()``arrayOf`Helper method for typed arrays (alternative to `array()->of()`)`mixed`Don't cast, return as is`enum`Get enum value`struct`Custom struct. Accept an array of values### Relation methods

[](#relation-methods)

MethodDescription`one`For relationship with a single value: `HasOne`, `BelongsTo`, ...`many`For relationship with many value: `HasMany`, `BelongsToMany`, ...### Enum

[](#enum)

Method `enum` allow to get enum value for backed enum or name for unit enum.

According to structure:

```
/// Role.php
enum Role {
    case ADMIN;
    case USER;
}
/// State.php
enum State:int {
    case ACTIVE = 1;
    case INACTIVE = 0;
}
/// User.php
class User extends Model
{
    $casts = [
        'role' => Role::class,
        'state' => State::class,
    ];
}
```

The following attributes resource:

```
// UserResource.php
public function toAttributes(Request $request): array
{
    return [
        'status' => $this->enum(),
        'role' => $this->enum(),
    ];
}
```

Will return:

```
[
    "status": 1,
    "role": "ADMIN"
]
```

### Typed Arrays

[](#typed-arrays)

The `array` descriptor supports typed arrays to ensure all elements are cast to a specific type. This is useful when you need to guarantee type consistency across array elements.

#### Basic Usage

[](#basic-usage)

```
// UserResource.php
public function toAttributes(Request $request): array
{
    return [
        // Array of strings - all values will be cast to string
        'tags' => $this->array('tags')->of($this->string()),

        // Array of integers - all values will be cast to integer
        'scores' => $this->array('scores')->of($this->integer()),

        // Array of floats
        'prices' => $this->array('prices')->of($this->float()),

        // Array of booleans
        'flags' => $this->array('flags')->of($this->bool()),
    ];
}
```

#### Using Class References

[](#using-class-references)

You can also use class references instead of descriptor instances:

```
use Ark4ne\JsonApi\Descriptors\Values\ValueString;
use Ark4ne\JsonApi\Descriptors\Values\ValueInteger;

public function toAttributes(Request $request): array
{
    return [
        'tags' => $this->array('tags')->of(ValueString::class),
        'scores' => $this->array('scores')->of(ValueInteger::class),
    ];
}
```

#### Alternative Syntax

[](#alternative-syntax)

You can also use the `arrayOf()` helper method:

```
public function toAttributes(Request $request): array
{
    return [
        'tags' => $this->arrayOf($this->string(), 'tags'),
        'scores' => $this->arrayOf($this->integer(), 'scores'),
    ];
}
```

#### Nested Typed Arrays

[](#nested-typed-arrays)

For multi-dimensional arrays, you can nest `array()->of()` calls:

```
public function toAttributes(Request $request): array
{
    return [
        // 2D array (matrix) of integers
        'matrix' => $this->array('matrix')->of(
            $this->array()->of($this->integer())
        ),
    ];
}
```

#### With Closures and Transformations

[](#with-closures-and-transformations)

Combine typed arrays with closures for data transformation:

```
public function toAttributes(Request $request): array
{
    return [
        // Transform and type cast
        'doubled' => $this->array(fn() => array_map(fn($n) => $n * 2, $this->numbers))
            ->of($this->integer()),

        // Access nested properties
        'user_ids' => $this->array(fn() => $this->users->pluck('id'))
            ->of($this->integer()),
    ];
}
```

#### With Conditions

[](#with-conditions)

Typed arrays support all conditional methods:

```
public function toAttributes(Request $request): array
{
    return [
        // Only include if not null
        'tags' => $this->array('tags')->of($this->string())->whenNotNull(),

        // Only include if array is not empty
        'scores' => $this->array('scores')->of($this->integer())->whenFilled(),

        // Conditional based on closure
        'admin_notes' => $this->array('notes')->of($this->string())
            ->when(fn() => $request->user()->isAdmin()),
    ];
}
```

> **⚠️ Important Note:** Conditions applied to the item type (inside `of()`) are **not evaluated per-item**. They apply to the entire array descriptor, not individual elements.
>
> ```
> // ❌ This will NOT filter individual items
> 'even-numbers' => $this->array('numbers')->of(
>     $this->integer()->when(fn($request, $model, $attr) => $attr % 2 === 0)
> )
> // All items will be included, the when() doesn't filter per item
>
> // ✅ To filter items, do it before passing to the array
> 'even-numbers' => $this->array(
>     fn() => array_filter($this->numbers, fn($n) => $n % 2 === 0)
> )->of($this->integer())
> ```

#### Example

[](#example)

Given a model with mixed-type arrays:

```
$user = new User([
    'tags' => ['php', 'laravel', 123, true],
    'scores' => [95.5, '87', 92, '78.9'],
]);
```

The resource will ensure type consistency:

```
public function toAttributes(Request $request): array
{
    return [
        'tags' => $this->array('tags')->of($this->string()),
        'scores' => $this->array('scores')->of($this->integer()),
    ];
}
```

Output:

```
{
    "tags": ["php", "laravel", "123", "1"],
    "scores": [95, 87, 92, 78]
}
```

###  Health Score

53

—

FairBetter than 96% of packages

Maintenance77

Regular maintenance activity

Popularity33

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 99.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~26 days

Recently: every ~4 days

Total

55

Last Release

90d ago

Major Versions

v0.0.1 → v1.0.02022-04-21

PHP version history (2 changes)v0.0.0-RC1PHP ^8.0

v1.4.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/a9364c697a58ed619a061145dc64c8ee6bbd0536c4594ab3c2b0ffce3e902bc5?d=identicon)[Ark4ne](/maintainers/Ark4ne)

---

Top Contributors

[![Ark4ne](https://avatars.githubusercontent.com/u/6144058?v=4)](https://github.com/Ark4ne "Ark4ne (175 commits)")[![yanis-git](https://avatars.githubusercontent.com/u/4113879?v=4)](https://github.com/yanis-git "yanis-git (1 commits)")

---

Tags

jsonapijsonapi-resourceslaravellaravelJSON-APIjsonapi.orglaravel jsonlaravel-resource

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ark4ne-laravel-json-api/health.svg)

```
[![Health](https://phpackages.com/badges/ark4ne-laravel-json-api/health.svg)](https://phpackages.com/packages/ark4ne-laravel-json-api)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

815320.5k3](/packages/defstudio-telegraph)[illuminate/auth

The Illuminate Auth package.

9327.9M1.2k](/packages/illuminate-auth)[illuminate/routing

The Illuminate Routing package.

1239.0M2.8k](/packages/illuminate-routing)[laravel-json-api/core

Contracts and support classes for Laravel JSON:API packages.

101.6M16](/packages/laravel-json-api-core)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
