Free·

User registration with first name or custom fields

Customizing user registration forms to include first name or other fields.

In this article, we will explore how to customize the user registration process to include additional fields such as the user's first name and last name separately. This enhancement can improve user experience and provide more personalized interactions within your application.

Of course, you can adapt this approach to include any other custom fields that are relevant to your application's needs.

The Problem

We need to customize the user registration form to include additional fields, such as first name and last name, and ensure that these fields are properly stored in the database.

The Solution

We will achieve this by following these steps:

  • Modify the User table to include the new fields.
  • Update the registration form to include input fields for first name and last name.
  • Create a Model Attribute to handle the full name.

Let's begin!

First we modify the users table to add first_name and last_name columns.

If you have an existing application with users, you will need to migrate the existing name data into the new columns.

Change in a new application

We change the initial migration file to include the new columns.

database/migrations/0001_01_01_000000_create_users_table.php
// ...
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('first_name');
            $table->string('last_name');
            // ... other columns ...
        });
// ...

Change in an existing application

We need to account for existing users. We will create a new migration to add the new columns, migrate the data, and then drop the old name column.

database/migrations/2025_09_30_160557_add_first_name_and_last_name_to_users_table.php
<?php

use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        // Step 1: Add 'first_name' and 'last_name' columns
        Schema::table('users', function (Blueprint $table) {
            $table->string('first_name')->nullable()->after('id');
            $table->string('last_name')->nullable()->after('first_name');
        });

        // Step 2: Migrate data from 'name' to 'first_name' and 'last_name'
        User::query()
            ->whereNull('first_name')
            ->chunk(100, function (Collection $users) {
                $users->each(function (User $user) {

                    // Use getRawOriginal to avoid any accessors/mutators
                    $name = $user->getRawOriginal('name');

                    $user->update([
                        'first_name' => str($name)->beforeLast(' ')->value(),
                        'last_name' => str($name)->afterLast(' ')->value(),
                    ]);
                });
            });

        // Step 3: Drop the 'name' column
        Schema::table('users', function (Blueprint $table) {
            $table->string('first_name')->nullable(false)->change();
            $table->string('last_name')->nullable(false)->change();
            $table->dropColumn('name');
        });
    }
};

Next steps

Now we update the User factory:

database/factories/UserFactory.php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * The current password being used by the factory.
     */
    protected static ?string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'first_name' => fake()->name(),
            'last_name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

And let's now change the registration form to include the new fields.

For that, we need to create a new Page class (without a view).

mkdir -p app/Filament/Pages/Auth
touch app/Filament/Pages/Auth/Register.php

And we extend the default Register page provided by Filament.

The form attributes will match attributes on the User model.

If your application is multilingual, you may translate the labels using Laravel's localization features.

app/Filament/Pages/Auth/Register.php
<?php

namespace App\Filament\Pages\Auth;

use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;

class Register extends \Filament\Auth\Pages\Register
{
    public function mount(): void
    {
        parent::mount();
    }

    public function form(Schema $schema): Schema
    {
        // If your Application is Multi-lingual, you may translate it like this:
        // Check: https://laravel.com/docs/12.x/localization
        // ->label(__('pages/auth/register.form.first_name'))
        // ->label(__('pages/auth/register.form.last_name'))

        return $schema
            ->components([
                TextInput::make('first_name')
                    ->label('First name')
                    ->required()
                    ->maxLength(255)
                    ->autofocus(),
                TextInput::make('last_name')
                    ->label(__('Last name'))
                    ->required()
                    ->maxLength(255),
                $this->getEmailFormComponent(),
                $this->getPasswordFormComponent(),
                $this->getPasswordConfirmationFormComponent(),
            ]);
    }
}

We now need to tell Filament to use our custom Register page instead of the default one.

We do this by updating the Panel provider.

app/Providers/Filament/AdminPanelProvider.php
<?php

namespace App\Providers\Filament;

use App\Filament\Pages\Auth\Register;
use Filament\Pages\Dashboard;
use Filament\Widgets\AccountWidget;
use Filament\Widgets\FilamentInfoWidget;
use App\Filament\Pages\Auth\Login;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->default()
            ->id('admin')
            ->path('admin')
            ->registration(Register::class)
            ->login(Login::class)
            ->colors([
                'primary' => Color::Amber,
            ])
            ->viteTheme('resources/css/filament/admin/theme.css')
            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
            ->pages([
                Dashboard::class,
            ])
            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
            ->widgets([
                AccountWidget::class,
                FilamentInfoWidget::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ]);
    }
}

Lastly, let's create a name attribute on the User model to easily get the full name of the user.

app/Models/User.php
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    /** @use HasFactory<UserFactory> */
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function name(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->first_name . ' ' . $this->last_name,
        );
    }
}

There you go, enjoy!