DEV Community

Cover image for How to Customize User Model in Laravel
Kristina
Kristina

Posted on • Originally published at craftable.pro

How to Customize User Model in Laravel

Packages are the cornerstone of PHP and Laravel development. Without them, we would have to reinvent (or copy&paste) the wheel for every single project. However, a package may not fully meet all your needs, so it's important that they are well-built and easily customizable.

One of the very well-known and one of our favorite packages is the Laravel-medialibrary from Spatie. The main model the package works with is called Media. 90% of the time, it fulfills all of our project’s needs, but for the remaining 10% we need to extend or tweak its functionality and that’s where the “swappability” feature comes into play.

If you are interested in how it’s implemented, feel free to head to its source code and browse. We will mention a simplified version of how we did it for our package further in this article.


Craftable PRO is an advanced Laravel admin panel and CRUD generator that goes beyond simple roles and permissions management. With Craftable PRO, you can effortlessly generate a fully functional admin panel based on your database structure. Additionally, it streamlines media management, facilitates translation handling, and offers various other powerful features. Simplify the development process of your entire Laravel backend with Craftable PRO.


One of the most important out-of-the-box models in Craftable PRO is definitely the CraftableProUser, which is the default authenticatable model. In the beginnings of the package history, however, it used to reside inside the vendor package and be hard-coded in many places of the codebase - and attempting to extend its functionality or swap it for a different model would have proved to be a great challenge.

We have implemented this feature recently and it turned out to be easier than expected.

The gist of it is as follows:

1. Add an attribute to config for the model’s class name:

'craftable_pro_user_model' => Brackets\CraftablePro\Models\CraftableProUser::class
Enter fullscreen mode Exit fullscreen mode

2. Create a base model which contains only the necessary code without which the package would not function:

<?php

namespace Brackets\CraftablePro\Models;

use Brackets\CraftablePro\Helpers\Initials;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class BaseCraftableProUser extends Authenticatable
{
    use HasRoles;

    protected $guard = 'craftable-pro';

    protected $fillable = [
        'first_name',
        'last_name',
        'email',
    ];

    protected function initials(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => Initials::new()->generate($this->first_name . ' ' . $this->last_name)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Replace all hard-coded mentions of the model with the config value. Instead of:

$user = CraftableProUser::updateOrCreate([
  'email' => $email,
], [
  'first_name' => 'Administrator',
]
Enter fullscreen mode Exit fullscreen mode

do:

$user = config('craftable-pro.craftable_pro_user_model')::updateOrCreate([
  'email' => $email,
], [
  'first_name' => 'Administrator',
]
Enter fullscreen mode Exit fullscreen mode

Now, let’s go and see how this new feature can be configured and used within Craftable Pro. For a quicker overview, the official documentation is located here: Users – Craftable PRO Documentation

Task: add a full_name attribute and a teams relationship to the authenticatable user model without having to rewrite any package code.

Step 1. Create a new Eloquent model extending the base class

<?php

namespace App\Models;

use Brackets\CraftablePro\Models\BaseCraftableProUser;

class AdminUser extends BaseCraftableProUser
{
    protected $table = 'craftable_pro_users';
}
Enter fullscreen mode Exit fullscreen mode

It can also be called the same as the original CraftableProUser model, since it is in a different namespace. Despite that, we will call it AdminUser for clarity.

If you want to keep using the underlying craftable_pro_users database table, you have to specify it because it does not get inherited from the base model.

Step 2. Register the new model in the config/craftable-pro.php file

If you don’t see the config/craftable-pro.php file, it first has to be published through the php artisan vendor:publish --tag=craftable-pro-config command.

return [
    /*
     * The fully qualified class name of the Craftable Pro user model.
     */
    'craftable_pro_user_model' => App\Models\AdminUser::class,
    ...
];
Enter fullscreen mode Exit fullscreen mode

Step 3. Configure the guards and auth providers in config/auth.php file:

Unfortunately (but understandably) there is no way to use a config value from one config file within another config file, so we have to specify the new model here as well.

use App\Models\AdminUser;

return [
  'providers' => [
        'craftable-pro-users' => [
            'driver' => 'eloquent',
            'model' => MyCraftableProUser::class,
        ],
  ],
  'guards' => [
        'craftable-pro' => [
            'driver' => 'session',
            'provider' => 'craftable-pro-users',
        ],
  ],
  ...
]
Enter fullscreen mode Exit fullscreen mode

Step 4. Customize the new AdminUser model however you like

Add attributes, relationships, appends, eager loading… - whatever you want.

<?php

namespace App\Models;

use Brackets\CraftablePro\Models\BaseCraftableProUser;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class AdminUser extends BaseCraftableProUser
{
    protected $table = 'craftable_pro_users';

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->appends = [...parent::getAppends(), 'full_name'];
        $this->with = ['teams'];
    }

    public function getFullNameAttribute(): string
    {
        return $this->first_name . ' ' . $this->last_name;
    }

    public function teams(): BelongsToMany
    {
        return $this->belongsToMany(Team::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5a. Update data in the database where necessary, mainly in polymorphic relationships:

Database OLD

to

Database NEW

Step 5b. If you are already using Laravel’s morph map, update the model class name instead:

Relation::morphMap([
  'CraftableProUser' => CraftableProUser::class
]);
Enter fullscreen mode Exit fullscreen mode

to

Relation::morphMap([
  'CraftableProUser' => App\Models\AdminUser::class
]);
Enter fullscreen mode Exit fullscreen mode

Customizing the user listing

To customize the user listing (Access tab), it is necessary to first copy the vue code from vendor/brackets/craftable-pro/resources/js/Pages/CraftableProUser/Index.vue and replace with it the code in resources/js/craftable-pro/Pages/CraftableProUser/Index.vue as the originally published resources/js/craftable-pro/Pages/CraftableProUser/Index.vuefile is only a stub that links to the vendor file.

Let’s replace the name with the new full_name attribute:

<ListingDataCell>
    <div class="flex items-center">
        <Avatar
            :src="item.avatar_url"
            :name="`${item.first_name} ${item.last_name}`"
        />
        <div class="ml-4">
            <div class="font-medium text-gray-900">
                <!-- TODO: maybe have full_name attribute? -->
                {{ item.first_name }} {{ item.last_name }}
            </div>
            <div class="text-gray-500">{{ item.email }}</div>
        </div>
    </div>
</ListingDataCell>
Enter fullscreen mode Exit fullscreen mode

This code can now be refactored as such:

<ListingDataCell>
    <div class="flex items-center">
        <Avatar
            :src="item.avatar_url"
            :name="item.full_name"
        />
        <div class="ml-4">
            <div class="font-medium text-gray-900">
                {{ item.full_name }}
            </div>
            <div class="text-gray-500">{{ item.email }}</div>
        </div>
    </div>
</ListingDataCell>
Enter fullscreen mode Exit fullscreen mode

Don’t forget to run the npm run craftable-pro:build command and you are done!

In summary, packages are vital components in PHP and Laravel development, streamlining processes and saving valuable time by providing pre-built functionalities.

The recent integration of this feature into Craftable PRO, particularly with the CraftableProUser model, highlights the importance of well-designed and adaptable packages. What was once a complex task has now become manageable, thanks to thoughtful design and implementation.

Top comments (0)