PHPackages                             trappistes/laravel-custom-fields - 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. trappistes/laravel-custom-fields

ActiveLaravel-package[Database &amp; ORM](/categories/database)

trappistes/laravel-custom-fields
================================

Laravel package for adding custom/dynamic fields to Eloquent models without schema changes

v1.0.0(2mo ago)00MITPHPPHP ^8.1

Since May 21Pushed 2mo agoCompare

[ Source](https://github.com/trappistes/laravel-custom-fields)[ Packagist](https://packagist.org/packages/trappistes/laravel-custom-fields)[ RSS](/packages/trappistes-laravel-custom-fields/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (3)Versions (2)Used By (0)

Laravel Custom Fields
=====================

[](#laravel-custom-fields)

A flexible EAV (Entity-Attribute-Value) package for Laravel Eloquent models, allowing dynamic custom fields without modifying the database schema.

**[🇨🇳 中文文档](README.zh-CN.md)** | **[🇺🇸 English](README.md)**

Features
--------

[](#features)

- **Dynamic Fields** — Add new fields to models without migrations
- **Strong Type Support** — Supports int, float, bool, json, date, datetime, daterange and more
- **Polymorphic Relations** — One custom fields table supports any model
- **Preloading Optimization** — Supports Eloquent preloading to avoid N+1 queries
- **Auto Type Conversion** — Automatic serialization and deserialization of field values
- **Batch Upsert** — Efficient bulk field operations via atomic upsert
- **Field Validation** — Observer-based key format and reserved word validation
- **Soft Deletes** — Custom fields preserved on soft delete, deleted on force delete

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

[](#installation)

```
composer require trappistes/laravel-custom-fields
```

Publishing Configuration
------------------------

[](#publishing-configuration)

```
php artisan vendor:publish --provider="Trappistes\CustomFields\Providers\CustomFieldsServiceProvider"
```

Configuration
-------------

[](#configuration)

```
// config/custom-fields.php
return [
    /*
    |--------------------------------------------------------------------------
    | Custom Field Model
    |--------------------------------------------------------------------------
    |
    | You can override the default CustomField model to customize the primary key
    | type (e.g., UUID, string) or add additional functionality.
    |
    */
    'model' => \Trappistes\CustomFields\Models\CustomField::class,

    /*
    |--------------------------------------------------------------------------
    | Custom Table Name
    |--------------------------------------------------------------------------
    |
    | Customize the table name for custom fields.
    |
    */
    'table_name' => 'custom_fields',

    /*
    |--------------------------------------------------------------------------
    | JSON Maximum Depth
    |--------------------------------------------------------------------------
    |
    | Maximum nesting depth for JSON encoding/decoding to prevent DoS attacks.
    |
    */
    'json_max_depth' => 64,
];
```

Run Migrations
--------------

[](#run-migrations)

```
php artisan migrate
```

Quick Start
-----------

[](#quick-start)

### 1. Use Trait in Model

[](#1-use-trait-in-model)

```
use Trappistes\CustomFields\Traits\HasCustomFields;

class User extends Model
{
    use HasCustomFields;
}
```

### 2. Set Custom Fields

[](#2-set-custom-fields)

```
$user = User::find(1);

// Set single field
$user->setCustomField('nickname', 'John');
$user->setCustomField('age', 25);
$user->setCustomField('is_vip', true);
$user->setCustomField('preferences', ['theme' => 'dark', 'lang' => 'en']);

// Batch set (uses upsert for performance)
$user->setCustomFields([
    'nickname' => 'John',
    'age' => 25,
    'is_vip' => true,
]);
```

### 3. Get Custom Fields

[](#3-get-custom-fields)

```
$user = User::find(1);

// Get single field
$nickname = $user->getCustomField('nickname');
$age = $user->getCustomField('age', 18); // with default value

// Using dynamic property
$nickname = $user->nickname;

// Check if field exists
$hasNickname = $user->hasCustomField('nickname');

// Get all custom fields
$allFields = $user->getAllCustomFields();
```

### 4. Preload Custom Fields

[](#4-preload-custom-fields)

```
// Recommended: use preloading to avoid N+1 queries
$users = User::with('customFields')->get();

foreach ($users as $user) {
    echo $user->getCustomField('nickname'); // no extra query
}
```

Supported Field Types
---------------------

[](#supported-field-types)

TypePHP TypeStorage FormatExample`string`stringraw string"Hello World"`bigint`intnumeric string"9223372036854775807"`float`floatnumeric string"3.14159"`bool`bool"1" or "0""1"`json`array/objectJSON string'{"key":"value"}'`date`Carbon/stringY-m-d"2024-01-15"`time`Carbon/stringH:i:s"14:30:00"`datetime`Carbon/stringY-m-d H:i:s"2024-01-15 14:30:00"`daterange`CarbonPeriod/DatePeriodJSON'{"start":"2024-01-01","end":"2024-01-31"}'Types are automatically inferred:

```
$user->setCustomField('metadata', ['key' => 'value']); // auto inferred as json
$user->setCustomField('count', 42); // auto inferred as bigint
```

Field Naming Rules
------------------

[](#field-naming-rules)

- Must start with letter or underscore
- Can only contain letters, numbers and underscores
- Reserved keys: `id`, `model_type`, `model_id`, `field_key`, `field_type`, `field_value`, `created_at`, `updated_at`, `deleted_at`, `exists`, `incrementing`, `wasRecentlyCreated`

Mass Assignment
---------------

[](#mass-assignment)

```
$user = User::find(1);

// fill() method also supports custom fields
$user->fill([
    'name' => 'John',
    'nickname' => 'nick',  // custom field
    'age' => 25,           // custom field
])->save();

// update() method separates regular and custom fields
$user->update([
    'name' => 'Updated Name',
    'department' => 'Engineering', // custom field
]);
```

Soft Deletes
------------

[](#soft-deletes)

When the main model uses soft deletes, custom fields are preserved on normal delete and deleted on force delete:

```
$user = User::find(1);

// Soft delete - custom fields preserved
$user->delete();

// Force delete - custom fields deleted too
$user->forceDelete();

// Restore - custom fields still available
$user->restore();
```

UUID / ULID Support
-------------------

[](#uuid--ulid-support)

By default, the `id` primary key and `model_id` column use `unsignedBigInteger`. To support UUID or ULID:

### Option 1: Use UUID

[](#option-1-use-uuid)

1. **Modify the migration** to use `uuid()` for the primary key and `model_id`:

```
// In your migration file
$table->uuid('id')->primary();
$table->uuid('model_id')->nullable();
```

2. **Create a custom model** with the `HasUuids` trait:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Trappistes\CustomFields\Models\CustomField as BaseCustomField;

class CustomField extends BaseCustomField
{
    use HasUuids;
}
```

### Option 2: Use ULID

[](#option-2-use-ulid)

1. **Modify the migration** to use `ulid()` for the primary key and `model_id`:

```
// In your migration file
$table->ulid('id')->primary();
$table->ulid('model_id')->nullable();
```

2. **Create a custom model** with the `HasUlids` trait:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Trappistes\CustomFields\Models\CustomField as BaseCustomField;

class CustomField extends BaseCustomField
{
    use HasUlids;
}
```

### Apply the Custom Model

[](#apply-the-custom-model)

Update your config to use the custom model:

```
// config/custom-fields.php
'model' => App\Models\CustomField::class,
```

Database Schema
---------------

[](#database-schema)

```
custom_fields
├── id              (bigint, primary)
├── model_type      (varchar, polymorphic type)
├── model_id        (bigint, polymorphic ID)
├── field_key       (varchar, attribute name)
├── field_type      (varchar, data type)
├── field_value     (text, serialized value)
├── created_at      (timestamp)
├── updated_at      (timestamp)

Indexes:
├── custom_fields_model_index (model_type, model_id)
├── custom_fields_key_index (field_key)
├── custom_fields_type_index (field_type)
└── custom_fields_unique_key UNIQUE (model_type, model_id, field_key)

```

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

[](#compatibility)

LaravelPHPStatus9.x8.1+Supported10.x8.1+Supported11.x8.2+Supported12.x8.2+SupportedTesting
-------

[](#testing)

```
# Run package tests with Orchestra Testbench
cd packages/laravel-custom-fields
composer install
vendor/bin/phpunit

# Run integration tests in main project
cd ../../
composer install
php artisan test
```

License
-------

[](#license)

MIT License

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance87

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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

Unknown

Total

1

Last Release

64d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4838f3fd4fa1be60d877abbbacd4432d7cab7fa25711cfc1343779d9f6c8473d?d=identicon)[一百万个答案](/maintainers/%E4%B8%80%E7%99%BE%E4%B8%87%E4%B8%AA%E7%AD%94%E6%A1%88)

---

Top Contributors

[![trappistes](https://avatars.githubusercontent.com/u/7021963?v=4)](https://github.com/trappistes "trappistes (1 commits)")

---

Tags

laraveleloquentcustom fieldsdynamic-fieldseavmodel-attributesmeta-fields

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/trappistes-laravel-custom-fields/health.svg)

```
[![Health](https://phpackages.com/badges/trappistes-laravel-custom-fields/health.svg)](https://phpackages.com/packages/trappistes-laravel-custom-fields)
```

###  Alternatives

[anourvalar/eloquent-serialize

Laravel Query Builder (Eloquent) serialization

11223.5M33](/packages/anourvalar-eloquent-serialize)[relaticle/custom-fields

User Defined Custom Fields for Laravel Filament

16354.2k](/packages/relaticle-custom-fields)[waad/laravel-model-metadata

A robust Laravel package for handling metadata with JSON casting, custom relation names, and advanced querying capabilities.

854.6k](/packages/waad-laravel-model-metadata)[mozex/laravel-scout-bulk-actions

Import, flush, and queue-import all your Laravel Scout searchable models at once. Auto-discovers models, runs in bulk, tracks progress.

1439.3k](/packages/mozex-laravel-scout-bulk-actions)

PHPackages © 2026

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