Back to blog

How to Customize User Model in Laravel

Mar 11, 2024
.
Kristína Odziomková

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 (Introduction | laravel-medialibrary ). 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.


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

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)
        );
    }
}

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

$user = CraftableProUser::updateOrCreate([
  'email' => $email,
], [
  'first_name' => 'Administrator',
]

do:

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

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';
}

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,
    ...
];

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',
        ],
  ],
  ...
]

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);
    }
}

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

to

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

Relation::morphMap([
  'CraftableProUser' => CraftableProUser::class
]);

to

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

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.vue file 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>

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>

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.