PHPackages                             salah/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. [Utility &amp; Helpers](/categories/utility)
4. /
5. salah/laravel-custom-fields

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

salah/laravel-custom-fields
===========================

Professional, flexible, and headless-ready Custom Fields (EAV) package for Laravel.

1.2.2(1mo ago)113MITPHPPHP ^8.2

Since Feb 8Pushed 1mo agoCompare

[ Source](https://github.com/salaheldeen911/laravel-custom-fields)[ Packagist](https://packagist.org/packages/salah/laravel-custom-fields)[ Docs](https://github.com/salaheldeen911/laravel-custom-fields)[ GitHub Sponsors]()[ RSS](/packages/salah-laravel-custom-fields/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (28)Versions (9)Used By (0)

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

[](#laravel-custom-fields)

[![Latest Version on Packagist](https://camo.githubusercontent.com/a37408ca1fc06a1fb8c04d875de1944372958d477404a12d416665ff360d6034/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73616c6168656c6465656e3931312f6c61726176656c2d637573746f6d2d6669656c64732e7376673f7374796c653d666f722d7468652d626164676526636f6c6f723d626c7565)](https://packagist.org/packages/salaheldeen911/laravel-custom-fields)[![Total Downloads](https://camo.githubusercontent.com/138350ef144d44a30e7e0e57ef1ad3f5d578615046a24ea9a3f15d4579818834/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73616c6168656c6465656e3931312f6c61726176656c2d637573746f6d2d6669656c64732e7376673f7374796c653d666f722d7468652d626164676526636f6c6f723d677265656e)](https://packagist.org/packages/salaheldeen911/laravel-custom-fields)[![PHP Version](https://camo.githubusercontent.com/a50f323e5ead331c10da5f453e2c0cafc64ac0b0921ffbc69e770c9098708e35/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f73616c6168656c6465656e3931312f6c61726176656c2d637573746f6d2d6669656c64732e7376673f7374796c653d666f722d7468652d626164676526636f6c6f723d373737626234)](https://packagist.org/packages/salaheldeen911/laravel-custom-fields)[![License](https://camo.githubusercontent.com/edaeccecb387afb7cabb5862b72432b42552f6cacf41c9d1faca6a3e156bc719/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f73616c6168656c6465656e3931312f6c61726176656c2d637573746f6d2d6669656c64732e7376673f7374796c653d666f722d7468652d626164676526636f6c6f723d6f72616e6765)](https://packagist.org/packages/salaheldeen911/laravel-custom-fields)

**The Professional, Sealed-Lifecycle EAV Solution for Modern Laravel Applications.**

Tired of messy "extra\_attributes" JSON columns that are impossible to validate? Treat user-defined fields as first-class citizens. This package provides high-performance, strictly validated, and extensible custom fields with native support for both **Blade (Full-Stack)** and **Headless (API)** architectures.

---

🔥 Why This Package?
-------------------

[](#-why-this-package)

- **🛡 Strict Lifecycle**: We validate the *rules* themselves. You can't save a `min > max` or an invalid `regex`.
- **🚫 Intelligent Conflict Prevention**: Automatically prevents assigning conflicting rules (e.g., you can't use `Letters Only` and `Alpha-Numeric` together).
- **⚡️ Built for Speed**: Uses database `upserts` and batch operations. Reduces database overhead from N queries to just **one** per request.
- **🏗 Refactor-Safe Polymorphism**: Uses a `config` map for models. High stability even if you change model namespaces.
- **🧩 Dual-Nature Architecture**:
    - **Blade**: Ready-to-use Tailwind components with error handling and old-input support.
    - **Headless**: Rich metadata API (`models-and-types`) explaining rules, labels, and tags to your Frontend.

---

📦 Installation
--------------

[](#-installation)

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

Install the package (publishes config, migrations, and assets):

```
php artisan custom-fields:install
```

---

⚙️ Configuration
----------------

[](#️-configuration)

1. **Map Your Models**: In `config/custom-fields.php`, define simple aliases for your models. This decouples your database from your class names.

    ```
    'models' => [
        'user'    => \App\Models\User::class,
        'product' => \App\Models\Product::class,
    ],
    ```
2. **Prepare Your Model**: Add the `HasCustomFields` trait.

    ```
    use Salah\LaravelCustomFields\Traits\HasCustomFields;

    }
    ```
3. **Advanced Configuration**: Tune the package in `config/custom-fields.php`.

    - **Cache Strategy**: Control `ttl` and `prefix` to balance performance and freshness.
    - **Security**: Enable `sanitize_html` to automatically strip tags from text inputs.
    - **Maintenance**: Configure `pruning` retention periods for soft-deleted fields.

Important

**API Security**: If you enable API or Web routes in config, the package will automatically check for authentication middleware. If missing, it will log a warning. Ensure your routes are protected by adding `auth` middleware in the config.

---

🧹 Maintenance &amp; Pruning
---------------------------

[](#-maintenance--pruning)

To keep your database clean, you can permanently remove soft-deleted custom fields that are older than a configured threshold.

1. **Configure**: Set `'prune_deleted_after_days' => 30` in your config file.
2. **Run Command**:

    ```
    php artisan custom-fields:prune
    ```

    *Tip: Schedule this command in your `App\Console\Kernel` to run weekly.*

---

🧠 Architecture &amp; Validation Concepts
----------------------------------------

[](#-architecture--validation-concepts)

This package separates the world into two distinct logical flows to prevent confusion:

### 1. The Admin Flow (Defining Fields)

[](#1-the-admin-flow-defining-fields)

- **Goal:** Define *what* a field is (e.g., "Age").
- **Trait:** `ValidatesFieldDefinition`
- **Usage:** Only used when creating/editing the field definitions themselves. It validates that your rules don't conflict (e.g., preventing `alpha` logic on a `number` field).

### 2. The User Flow (Entering Data)

[](#2-the-user-flow-entering-data)

- **Goal:** Fill in the field (e.g., "25").
- **Trait:** `ValidatesFieldData`
- **Usage:** Used in your Application's forms. It applies the rules defined in Step 1 to the user's input.

---

🏛 Usage: The Laravel Way
------------------------

[](#-usage-the-laravel-way)

### 1. Rendering the UI (Blade)

[](#1-rendering-the-ui-blade)

Automatically render all custom fields for a specific model using a single tag. It handles `errors`, `old()`, and specific input types.

```

    @csrf

    Save

```

### 2. Validation (Option A: Form Request - Recommended)

[](#2-validation-option-a-form-request---recommended)

The cleanest way to validate custom fields is by using the `ValidatesFieldData` trait in your Form Request.

> **CRITICAL:** If `strict_validation` is enabled in config (default: true), you **MUST** use this trait. It not only merges rules but also "marks" the data as safely validated. Failure to use it will result in a `ValidationIntegrityException`.

```
use Salah\LaravelCustomFields\Traits\ValidatesFieldData;

class StoreUserRequest extends FormRequest
{
    use ValidatesFieldData;

    public function rules(): array
    {
        // MERGE custom fields rules into your existing rules
        return $this->withCustomFieldsRules(User::class, [
            'name' => 'required|string|max:255',
        ]);
    }
}
```

### 3. Validation (Option B: Controller)

[](#3-validation-option-b-controller)

If you prefer validating in the controller, use the helper method on the model:

```
$validated = $request->validate(array_merge([
    'name' => 'required',
], User::getCustomFieldRules()));

// Note: getCustomFieldRules() is a helper from the HasCustomFields trait
// getCustomFieldModelAlias() is also available for programmatic model resolution
```

### 3. Validation (Option C: Manual Service)

[](#3-validation-option-c-manual-service)

For complex scenarios where you need granular control or are validating data outside of a request:

```
// Validate only custom fields (Throws ValidationException on failure)
app(CustomFieldsService::class)->validate(User::class, $data);
```

### 4. Storage &amp; Updates

[](#4-storage--updates)

Use optimized batch methods to save or update custom values.

> **Recommendation:** It is highly recommended to wrap the creation/update of your main model and the custom fields in a **Database Transaction**. This ensures that if the custom field validation fails (or any other error occurs), the main model is not created/updated partially.

```
use Illuminate\Support\Facades\DB;

// Storing
DB::transaction(function () use ($request) {
    $user = User::create($request->validated());
    $user->saveCustomFields($request->validated());
});

// Updating (Uses high-performance UPSERT logic)
DB::transaction(function () use ($request, $user) {
    $user->update($request->validated());
    $user->updateCustomFields($request->validated());
});
```

---

🔍 Retrieval &amp; Powerful Querying
-----------------------------------

[](#-retrieval--powerful-querying)

### Get Single Value

[](#get-single-value)

```
$bio = $user->custom('biography');
```

### Get All Values (Flat Array)

[](#get-all-values-flat-array)

Perfect for API responses or data exports.

```
return response()->json([
    'user' => $user,
    'custom_data' => $user->customFieldsResponse()
]);
// Response: {"biography": "...", "age": 30, "city": "Cairo"}
```

### Querying like a Pro

[](#querying-like-a-pro)

The package provides a powerful scope to filter your models by custom fields values.

```
// Find users where custom field 'city' is 'Cairo'
$users = User::whereCustomField('city', 'Cairo')->get();
```

---

⚡️ Performance &amp; Eager Loading
----------------------------------

[](#️-performance--eager-loading)

To avoid the **N+1 query problem** when displaying multiple models, always use the `withCustomFields` scope. This eager loads all values and their field configurations in just two queries.

```
// Optimized for lists/tables
$users = User::withCustomFields()->paginate(20);

foreach ($users as $user) {
    echo $user->custom('biography'); // No extra queries!
}
```

### Optimize Show/Edit Pages

[](#optimize-showedit-pages)

When displaying a single model (e.g., in `show` or `edit` methods), use the `loadCustomFields()` helper to ensure all data is loaded efficiently before rendering the view.

```
public function edit(User $user)
{
    // Eager loads values relationship
    return view('users.edit')->with('user', $user->loadCustomFields());
}
```

---

🧩 Built-in Field Types
----------------------

[](#-built-in-field-types)

TypeIconHTML ControlSupported Rules`text`📝```min`, `max`, `regex`, `alpha`, `alpha_dash`, `alpha_num``textarea`📄```min`, `max`, `regex`, `not_regex``number`🔢```min`, `max``decimal`💹```min`, `max``date`📅```after`, `before`, `after_or_equal`, `date_format``time`🕒```required` (Standard string validation)`select`🔽```required` (Strictly validated against options)`checkbox`✅```required``phone`📞```phone`, `mobile`, `landline` (Supports formats or `AUTO` detection)`email`✉️```min`, `max`, `regex` (Native email validation)`url`🔗```min`, `max`, `regex` (Native URL validation)`color`🎨```required` (Validates hex color format)`file`📂```mimes`, `max_file_size` (Secure storage &amp; URL generation)---

🛡 Validation Rule Conflicts
---------------------------

[](#-validation-rule-conflicts)

The system is smart enough to prevent logical errors in your field configurations. If you try to apply conflicting rules, the system will throw a validation error during the field creation/update.

**Common Conflicts Prevented:**

- `alpha` vs `alpha_num` vs `alpha_dash`
- `after` vs `after_or_equal`
- `before` vs `before_or_equal`

---

🛠 Advanced Customization
------------------------

[](#-advanced-customization)

### Registering New Types

[](#registering-new-types)

Create a class extending `FieldType` and register it in your `AppServiceProvider`.

```
public function boot() {
    $this->app->make(FieldTypeRegistry::class)->register(new MyCustomType());
}
```

### Extending Validation Rules

[](#extending-validation-rules)

You can add your own validation rules. If your rule conflicts with another, simply override the `conflictsWith()` method:

```
class MyPremiumRule extends ValidationRule {
    public function conflictsWith(): array {
        return ['basic_rule_name'];
    }
}
```

---

🏛 Headless &amp; API Reference
------------------------------

[](#-headless--api-reference)

This package is a first-class citizen for Headless architectures. It provides a built-in API to manage custom fields and provides the necessary metadata for frontends to render them.

### 1. The Blueprint (Metadata)

[](#1-the-blueprint-metadata)

Before rendering any UI, your frontend (React/Vue/Mobile) should fetch the types and rules.

**Endpoint:** `GET /api/custom-fields/models-and-types`

**Response:**

```
{
  "success": true,
  "data": {
    "models": ["user", "product"],
    "types": [
      {
        "name": "text",
        "label": "Text Field",
        "tag": "input",
        "type": "text",
        "has_options": false,
        "allowed_rules": [
          { "name": "min", "label": "Min Length", "tag": "input", "type": "number" }
        ]
      }
    ]
  }
}
```

### 2. Managing Fields (CRUD API)

[](#2-managing-fields-crud-api)

If you are building your own Admin Dashboard in a JS framework, use these endpoints:

MethodEndpointDescription**GET**`/api/custom-fields`List all fields (Paginated)**POST**`/api/custom-fields`Create a new field**PUT**`/api/custom-fields/{id}`Update field configuration**DELETE**`/api/custom-fields/{id}`Soft delete a field**POST**`/api/custom-fields/{id}/restore`Restore a soft-deleted field**DELETE**`/api/custom-fields/{id}/force`Permanently delete a field#### Example: Creating a Field

[](#example-creating-a-field)

**Payload (`POST /api/custom-fields`):**

```
{
  "name": "Technical Bio",
  "model": "user",
  "type": "text",
  "required": true,
  "validation_rules": {
    "min": 10,
    "max": 500
  }
}
```

### 3. Storing Values (Entity Integration)

[](#3-storing-values-entity-integration)

When your frontend sends data to update a model (like a User profile), send the custom fields as a flat object where the key is the **slug**.

**Payload (`PUT /api/users/12`):**

```
{
  "name": "Salah Eldeen",
  "email": "salah@example.com",
  "technical-bio": "Full-stack developer with 10 years of experience."
}
```

**Controller Implementation:**

```
public function update(Request $request, User $user) {
    $user->update($request->all());
    $user->updateCustomFields($request->all()); // Scans for slugs and updates values automatically

    return response()->json(['success' => true]);
}
```

---

🎨 Management UI
---------------

[](#-management-ui)

The package comes with a built-in, secure management interface to create and manage fields.

- **Route**: `/custom-fields` (Configurable in `custom-fields.php`)
- **Features**: List, Search, Create, Edit, and Trash management.

---

---

👨‍🍳 Cookbook: Advanced Scenarios
--------------------------------

[](#‍-cookbook-advanced-scenarios)

### Creating a Dependent Dropdown Field Type

[](#creating-a-dependent-dropdown-field-type)

Scenario: You want a `City` field that updates its options based on a `Country` field.

**1. Create the Field Type Class**

Create `app/CustomFields/Types/DependentSelectField.php`. We will use the `options` array to store the "parent field" slug.

```
namespace App\CustomFields\Types;

use Salah\LaravelCustomFields\FieldTypes\FieldType;

class DependentSelectField extends FieldType
{
    public function name(): string { return 'dependent_select'; }
    public function label(): string { return 'Dependent Select'; }
    public function htmlTag(): string { return 'select'; }

    // We expect 'options' to contain the slug of the parent field
    // e.g., options: ["country"]
    public function hasOptions(): bool { return true; }

    public function description(): string {
        return 'A select menu that depends on another field.';
    }

    public function baseRule(): array {
        return ['string']; // Basic validation
    }

    public function view(): string {
        return 'components.custom-fields.dependent-select';
    }
}
```

**2. Register the Type**

In `AppServiceProvider::boot()`:

```
use Salah\LaravelCustomFields\FieldTypeRegistry;
use App\CustomFields\Types\DependentSelectField;

public function boot() {
    app(FieldTypeRegistry::class)->register(new DependentSelectField());
}
```

**3. Frontend Implementation**

Since the dependency logic is frontend-heavy, your component (`resources/views/components/custom-fields/dependent-select.blade.php`) should listen to the parent field.

```
@props(['field', 'value', 'allFields'])

@php
    $parentSlug = $field->options[0] ?? null;
@endphp

        Select Option

```

---

📄 License
---------

[](#-license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance89

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 57.8% 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 ~6 days

Total

7

Last Release

54d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/26fa18aeefe5f9957e6477dc4c6eaa8cd381ba8961d5faa5a2a0c5ad778474a2?d=identicon)[salaheldeen911](/maintainers/salaheldeen911)

---

Top Contributors

[![freekmurze](https://avatars.githubusercontent.com/u/483853?v=4)](https://github.com/freekmurze "freekmurze (378 commits)")[![mvdnbrk](https://avatars.githubusercontent.com/u/802681?v=4)](https://github.com/mvdnbrk "mvdnbrk (46 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (29 commits)")[![salaheldeen911](https://avatars.githubusercontent.com/u/68305647?v=4)](https://github.com/salaheldeen911 "salaheldeen911 (23 commits)")[![Nielsvanpach](https://avatars.githubusercontent.com/u/10651054?v=4)](https://github.com/Nielsvanpach "Nielsvanpach (23 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (20 commits)")[![pforret](https://avatars.githubusercontent.com/u/474312?v=4)](https://github.com/pforret "pforret (16 commits)")[![sebastiandedeyne](https://avatars.githubusercontent.com/u/1561079?v=4)](https://github.com/sebastiandedeyne "sebastiandedeyne (14 commits)")[![AlexVanderbist](https://avatars.githubusercontent.com/u/6287961?v=4)](https://github.com/AlexVanderbist "AlexVanderbist (12 commits)")[![riasvdv](https://avatars.githubusercontent.com/u/3626559?v=4)](https://github.com/riasvdv "riasvdv (10 commits)")[![patinthehat](https://avatars.githubusercontent.com/u/5508707?v=4)](https://github.com/patinthehat "patinthehat (10 commits)")[![AdrianMrn](https://avatars.githubusercontent.com/u/12762044?v=4)](https://github.com/AdrianMrn "AdrianMrn (8 commits)")[![crynobone](https://avatars.githubusercontent.com/u/172966?v=4)](https://github.com/crynobone "crynobone (8 commits)")[![mghrby](https://avatars.githubusercontent.com/u/25436794?v=4)](https://github.com/mghrby "mghrby (7 commits)")[![thecaliskan](https://avatars.githubusercontent.com/u/13554944?v=4)](https://github.com/thecaliskan "thecaliskan (5 commits)")[![irfanm96](https://avatars.githubusercontent.com/u/42065936?v=4)](https://github.com/irfanm96 "irfanm96 (5 commits)")[![IGedeon](https://avatars.githubusercontent.com/u/694313?v=4)](https://github.com/IGedeon "IGedeon (4 commits)")[![yaroslawww](https://avatars.githubusercontent.com/u/23663794?v=4)](https://github.com/yaroslawww "yaroslawww (3 commits)")[![jessarcher](https://avatars.githubusercontent.com/u/4977161?v=4)](https://github.com/jessarcher "jessarcher (3 commits)")[![koossaayy](https://avatars.githubusercontent.com/u/6431084?v=4)](https://github.com/koossaayy "koossaayy (3 commits)")

---

Tags

laravellaravel-custom-fieldsCustom Fields Lab

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

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

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

###  Alternatives

[spatie/laravel-data

Create unified resources and data transfer objects

1.7k28.9M626](/packages/spatie-laravel-data)[hirethunk/verbs

An event sourcing package that feels nice.

513162.9k6](/packages/hirethunk-verbs)[worksome/exchange

Check Exchange Rates for any currency in Laravel.

123544.7k](/packages/worksome-exchange)[ralphjsmit/livewire-urls

Get the previous and current url in Livewire.

82270.3k4](/packages/ralphjsmit-livewire-urls)[hydrat/filament-table-layout-toggle

Filament plugin adding a toggle button to tables, allowing user to switch between Grid and Table layouts.

6292.3k1](/packages/hydrat-filament-table-layout-toggle)[ralphjsmit/laravel-helpers

A package containing handy helpers for your Laravel-application.

13704.6k2](/packages/ralphjsmit-laravel-helpers)

PHPackages © 2026

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