Elegant, customizable public profiles for Laravel applications.
Laravel Persona gives an Eloquent user model profile pages, unique public usernames, display names, headlines, mottos, biographies, avatars, banners, social links, custom links, visibility controls, publishing, view tracking, profile comments, completeness scoring, badges, and convenient model helpers.
$profile = $user->createPersona([
'display_name' => 'Nick',
'headline' => 'Laravel Package Builder',
'motto' => 'Build useful things.',
'bio' => 'Building useful Laravel packages.',
'location' => 'Kansas',
'is_public' => true,
'published_at' => now(),
]);
$url = $user->personaUrl();
$score = $user->personaCompletenessScore();| Package version | PHP | Laravel / Illuminate |
|---|---|---|
| Current | ^8.2 |
^12.0 || ^13.0 |
Composer resolves the compatible Illuminate packages for the consuming Laravel application.
Install Persona:
composer require eloquent-works/personaPublish the configuration and migrations:
php artisan persona:installRun the migrations:
php artisan migrateAdd HasPersona to the application user model:
<?php
namespace App\Models;
use EloquentWorks\Persona\Traits\HasPersona;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasPersona;
}See Installation for publishing options and setup guidance.
- Public or private user profiles
- Slug-based public usernames and route model binding
- Configurable username-change tokens
- Reserved-name, format, length, and uniqueness checks
- Display names, headlines, mottos, biographies, and locations
- Avatar and banner URLs through Laravel filesystems
- Website URLs, social links, and custom links
- Publishing and configurable visibility requirements
- Public, published, and visible query scopes
- Profile view counters
- Profile comments with replies, approval, pinning, editing, and deletion
- Profile completeness scoring
- Custom profile badge awarding
- Lifecycle events for creation, updates, publishing, unpublishing, and views
- Publishable views and customizable public routes
- Configurable models, tables, field limits, feature flags, and storage
- PHPUnit, PHPStan/Larastan, Laravel Pint, and Composer quality scripts
$profile = $user->createPersona([
'display_name' => 'Nick',
'headline' => 'Laravel Package Builder',
'motto' => 'Build useful things.',
'bio' => 'Building useful Laravel packages.',
'location' => 'Kansas',
'website_url' => 'https://example.com',
'is_public' => true,
'published_at' => now(),
]);$profile = $user->persona;
$user->hasPersona();
$user->updatePersona([
'headline' => 'Open-source Laravel Developer',
]);Persona does not register public routes automatically.
use Illuminate\Support\Facades\Route;
Route::persona();The default route format is:
/@{persona}
$profile->url();
$profile->avatarUrl();
$profile->bannerUrl();
$user->personaUrl();$profile->isVisible();
$publicProfiles = Persona::public()->get();
$publishedProfiles = Persona::published()->get();
$visibleProfiles = Persona::visible()->get();Visibility follows:
config('persona.visibility.require_published_at');When require_published_at is disabled, a public profile may be visible without a publication timestamp. When enabled, the profile must be public and have a past published_at value.
By default, Persona preserves its historical behavior and returns 404 for
profiles that are not publicly visible:
'visibility' => [
'private_profile_response' => '404',
],Applications may instead render a privacy-safe placeholder:
'visibility' => [
'private_profile_response' => 'view',
],The default private view displays:
This profile is private. This user has chosen to keep their profile private.
The private placeholder intentionally receives no Persona model and no user model. This prevents customized placeholder views from accidentally exposing private profile fields.
Customize the view with:
'views' => [
'private' => 'profiles.private',
],Authenticated profile owners may view their own private profile by default. Disable that behavior with:
'visibility' => [
'owner_can_view_private' => false,
],Persona uses the profile slug as its public username.
$profile->usernameTokens();
$profile->canChangeUsername();
$profile->nextUsernameTokenAt();
$profile->usernameIsAvailable('signal-nick');
$profile->changeUsername('signal-nick');Use the helpers on the user model:
$user->personaUsernameTokens();
$user->canChangePersonaUsername();
$user->changePersonaUsername('signal-nick');An administrative change can skip token spending:
$profile->changeUsername(
'signal-nick',
spendToken: false,
);Persona normalizes the username and applies the configured length, regular expression, reserved-name, uniqueness, and token rules.
Refresh the profile's completeness score directly:
$score = $profile->refreshCompleteness();Or through the user model:
$score = $user->personaCompletenessScore();The user helper returns 0 when no Persona profile exists.
Award a badge directly through the profile:
$badge = $profile->awardBadge(
'package-builder',
[
'label' => 'Package Builder',
'description' => 'Published a Laravel package.',
],
$user,
);Or use the user-model helper:
$badge = $user->awardPersonaBadge(
'package-builder',
[
'label' => 'Package Builder',
],
);The helper returns null when the user does not have a Persona profile.
$comment = $profile->addComment(
$user,
'Great profile.',
);
$reply = $comment->addReply(
$otherUser,
'Thank you.',
);Moderate and edit comments:
$comment->approve();
$comment->unapprove();
$comment->pin();
$comment->unpin();
$comment->edit('Updated comment.');
$comment->delete();Retrieve common comment groups:
$profile->approvedComments()->get();
$profile->pinnedComments()->get();
PersonaComment::topLevel()->approved()->get();
PersonaComment::repliesOnly()->get();
PersonaComment::pinned()->get();Persona provides the model API. The consuming application remains responsible for routes, request validation, authorization, rate limiting, spam controls, and guest identity handling.
See Profile Comments.
Persona can dispatch:
PersonaCreatedPersonaUpdatedPersonaPublishedPersonaUnpublishedPersonaViewed
Disable lifecycle events globally:
'dispatch_events' => false,See Events.
Publish the configuration file:
php artisan vendor:publish --tag=persona-configMajor configuration groups include:
return [
'tables' => [],
'models' => [],
'routes' => [],
'usernames' => [],
'views' => [],
'storage' => [],
'slugs' => [],
'fields' => [],
'comments' => [],
'visibility' => [],
'links' => [],
'features' => [],
'dispatch_events' => true,
];See Configuration for the complete reference.
Run all package checks:
composer qualityOr run them separately:
composer format
composer format:test
composer analyse
composer testValidate Composer metadata before a release:
composer validate --strictThe quality pipeline should complete with zero formatting, PHPStan, or PHPUnit failures.
See Testing and Quality.
- Documentation Index
- Installation
- Configuration
- Usage
- Completeness and Badges
- Profile Comments
- Routes
- Events
- Customization
- Security
- Testing and Quality
Treat every public profile field, URL, link, comment, metadata value, and badge attribute as user-generated or administrator-generated content.
Validate URLs, escape rendered values, authorize profile and comment changes, rate-limit public write endpoints, and avoid exposing private profiles through search, sitemaps, APIs, or public routes.
Security vulnerabilities should be reported privately according to SECURITY.md.
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Built by Eloquent Works.
Laravel Persona is open-source software licensed under the MIT License.