cleaniquecoders/profile
Laravel package for adding and managing user profiles. It provides profile models, migrations, routes, and views so applications can store and maintain user profile data with less boilerplate, integrating cleanly with existing Laravel user models.
The package provides a polymorphic profile record (Profile) that can be attached to any model via a morphMany relationship. It stores basic profile fields (name, email, phone, address, etc.) against a nullable profileable morph.
composer install cleaniquecoders/profile
profiles table):php artisan vendor:publish --tag="profile-migrations"
php artisan migrate
HasProfile trait or expects you to add the morph relation:use Cleaniquecoders\Profile\Traits\HasProfile;
class User extends Authenticatable
{
use HasProfile;
}
src/Models/Profile.php, the trait, and the migration to confirm fillable columns and method names. The README and src/ are your ground truth since the API is minimal.First use case: creating a profile on user registration.
$user = User::create([...]);
$user->profile()->create([
'full_name' => 'Jane Doe',
'email' => '[email protected]',
'phone' => '555-1234',
]);
Then resolve it anywhere:
$user->profile->full_name;
Profile creation on registration (observer or event listener). Keep profile creation decoupled from the controller:
// app/Observers/UserObserver.php
public function created(User $user): void
{
$user->profile()->create([
'full_name' => $user->name,
'email' => $user->email,
]);
}
This avoids the "user exists but no profile" trap and keeps registration logic clean.
Update-or-create pattern. Since a model technically can have many profiles (morphMany), guard against duplicates by using updateOrCreate. If you only ever want one profile per model, enforce a unique constraint on (profileable_type, profileable_id) in a follow-up migration:
$user->profile()->updateOrCreate(
['profileable_id' => $user->id],
$request->validated()
);
Fluent accessor / helper method on the host model. Wrap the relation so controllers stay readable:
// in HasProfile trait, or your own trait
public function profileData(): Profile
{
return $this->profile()->firstOrCreate([]);
}
Then ProfileData can lazily build an empty shell, which is useful for admin edit screens where a profile may not exist yet.
Form requests + validation. Put validation rules in a FormRequest keyed to the profile columns. Validate only columns you actually expose; don't mass-assign everything. Then pass $request->validated() straight to updateOrCreate.
Admin resource / Filament integration. Because Profile is a normal Eloquent model with a morph, you can register it directly in Filament/Nova as a related record (e.g., Fieldset or nested RelationManager). Use morphMany handling (ProfileResource related to User) — configure the relationship manager on the parent resource, not the child.
Multiple profileable types. The polymorph benefit is attaching profiles to non-User entities (organizations, teams, contacts). Model these explicitly:
class Organization extends Model { use HasProfile; }
This is the main reason to pick this package over just adding columns to users: you can reuse the exact profile schema across User, Organization, Vendor, etc., with a single migration.
API resources. Use an ApiResource to shape output. Because profile is nullable, guard it:
'profile' => $this->whenLoaded('profile', fn () => new ProfileResource($this->profile)),
Eager-load in list endpoints to avoid N+1: User::with('profile')->paginate().
morphMany means multiple profiles are structurally allowed. The package doesn't enforce one-profile-per-model. If that's a business rule, add a unique index on (profileable_type, profileable_id) and always use updateOrCreate, never create().$user->profile is null until created. Every blade template and accessor must handle null (optional($user->profile)->email). Prefer a firstOrCreate accessor to sidestep this.$fillable / $guarded on the Profile model before passing $request->all(). Publish the model (vendor:publish --tag="profile-models" if supported) so you can extend it with app-specific columns without editing vendor files.vendor/ into database/migrations manually and delete the duplicate from the package. Check src/ — the migration location is usually database/migrations.profiles rather than forking. Keeps package updates clean.whereHasMorph or the profileable_type column. Remember morph types are the full class string — if you refactor a model's namespace, existing rows become stale. Use morph maps (Relation::morphMap([...])) to keep values stable.$user->profile caching bites when you update via $user->profile()->update() (query builder) — the loaded relation is stale. Use $user->profile()->update([...]); $user->refresh(); or set $user->profile->fill(...)->save().with('profile') in index/API endpoints; otherwise each row triggers a separate query.morphMany(...)->cascadeOnDelete() or handle it in a deleting event. Add cascade to avoid orphan rows.src/ as documentation. Read Profile.php and the trait before writing any code — the public API is a handful of methods and fillable fields.composer.json since it's low-activity (14 stars, single maintainer). Review the changelog between updates; a morph schema change can silently break queries.How can I help you explore Laravel packages today?