PHPackages                             staudenmeir/eloquent-json-relations - 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. [Database &amp; ORM](/categories/database)
4. /
5. staudenmeir/eloquent-json-relations

ActiveLibrary[Database &amp; ORM](/categories/database)

staudenmeir/eloquent-json-relations
===================================

Laravel Eloquent relationships with JSON keys

v1.15(2mo ago)1.1k5.8M—5%64[6 issues](https://github.com/staudenmeir/eloquent-json-relations/issues)20MITPHPPHP ^8.3CI passing

Since Oct 19Pushed 2mo ago10 watchersCompare

[ Source](https://github.com/staudenmeir/eloquent-json-relations)[ Packagist](https://packagist.org/packages/staudenmeir/eloquent-json-relations)[ Fund](https://paypal.me/JonasStaudenmeir)[ RSS](/packages/staudenmeir-eloquent-json-relations/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (18)Versions (66)Used By (20)

Eloquent JSON Relations
=======================

[](#eloquent-json-relations)

[![CI](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/ci.yml/badge.svg)](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/ci.yml?query=branch%3Amain)[![Code Coverage](https://camo.githubusercontent.com/fc5030ec42c47c738142f5a6cfab91252a07a212fe9644c20079576272188111/68747470733a2f2f636f6465636f762e696f2f67682f7374617564656e6d6569722f656c6f7175656e742d6a736f6e2d72656c6174696f6e732f67726170682f62616467652e7376673f746f6b656e3d54343149583533493555)](https://codecov.io/gh/staudenmeir/eloquent-json-relations)[![PHPStan](https://camo.githubusercontent.com/2b1732baa25914ee5ccbeaf42980d671de29700b49e0639e1edc8e66181f6905/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c25323031302d627269676874677265656e2e7376673f7374796c653d666c6174)](https://github.com/staudenmeir/eloquent-json-relations/actions/workflows/static-analysis.yml?query=branch%3Amain)[![Latest Stable Version](https://camo.githubusercontent.com/de6b1360c4b0ff505f3654bfeaf079ebe66bc969a0168897051b4a7317fa56f0/68747470733a2f2f706f7365722e707567782e6f72672f7374617564656e6d6569722f656c6f7175656e742d6a736f6e2d72656c6174696f6e732f762f737461626c65)](https://packagist.org/packages/staudenmeir/eloquent-json-relations)[![Total Downloads](https://camo.githubusercontent.com/9ca400f1055aae14587acc999452af1223a32511a3f8872e812268aa7d44ecde/68747470733a2f2f706f7365722e707567782e6f72672f7374617564656e6d6569722f656c6f7175656e742d6a736f6e2d72656c6174696f6e732f646f776e6c6f616473)](https://packagist.org/packages/staudenmeir/eloquent-json-relations/stats)[![License](https://camo.githubusercontent.com/aee81f7c13a26f1c75183a6473141db6b850e1ecbcaf27f5de9f64262aee2271/68747470733a2f2f706f7365722e707567782e6f72672f7374617564656e6d6569722f656c6f7175656e742d6a736f6e2d72656c6174696f6e732f6c6963656e7365)](https://github.com/staudenmeir/eloquent-json-relations/blob/main/LICENSE)

This Laravel Eloquent extension adds support for JSON foreign keys to `BelongsTo`, `HasOne`, `HasMany`, `HasOneThrough`, `HasManyThrough`, `MorphTo`, `MorphOne` and `MorphMany` relationships.

It also provides [many-to-many](#many-to-many-relationships) and [has-many-through](#has-many-through-relationships)relationships with JSON arrays.

Compatibility
-------------

[](#compatibility)

- MySQL 5.7+
- MariaDB 10.2+
- PostgreSQL 9.3+
- SQLite 3.38+
- SQL Server 2016+

Installation
------------

[](#installation)

```
composer require "staudenmeir/eloquent-json-relations:^1.1"

```

Use this command if you are in PowerShell on Windows (e.g. in VS Code):

```
composer require "staudenmeir/eloquent-json-relations:^^^^1.1"

```

Versions
--------

[](#versions)

LaravelPackage13.x1.1512.x1.1411.x1.1110.x1.89.x1.78.x1.67.x1.56.x1.45.81.35.5–5.71.2Usage
-----

[](#usage)

- [One-To-Many Relationships](#one-to-many-relationships)
    - [Referential Integrity](#referential-integrity)
- [Many-To-Many Relationships](#many-to-many-relationships)
    - [Array of IDs](#array-of-ids)
    - [Array of Objects](#array-of-objects)
    - [HasOneJson](#hasonejson)
    - [Composite Keys](#composite-keys)
    - [Query Performance](#query-performance)
- [Has-Many-Through Relationships](#has-many-through-relationships)
- [Deep Relationship Concatenation](#deep-relationship-concatenation)

### One-To-Many Relationships

[](#one-to-many-relationships)

In this example, `User` has a `BelongsTo` relationship with `Locale`. There is no dedicated column, but the foreign key (`locale_id`) is stored as a property in a JSON field (`users.options`):

```
class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
        'options' => 'json',
    ];

    public function locale()
    {
        return $this->belongsTo(Locale::class, 'options->locale_id');
    }
}

class Locale extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users()
    {
        return $this->hasMany(User::class, 'options->locale_id');
    }
}
```

Remember to use the `HasJsonRelationships` trait in both the parent and the related model.

#### Referential Integrity

[](#referential-integrity)

On [MySQL](https://dev.mysql.com/doc/refman/en/create-table-foreign-keys.html), [MariaDB](https://mariadb.com/kb/en/library/foreign-keys/), and [SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table)you can still ensure referential integrity with foreign keys on generated/computed columns.

Laravel migrations support this feature on MySQL/MariaDB:

```
Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->json('options');
    $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id');
    $table->unsignedBigInteger('locale_id')->storedAs($locale_id);
    $table->foreign('locale_id')->references('id')->on('locales');
});
```

Laravel migrations also support this feature on SQL Server:

```
Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->json('options');
    $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id');
    $locale_id = 'CAST('.$locale_id.' AS INT)';
    $table->computed('locale_id', $locale_id)->persisted();
    $table->foreign('locale_id')->references('id')->on('locales');
});
```

### Many-To-Many Relationships

[](#many-to-many-relationships)

The package also introduces two new relationship types: `BelongsToJson` and `HasManyJson`

Use them to implement many-to-many relationships with JSON arrays.

In this example, `User` has a `BelongsToMany` relationship with `Role`. There is no pivot table, but the foreign keys are stored as an array in a JSON field (`users.options`):

#### Array of IDs

[](#array-of-ids)

By default, the relationship stores pivot records as an array of IDs:

```
class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
       'options' => 'json',
    ];

    public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
    {
        return $this->belongsToJson(Role::class, 'options->role_ids');
    }
}

class Role extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
    {
        return $this->hasManyJson(User::class, 'options->role_ids');
    }
}
```

On the side of the `BelongsToJson` relationship, you can use `attach()`, `detach()`, `sync()` and `toggle()`:

```
$user = new User;
$user->roles()->attach([1, 2])->save(); // Now: [1, 2]

$user->roles()->detach([2])->save();    // Now: [1]

$user->roles()->sync([1, 3])->save();   // Now: [1, 3]

$user->roles()->toggle([2, 3])->save(); // Now: [1, 2]
```

#### Array of Objects

[](#array-of-objects)

You can also store pivot records as objects with additional attributes:

```
class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
       'options' => 'json',
    ];

    public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
    {
        return $this->belongsToJson(Role::class, 'options->roles[]->role_id');
    }
}

class Role extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
    {
        return $this->hasManyJson(User::class, 'options->roles[]->role_id');
    }
}
```

Here, `options->roles` is the path to the JSON array. `role_id` is the name of the foreign key property inside the record object:

```
$user = new User;
$user->roles()->attach([1 => ['active' => true], 2 => ['active' => false]])->save();
// Now: [{"role_id":1,"active":true},{"role_id":2,"active":false}]

$user->roles()->detach([2])->save();
// Now: [{"role_id":1,"active":true}]

$user->roles()->sync([1 => ['active' => false], 3 => ['active' => true]])->save();
// Now: [{"role_id":1,"active":false},{"role_id":3,"active":true}]

$user->roles()->toggle([2 => ['active' => true], 3])->save();
// Now: [{"role_id":1,"active":false},{"role_id":2,"active":true}]
```

**Limitations:** On SQLite and SQL Server, these relationships only work partially.

#### HasOneJson

[](#hasonejson)

Define a `HasOneJson` relationship if you only want to retrieve a single related instance:

```
class Role extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function latestUser(): \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson
    {
        return $this->hasOneJson(User::class, 'options->roles[]->role_id')
            ->latest();
    }
}
```

#### Composite Keys

[](#composite-keys)

If multiple columns need to match, you can define a composite key.

Pass an array of keys that starts with JSON key:

```
class Employee extends Model
{
    public function tasks(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
    {
        return $this->belongsToJson(
            Task::class,
            ['options->work_stream_ids', 'team_id'],
            ['work_stream_id', 'team_id']
        );
    }
}

class Task extends Model
{
    public function employees(): \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
    {
        return $this->hasManyJson(
            Employee::class,
            ['options->work_stream_ids', 'team_id'],
            ['work_stream_id', 'team_id']
        );
    }
}
```

#### Query Performance

[](#query-performance)

##### MySQL

[](#mysql)

On MySQL 8.0.17+, you can improve the query performance with [multi-valued indexes](https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-multi-valued).

Use this migration when the array is the column itself (e.g. `users.role_ids`):

```
Schema::create('users', function (Blueprint $table) {
    // ...

    // Array of IDs
    $table->rawIndex('(cast(`role_ids` as unsigned array))', 'users_role_ids_index');

    // Array of objects
    $table->rawIndex('(cast(`roles`->\'$[*]."role_id"\' as unsigned array))', 'users_roles_index');
});
```

Use this migration when the array is nested inside an object (e.g. `users.options->role_ids`):

```
Schema::create('users', function (Blueprint $table) {
    // ...

    // Array of IDs
    $table->rawIndex('(cast(`options`->\'$."role_ids"\' as unsigned array))', 'users_role_ids_index');

    // Array of objects
    $table->rawIndex('(cast(`options`->\'$."roles"[*]."role_id"\' as unsigned array))', 'users_roles_index');
});
```

MySQL is quite picky about the syntax so I recommend that you check once with [`EXPLAIN`](https://dev.mysql.com/doc/refman/8.0/en/using-explain.html) that the executed relationship queries actually use the index.

##### PostgreSQL

[](#postgresql)

On PostgreSQL, you can improve the query performance with `jsonb` columns and [`GIN` indexes](https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING).

Use this migration when the array of IDs/objects is the column itself (e.g. `users.role_ids`):

```
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->jsonb('role_ids');
    $table->index('role_ids')->algorithm('gin');
});
```

Use this migration when the array is nested inside an object (e.g. `users.options->role_ids`):

```
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->jsonb('options');
    $table->rawIndex('("options"->\'role_ids\')', 'users_options_index')->algorithm('gin');
});
```

### Has-Many-Through Relationships

[](#has-many-through-relationships)

Similar to Laravel's [`HasManyThrough`](https://laravel.com/docs/9.x/eloquent-relationships#has-many-through), you can define `HasManyThroughJson` relationships when the JSON column is in the intermediate table (Laravel 9+). This requires [staudenmeir/eloquent-has-many-deep](https://github.com/staudenmeir/eloquent-has-many-deep).

Consider a relationship between `Role` and `Project` through `User`:

`Role` → has many JSON → `User` → has many `Project`

[Install](https://github.com/staudenmeir/eloquent-has-many-deep/#installation) the additional package, add the `HasRelationships` trait to the parent (first) model and pass the JSON column as a `JsonKey` object:

```
class Role extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function projects()
    {
        return $this->hasManyThroughJson(
            Project::class,
            User::class,
            new \Staudenmeir\EloquentJsonRelations\JsonKey('options->role_ids')
        );
    }
}
```

The reverse relationship would look like this:

```
class Project extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function roles()
    {
        return $this->hasManyThroughJson(
            Role::class, User::class, 'id', 'id', 'user_id', new JsonKey('options->role_ids')
        );
    }
}
```

### Deep Relationship Concatenation

[](#deep-relationship-concatenation)

You can include JSON relationships into deep relationships by concatenating them with other relationships using [staudenmeir/eloquent-has-many-deep](https://github.com/staudenmeir/eloquent-has-many-deep) (Laravel 9+).

Consider a relationship between `User` and `Permission` through `Role`:

`User` → belongs to JSON → `Role` → has many → `Permission`

[Install](https://github.com/staudenmeir/eloquent-has-many-deep/#installation) the additional package, add the `HasRelationships` trait to the parent (first) model and [define](https://github.com/staudenmeir/eloquent-has-many-deep/#concatenating-existing-relationships) a deep relationship:

```
class User extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function permissions(): \Staudenmeir\EloquentHasManyDeep\HasManyDeep
    {
        return $this->hasManyDeepFromRelations(
            $this->roles(),
            (new Role)->permissions()
        );
    }

    public function roles(): \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
    {
        return $this->belongsToJson(Role::class, 'options->role_ids');
    }
}

class Role extends Model
{
    public function permissions()
    {
        return $this->hasMany(Permission::class);
    }
}

$permissions = User::find($id)->permissions;
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE OF CONDUCT](.github/CODE_OF_CONDUCT.md) for details.

###  Health Score

74

—

ExcellentBetter than 100% of packages

Maintenance85

Actively maintained with recent releases

Popularity68

Solid adoption and visibility

Community36

Small or concentrated contributor base

Maturity91

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 95.5% 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 ~45 days

Recently: every ~93 days

Total

60

Last Release

79d ago

Major Versions

v1.15 → 8.5.x-dev2026-03-01

PHP version history (11 changes)v1.0PHP &gt;=7.0

v1.2PHP ^7.1.3

v1.3PHP ^7.2

v1.4PHP ^7.2.5

v1.5PHP ^7.3

v1.5.1PHP ^7.3|^8.0

1.4.x-devPHP ^7.2.5|^8.0

v1.6PHP ^8.0.2

v1.8PHP ^8.1

v1.11PHP ^8.2

v1.15PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/3c1c3cda28d147c1899037c3187f89a606ca14f51190de59e88fa91e51647ff6?d=identicon)[staudenmeir](/maintainers/staudenmeir)

---

Top Contributors

[![staudenmeir](https://avatars.githubusercontent.com/u/1853169?v=4)](https://github.com/staudenmeir "staudenmeir (169 commits)")[![dsazup](https://avatars.githubusercontent.com/u/8631224?v=4)](https://github.com/dsazup "dsazup (1 commits)")[![francoism90](https://avatars.githubusercontent.com/u/5028905?v=4)](https://github.com/francoism90 "francoism90 (1 commits)")[![iksaku](https://avatars.githubusercontent.com/u/4632429?v=4)](https://github.com/iksaku "iksaku (1 commits)")[![mstaack](https://avatars.githubusercontent.com/u/10169509?v=4)](https://github.com/mstaack "mstaack (1 commits)")[![nikazooz](https://avatars.githubusercontent.com/u/13997617?v=4)](https://github.com/nikazooz "nikazooz (1 commits)")[![szepeviktor](https://avatars.githubusercontent.com/u/952007?v=4)](https://github.com/szepeviktor "szepeviktor (1 commits)")[![27pchrisl](https://avatars.githubusercontent.com/u/6286310?v=4)](https://github.com/27pchrisl "27pchrisl (1 commits)")[![ziadoz](https://avatars.githubusercontent.com/u/645637?v=4)](https://github.com/ziadoz "ziadoz (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

### Embed Badge

![Health badge](/badges/staudenmeir-eloquent-json-relations/health.svg)

```
[![Health](https://phpackages.com/badges/staudenmeir-eloquent-json-relations/health.svg)](https://phpackages.com/packages/staudenmeir-eloquent-json-relations)
```

###  Alternatives

[staudenmeir/eloquent-has-many-deep

Laravel Eloquent HasManyThrough relationships with unlimited levels

2.9k13.2M39](/packages/staudenmeir-eloquent-has-many-deep)[staudenmeir/laravel-adjacency-list

Recursive Laravel Eloquent relationships with CTEs

1.5k4.7M27](/packages/staudenmeir-laravel-adjacency-list)[owen-it/laravel-auditing

Audit changes of your Eloquent models in Laravel

3.4k33.0M95](/packages/owen-it-laravel-auditing)[bavix/laravel-wallet

It's easy to work with a virtual wallet.

1.3k1.1M11](/packages/bavix-laravel-wallet)[dragon-code/migrate-db

Easy data transfer from one database to another

15717.4k](/packages/dragon-code-migrate-db)[gearbox-solutions/eloquent-filemaker

A package for getting FileMaker records as Eloquent models in Laravel

6454.8k2](/packages/gearbox-solutions-eloquent-filemaker)

PHPackages © 2026

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