Apps (Coming Soon) 25 / 32
Accounts
A drop-in, Clerk-style account area for the TALL stack — profile, avatar, verified email changes, connected accounts, password, two-factor, active devices, and billing — added to any Laravel app with a single Blade component.
devdojo/accounts gives your users a complete account management UI in one line of Blade. Think Clerk's <UserProfile />, rebuilt natively for Tailwind, Alpine, Livewire, and Laravel:
<x-accounts::user-profile />
That single component renders the whole account area: profile details and avatar, verified email changes, connected social accounts, password management, two-factor authentication, a list of active devices, an account-deletion flow, and — when billing is installed — a billing tab. Every section degrades gracefully: anything that depends on a companion package hides itself when that package isn't present.
It sits directly on top of auth, and both are bundled by the Foundation metapackage.
Requirements
- PHP 8.2+ and Laravel 10 – 13.
- Livewire 3/4 (installed automatically as a dependency) and Tailwind CSS in your build.
- Everything else is optional: auth unlocks connected accounts and two-factor, billing unlocks the Billing tab, and
SESSION_DRIVER=databaseunlocks the active-devices list. Sections without their companion hide themselves.
Installation
composer require devdojo/accounts
Add the HasProfile trait to your User model:
use Devdojo\Accounts\Traits\HasProfile;
class User extends Authenticatable
{
use HasProfile;
}
Run the migrations — they add a nullable avatar column (if you don't already have one) and a small profile_email_changes table for pending, unverified email swaps:
php artisan migrate
Migrations load straight from the package (publish them with --tag=accounts:migrations if you'd rather own the files). Every schema change is guarded with hasColumn/hasTable checks, so they're safe to run against an existing users table. A few legacy profile migrations ship alongside — a profile_key_values table plus social_links and privacy_settings JSON columns on users.
Tell Tailwind about the package views so its classes get compiled. For Tailwind v4, add to your CSS entry:
@source "../../vendor/devdojo/accounts/resources/views/**/*.blade.php";
If you store avatars on the default public disk, link storage with php artisan storage:link. Then visit /user/profile — the full account area is already there.
Tip — The fastest path to a complete auth + account experience is to install auth for registration/login/social/2FA, then add accounts. Users can sign up, sign in, and manage everything about their account without you writing a single screen.
Three ways to mount it
The AccountsServiceProvider registers the components as both Livewire components and anonymous Blade components (the accounts namespace), so you can drop the account area in wherever you need it.
As a ready-made page (zero work). The package registers /user/profile with deep-linkable tabs — /user/profile, /user/profile/security, and /user/profile/billing:
// config/devdojo/accounts.php
'routes' => [
'enabled' => true,
'path' => 'account', // now lives at /account
'middleware' => ['web', 'auth'],
'layout' => 'layouts.app', // render inside your own Blade layout, or null for the package's
],
Embedded anywhere. Drop the component into an existing page, optionally opening on a specific tab:
<x-accounts::user-profile />
<x-accounts::user-profile tab="security" />
As a modal. The same component renders on top of everything with mode="modal". Include it once (e.g. in your layout) and open it from anywhere via an Alpine event:
<x-accounts::user-profile mode="modal" />
<button x-data @click="$dispatch('accounts-open-modal')">Account settings</button>
It closes on Esc or backdrop click. <x-accounts::user-profile-modal /> is a shorthand and takes the same tab / :open props.
The user button
The classic avatar dropdown — identity header, "Manage account," your own menu items, and sign out:
<x-accounts::user-button />
<x-accounts::user-button :show-name="true" />
<x-accounts::user-button mode="modal" />
Add custom items via the user_button.menu config key.
Configuration
Publish the config to config/devdojo/accounts.php:
php artisan vendor:publish --tag=accounts:config
The features block toggles each section. Companion-package sections hide themselves automatically when the package isn't installed; these flags let you hide them even when it is.
| Feature | Default | Depends on |
|---|---|---|
avatar |
true |
— |
name |
true |
— |
username |
false |
a username column on users |
email |
true |
— |
connected_accounts |
true |
auth |
password |
true |
— |
two_factor |
true |
auth + enable_2fa |
sessions |
true |
SESSION_DRIVER=database |
delete_account |
true |
— |
billing |
true |
billing |
Other blocks configure the avatar disk/directory/max_kilobytes, email-change code_expires_in and resend_throttle_seconds, the delete_account.redirect, the user-button menu, and custom_pages (below).
Note — The whole package is also gated by the Foundation flag config('foundation.features.accounts', true). If that flag is off, the provider returns early from boot() and registers nothing. The , true default means the package works standalone when no Foundation is installed.
How it's wired
Devdojo\Accounts\Accounts decides what's available. It resolves the tab list and answers capability questions, so the UI never shows a section it can't back:
Accounts::authInstalled()/billingInstalled()— is the companion package present (and, for billing, enabled).Accounts::twoFactorAvailable()— requires thetwo_factorfeature, auth,Google2FA, anddevdojo.auth.settings.enable_2fa.Accounts::connectedAccountsAvailable()— requires auth and at least one active provider fromconfig/devdojo/auth/providers.php.Accounts::sessionsAvailable()— requiressession.driver === 'database'.Accounts::billingTabAvailable()— requires thebillingfeature and the billing package.Accounts::pages()— builds the ordered tab list (Profile, Security, Billing, then custom pages).Accounts::url($tab)— the deep link to the full-page area, ornullwhen routes are disabled.
The Livewire components — UserProfile, UserProfilePage, ProfileDetails, Security, BillingTab, and UserButton — are registered under accounts.* aliases. The routes file registers the accounts.show page route (/{path}/{tab?}) and, when auth and Socialite are present, the accounts.connect / accounts.connect.callback routes that link a provider against the authenticated user.
Avatars and helpers
The HasProfile trait gives every user an avatar and a set of identity helpers. profileAvatarUrl() returns their upload, else a connected social account's avatar, else a deterministically generated initials avatar — no external avatar service involved:
<img src="{{ $user->profileAvatarUrl() }}" class="size-8 rounded-full" />
$user->profileDisplayName(); // "Tony Stark" (name → username → email prefix)
$user->profileInitials(); // "TS"
$user->hasUploadedAvatar(); // bool
$user->updateProfileAvatar($uploadedFile);
$user->removeProfileAvatar();
Companion packages
auth unlocks the Security-tab extras. Connected accounts read the active providers from your auth config; the linking callback must be registered with each OAuth app:
https://your-app.com/user/profile/connect/{provider}/callback
Two-step verification uses the same 2FA columns, encryption format, and recovery codes as auth, so the login challenge works with no extra wiring, and password rules automatically follow your devdojo.auth.settings.password_* policy.
billing adds a Billing tab (plan, status, renewal, customer portal, plans, invoices) with no configuration — it reads everything from the billing package.
Active devices need database sessions:
SESSION_DRIVER=database
The section hides itself for any other driver.
Extension points
Custom pages. Add your own tabs from config — each is deep-linkable, appears in the mobile nav, and works in the modal:
'custom_pages' => [
[
'slug' => 'api-keys', // /user/profile/api-keys
'label' => 'API Keys',
'icon' => '<svg …></svg>',
'view' => 'pages.account.api-keys', // a Blade view…
'component' => null, // …or a Livewire component alias
],
],
For per-user visibility, register pages at runtime from a service provider — the when closure runs on every render:
use Devdojo\Accounts\Accounts;
Accounts::registerPage([
'slug' => 'import',
'label' => 'Import',
'icon' => '<svg …>',
'view' => 'pages.account.import',
'when' => fn () => auth()->user()?->can('import-posts'),
]);
Events. Every meaningful action fires an event with the $user (and a $payload where useful), so you can react without touching the components:
| Event | Fired when |
|---|---|
ProfileUpdated |
Name/username/profile saved. |
AvatarUpdated / AvatarRemoved |
Avatar stored / removed. |
EmailChangeRequested / EmailChanged |
Verification code sent / email swapped. |
PasswordUpdated |
Password set or updated. |
TwoFactorEnabled / RecoveryCodesRegenerated |
TOTP confirmed / backup codes regenerated. |
SessionRevoked |
A device signed out. |
ConnectedAccountDisconnected |
Provider unlinked. |
AccountDeleted |
Fired before the user row is deleted. |
All events live under Devdojo\Accounts\Events. AccountDeleted is your hook to archive posts, cancel subscriptions, and clean up related data before the row disappears.
Theming, views, and i18n. Every significant element carries a stable ac-* class (ac-card, ac-sidebar, ac-nav-item, ac-section, ac-button, ac-otp-digit, …) so you can restyle with plain CSS. Publish the views with accounts:views and the language file with accounts:lang for full control. Dark mode works out of the box via Tailwind's dark: variant.
Security model. Email changes are verified at the new address before the swap; codes are stored hashed, expire, throttle resends, and lock after five failed attempts. Password updates require the current password (when one exists) and can revoke every other session. Deleting an account requires typing the confirmation phrase and the password. A social account can't be disconnected when it's the user's only sign-in method, and the current session can never be revoked from the devices list.