Skip to main content

Command reference

The install command​

php artisan email-magic-link:install

Publishes the configuration and prints the setup steps.

OptionEffect
--forceOverwrite files that were already published
--viewsAlso 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
TagPublishes to
email-magic-linkevery group below, in one command
email-magic-link-configconfig/email-magic-link.php
email-magic-link-migrationsdatabase/migrations
email-magic-link-viewsresources/views/vendor/email-magic-link
email-magic-link-langlang/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.schedule you asked for that is not registered, or, with the switch off, no entry in your scheduler that runs email-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 database store 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 SQLSTATE 25P02 while another one holds the lock; see What the cache instance has to survive.
  • Fallback: an app.fallback_locale the 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 .env file, so the command then reads only the process environment and says so; run php artisan config:clear first to check .env as well.
  • Remember: with remember.enabled on, a guard whose user table has no remember_token column, 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 neither eloquent nor database, 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.