Installation
Requirements
| Component | Constraint |
|---|---|
| PHP | ^8.4 (8.4 and 8.5) |
| Laravel | ^13.0 |
| Laravel Fortify | ^1.0 — optional, only for the two-factor handoff |
The package requires laravel/framework (for the FormRequest base it validates with) and
adds no third-party runtime dependencies. Fortify is a suggested dependency: the core
never references a Fortify symbol unless Fortify is installed and the bridge is enabled.
Install the package
composer require pushery/email-magic-link-for-laravel
Then run the installer to publish the configuration and print the next steps:
php artisan email-magic-link:install
Now run your migrations to create the token table. The migration ships with the package and is loaded automatically — you do not need to publish it first:
php artisan migrate
Add --views to the installer to also publish the Blade views. Prefer to do it by hand? The
individual publish tags are still available:
php artisan vendor:publish --tag=email-magic-link # everything at once
php artisan vendor:publish --tag=email-magic-link-config
php artisan vendor:publish --tag=email-magic-link-migrations
php artisan vendor:publish --tag=email-magic-link-views
php artisan vendor:publish --tag=email-magic-link-lang
Publish the migrations only if you need to change them. The package loads its bundled
migrations itself, and the published copies get a fresh date prefix, so both would be pending
for the same tables; once a published *_create_magic_link_tokens_table.php exists the package
stops loading its own. If you rename that file, say so explicitly by calling
EmailMagicLinkServiceProvider::ignoreMigrations() in a service provider's register().
The command's full option list is in the command reference.
Run a queue worker
The magic-link email is dispatched to the queue. This is deliberate: the request that issues a link returns in constant time and never blocks on the mailer, so it cannot reveal whether an account exists. The trade-off is that the mail is only actually sent once a queue worker processes the job, so a worker has to be running:
php artisan queue:work
If nothing is consuming the queue, the email never leaves — the request still succeeds, but
the job just sits there. In production, keep a worker (or Horizon or Supervisor) running.
For local development you can send mail synchronously instead by setting
QUEUE_CONNECTION=sync in your .env.
Next
- Quick start — the routes you just got, and the flow a user walks through.
- Configuration — the config file end to end.
- Keeping the token table small — schedule the purge command.