Command reference
The install command
php artisan email-magic-link:install
Publishes the configuration and prints the setup steps.
| Option | Effect |
|---|---|
--force | Overwrite files that were already published |
--views | Also publish the Blade views for customizing |
The migrations are loaded automatically, so a fresh application works without publishing
anything; this command is a convenience for customizing the config or the views. The
individual publish tags stay available. Publishing the migrations turns the bundled ones
off: once a published *_create_magic_link_tokens_table.php exists the package no longer
loads its own copies, and a host that renames the file calls
EmailMagicLinkServiceProvider::ignoreMigrations(tablesExist: true) in a service provider's
register(), so a scheduled purge keeps running.
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
| Tag | Publishes to |
|---|---|
email-magic-link | every group below, in one command |
email-magic-link-config | config/email-magic-link.php |
email-magic-link-migrations | database/migrations |
email-magic-link-views | resources/views/vendor/email-magic-link |
email-magic-link-lang | lang/vendor/email-magic-link |
The purge command
php artisan email-magic-link:purge
Deletes magic-link tokens that are expired or already consumed, and reports how many rows it removed. Safe to run at any cadence.
It declares no options of its own. It does implement Laravel's Isolatable, so --isolated
works and is what the token cleanup guide schedules with -- it
takes an atomic lock for the run, so an overlapping schedule tick exits rather than purging
twice against the same rows.
When invitations are switched on it purges those too, in the same run, and reports them on a second line. That is deliberate: one command to schedule rather than two, and one to forget. An installation without invitations sees exactly the output it always saw, because the second line only appears when the feature is on.
The package can register the schedule for you. It is off by default — a package that deletes rows on a cadence nobody chose is making your decision, and an application that already wires the command itself would end up running it twice:
// config/email-magic-link.php
'prune' => [
'schedule' => true,
'frequency' => 'daily', // hourly · daily · weekly · monthly
],
The registered entry uses withoutOverlapping(), so a purge that outlives its own interval on
a large table cannot stack. If you would rather keep the schedule in your own file, leave the
switch off and write it there:
use Illuminate\Support\Facades\Schedule;
Schedule::command('email-magic-link:purge')->daily();
It delegates to TokenStore::purge(), so a custom store implements the deletion itself. Why
the rows survive consumption in the first place is explained in
Keeping the token table small.
The doctor command
php artisan email-magic-link:doctor
Compares your published config/email-magic-link.php against the one this version ships, and
names every key your file does not mention.
Publishing the config freezes the file at that version. The package still merges its own defaults underneath it, so every key keeps working — but your file never mentions them, and a setting you cannot see is a setting you cannot tune. The gap grows quietly with every upgrade: one consuming application was found running the resend guard, a security-relevant subsystem, on defaults its configuration file had never heard of.
The command reports the drift first:
- Keys this version ships that your file lacks, with the value currently in force. They need no action — add the ones you want to tune, ignore the rest.
- Keys in your file this version does not know. Either they were removed in an upgrade, or the key is a typo — and a typo reads exactly like a setting that stopped working, so both are worth a look.
Then it checks the running application. The first two lines always appear, the others only when there is something to fix:
- CSP nonce: where the screens get their nonce from, or that no source was found.
- Link origin: whether the host in an emailed link is forced, restricted by
TrustHosts, or taken from the request as it came in. - Purge: a
prune.scheduleyou asked for that is not registered, or, with the switch off, no entry in your scheduler that runsemail-magic-link:purge, so no spent or expired token is ever deleted. A purge you run outside Laravel's scheduler, from a cron line or a job, is invisible to this check. - Issue lock: a lock store that is not defined, or the
databasestore on a PostgreSQL connection the package's tables use or that is your application's default. An issue inside a transaction of yours then fails with SQLSTATE25P02while another one holds the lock; see What the cache instance has to survive. - Fallback: an
app.fallback_localethe package has no strings for, or an empty one, which leaves no fallback at all. A visitor whose own locale has no bundle either then sees every text as its key; see Why the identical-looking bundles exist. - Environment: an
EMAIL_MAGIC_LINK_*number that is set and is not a number of at least 1, with the value it found and the one the shipped config keeps instead. Such a value never switches a limit off, and that is exactly why nothing else would tell you it was not applied. A cached configuration loads no.envfile, so the command then reads only the process environment and says so; runphp artisan config:clearfirst to check.envas well. - Remember: with
remember.enabledon, a guard whose user table has noremember_tokencolumn, where a ticked "Stay signed in" fails the sign-in after the link or code is spent, and a user model that names no remember token column, where the box keeps nobody signed in. A guard whose provider is neithereloquentnordatabase, or whose table it cannot find or read, is named as not checked; see Staying signed in.
It exits 0 either way. A drifted config is a thing to read, not a thing to fail a deploy on.