Extend and customize BetterAuth entities for your application.
| Method | Use Case | Complexity |
|---|---|---|
| Auto-generate with setup-features | Add features (Magic Link, 2FA, etc.) | Very Low ⭐ |
| Use UserProfileTrait | Include optional fields (name, avatar) | Very Low |
| Exclude optional fields | Minimal User entity | Very Low |
| Extend entities | Add custom fields | Low |
| Migration only | Simple field additions | Very Low |
| Complete custom | Full control | High |
⭐ Recommended: Use
better-auth:setup-featuresto automatically generate entities for Magic Link, 2FA, OAuth, and other features. See Installation Guide for details.
The easiest way to add entities for features like Magic Link, 2FA, OAuth, etc.:
# Enable Magic Link - automatically generates MagicLinkToken entity
php bin/console better-auth:setup-features --enable=magic_link --migrate
# Enable 2FA - automatically generates TotpData entity
php bin/console better-auth:setup-features --enable=two_factor --migrate
# Enable multiple features at once
php bin/console better-auth:setup-features --enable=magic_link --enable=two_factor --migrate
This command will:
config/packages/better_auth.yaml--with-controllers)--migrate)Available entities that can be auto-generated:
| Entity | Feature | Command |
|---|---|---|
MagicLinkToken |
Magic Link | --enable=magic_link |
TotpData |
Two-Factor Auth | --enable=two_factor |
EmailVerificationToken |
Email Verification | --enable=email_verification |
PasswordResetToken |
Password Reset | --enable=password_reset |
Device |
Device Tracking | --enable=device_tracking |
SecurityEvent |
Security Monitoring | --enable=security_monitoring |
GuestSession |
Guest Sessions | --enable=guest_sessions |
Organization |
Multi-Tenant | --enable=multi_tenant |
OrganizationMember |
Multi-Tenant | --enable=multi_tenant |
See Installation Guide - Setup Features for complete documentation.
BetterAuth provides optional profile fields (name, avatar) that can be included or excluded based on your needs.
# Include all fields (default)
php bin/console better-auth:install --id-strategy=uuid --mode=api
# Exclude all optional fields (minimal)
php bin/console better-auth:install --id-strategy=uuid --mode=api --minimal
# Exclude specific fields
php bin/console better-auth:install --id-strategy=uuid --mode=api --exclude-fields=avatar
Use the better-auth:user-fields command to add or remove fields:
# Add fields
php bin/console better-auth:user-fields add name
php bin/console better-auth:user-fields add name,avatar
# Remove fields (WARNING: data loss after migration!)
php bin/console better-auth:user-fields remove avatar
php bin/console better-auth:user-fields remove name,avatar --force
# Generate and run migration
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
If you want profile fields, your User entity can use the UserProfileTrait:
<?php
// src/Entity/User.php
namespace App\Entity;
use BetterAuth\Symfony\Model\User as BaseUser;
use BetterAuth\Symfony\Model\UserProfileTrait;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User extends BaseUser
{
use UserProfileTrait; // Adds name and avatar fields
#[ORM\Id]
#[ORM\Column(type: 'string', length: 36)]
protected string $id;
// Your custom fields...
}
For authentication-only use cases:
<?php
// src/Entity/User.php
namespace App\Entity;
use BetterAuth\Symfony\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User extends BaseUser
{
// No UserProfileTrait = no name/avatar fields
#[ORM\Id]
#[ORM\Column(type: 'string', length: 36)]
protected string $id;
// Essential fields only: email, password, roles, emailVerified, timestamps
}
Create your own entity classes that extend BetterAuth base entities.
<?php
// src/Entity/User.php
namespace App\Entity;
use BetterAuth\Core\Entities\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User extends BaseUser
{
#[ORM\Column(type: 'string', length: 20, nullable: true)]
private ?string $phone = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $company = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $avatar = null;
#[ORM\Column(type: 'json', nullable: true)]
private ?array $preferences = null;
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(?string $phone): self
{
$this->phone = $phone;
return $this;
}
public function getCompany(): ?string
{
return $this->company;
}
public function setCompany(?string $company): self
{
$this->company = $company;
return $this;
}
public function getAvatar(): ?string
{
return $this->avatar;
}
public function setAvatar(?string $avatar): self
{
$this->avatar = $avatar;
return $this;
}
public function getPreferences(): ?array
{
return $this->preferences;
}
public function setPreferences(?array $preferences): self
{
$this->preferences = $preferences;
return $this;
}
}
<?php
// src/Entity/Session.php
namespace App\Entity;
use BetterAuth\Core\Entities\Session as BaseSession;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'sessions')]
class Session extends BaseSession
{
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $deviceFingerprint = null;
public function getDeviceFingerprint(): ?string
{
return $this->deviceFingerprint;
}
public function setDeviceFingerprint(?string $deviceFingerprint): self
{
$this->deviceFingerprint = $deviceFingerprint;
return $this;
}
}
Tell BetterAuth to use your custom entities:
# config/services_betterauth.yaml
services:
BetterAuth\Symfony\Storage\Doctrine\DoctrineUserRepository:
arguments:
$userClass: 'App\Entity\User'
BetterAuth\Symfony\Storage\Doctrine\DoctrineSessionRepository:
arguments:
$sessionClass: 'App\Entity\Session'
BetterAuth\Symfony\Storage\Doctrine\DoctrineRefreshTokenRepository:
arguments:
$refreshTokenClass: 'App\Entity\RefreshToken'
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Add custom fields without custom entity classes:
php bin/console make:migration
Edit the generated migration:
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD phone VARCHAR(20) DEFAULT NULL');
$this->addSql('ALTER TABLE users ADD company VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE users ADD avatar VARCHAR(255) DEFAULT NULL');
}
For full control, create completely custom entities:
<?php
// src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\Column(type: 'uuid')]
private Uuid $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
private string $email;
#[ORM\Column(type: 'string')]
private string $password;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $name = null;
#[ORM\Column(type: 'boolean')]
private bool $emailVerified = false;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
// Add all your custom fields...
public function __construct()
{
$this->id = Uuid::v7();
$this->createdAt = new \DateTimeImmutable();
}
// Implement all getters/setters...
}
Then implement the repository interface:
<?php
// src/Repository/UserRepository.php
namespace App\Repository;
use BetterAuth\Core\Interfaces\UserRepositoryInterface;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class UserRepository extends ServiceEntityRepository implements UserRepositoryInterface
{
public function findByEmail(string $email): ?User
{
return $this->findOneBy(['email' => $email]);
}
public function findById(string $id): ?User
{
return $this->find($id);
}
// Implement all interface methods...
}
// src/Controller/AuthController.php
private function formatUser(User $user): array
{
return [
'id' => $user->getId(),
'email' => $user->getEmail(),
'name' => $user->getName(),
'emailVerified' => $user->isEmailVerified(),
// Custom fields
'phone' => $user->getPhone(),
'company' => $user->getCompany(),
'avatar' => $user->getAvatar(),
'preferences' => $user->getPreferences(),
];
}
#[Route('/auth/register', methods: ['POST'])]
public function register(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
$additionalData = [
'name' => $data['name'] ?? null,
'phone' => $data['phone'] ?? null,
'company' => $data['company'] ?? null,
];
$user = $this->authManager->signUp(
$data['email'],
$data['password'],
$additionalData
);
// ...
}
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $avatar = null;
public function getAvatarUrl(): ?string
{
if (!$this->avatar) {
return null;
}
return '/uploads/avatars/' . $this->avatar;
}
#[ORM\Column(type: 'json')]
private array $roles = ['ROLE_USER'];
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER'; // Ensure ROLE_USER
return array_unique($roles);
}
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $lastLoginAt = null;
public function updateLastLogin(): void
{
$this->lastLoginAt = new \DateTimeImmutable();
}
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $deletedAt = null;
public function isDeleted(): bool
{
return $this->deletedAt !== null;
}
public function delete(): void
{
$this->deletedAt = new \DateTimeImmutable();
}
How can I help you explore Laravel packages today?