Skip to main content

Configuration reference

All values live in config/email-magic-link.php. The narrative walkthrough — which decision each key belongs to — is in Configuration.

Channel

KeyDefaultEnvironment variablePurpose
enabledtrueEMAIL_MAGIC_LINK_ENABLEDMaster switch for the channel (routes, notifications, limiters).
mode'link'EMAIL_MAGIC_LINK_MODE'link', 'code', or 'both'.

Token lifetime and redemption

KeyDefaultEnvironment variablePurpose
ttl900EMAIL_MAGIC_LINK_TTLDefault token lifetime in seconds.
link_ttlnullEMAIL_MAGIC_LINK_LINK_TTLLink lifetime in seconds; inherits ttl when unset or non-positive.
code_ttlnullEMAIL_MAGIC_LINK_CODE_TTLCode lifetime in seconds; inherits ttl when unset (handy for a shorter, hand-typed code).
max_uses1Default redemptions per link (1 = single-use). Override per link via issueLink($user, maxUses: N).

One-time codes

KeyDefaultEnvironment variablePurpose
code_length8One-time code length.
code_alphabet'ABCDEFGHJKMNPQRSTUVWXYZ23456789'Alphabet for codes (governs keyspace); omits visually ambiguous characters.
max_attempts_per_token5Hard per-token lockout for code mode.
entropy_safety_factor1_000_000Guardrail bar; cannot be lowered below this floor.

Guards and resolution

KeyDefaultEnvironment variablePurpose
guardapplication defaultEMAIL_MAGIC_LINK_GUARDDefault stateful guard to log into.
guards[]Extra guards a request may select via a guard field.
user_lookupnull (bundled)UserLookup implementation.
eligibilitynull (everyone signs in)SignInEligibility implementation, asked when a credential is issued and again when it is redeemed.
token_storenull (bundled)TokenStore implementation.
captchanullCaptchaGuard implementation, run before any user lookup.
notificationMagicLinkNotification::classNotification class (extend it to customize).

Routing

KeyDefaultEnvironment variablePurpose
routes.prefix''Route prefix.
routes.middleware['web']Route middleware (sessions + CSRF).
routes.redirect_to'/'Fallback redirect after login.
routes.intendedtrueReturn to the originally requested URL after login.
api.enabledfalseDirect JSON token exchange for SPA and mobile clients.
KeyDefaultEnvironment variablePurpose
invalid_response.via'redirect'Browser response for an invalid or expired link: 'redirect', 'view', 'abort', 'json', or a custom InvalidLinkResponder class-string.
invalid_response.view'email-magic-link::invalid'View the 'view' strategy renders (receives a message), answered with abort_status.
invalid_response.redirect_tonullRedirect target for the 'redirect' strategy; null keeps the sign-in form.
invalid_response.abort_status403HTTP status the 'abort' and 'view' strategies answer with.
invalid_response.error_code'invalid_or_expired'Stable JSON error code (JSON clients and the 'json' strategy).

User interface

KeyDefaultEnvironment variablePurpose
ui.mode'auto'EMAIL_MAGIC_LINK_UI'auto' (WireKit views if installed) or 'blade'.
ui.vite['resources/css/app.css']Vite entries the WireKit layout loads; false or [] for a non-Vite host.
ui.styles[]Plain stylesheet URLs to <link> into the WireKit layout.
ui.script_noncenullClass implementing ScriptNonce, supplying the CSP nonce for every tag the bundled screens emit (the countdown script, the inline stylesheet, WireKit's tags). null reads the csp-nonce container binding spatie/laravel-csp registers, then a global csp_nonce().
ui.header_viewnullView rendered above the card on every screen this package draws, in both view trees — a wordmark, a language switcher. A name that resolves to no view is dropped and logged at warning, never thrown.
ui.footer_viewnullThe same slot below the card. Same fallback: an unresolvable name spoils nothing.
ui.html_classnullClass attribute of the WireKit layout's <html> element. WireKit switches its tokens to the dark scheme under .dark, so 'dark' renders the screens dark. null renders no attribute. The plain Blade screens do not read it.

Fortify bridge

KeyDefaultEnvironment variablePurpose
fortify.mode'auto'EMAIL_MAGIC_LINK_FORTIFY'auto' (on if Fortify present), true, or false.
fortify.respect_two_factortrueRoute users with two-factor enabled through the challenge.
fortify.challenge_route'two-factor.login'Fortify challenge route name.

Rate limiting

KeyDefaultEnvironment variablePurpose
limiters.request'email-magic-link:request'Named limiter on the request endpoint; override with RateLimiter::for().
limiters.consume'email-magic-link:consume'Named limiter on the three endpoints that spend a credential.
limiters.invitation_view'email-magic-link:invitation-view'Named limiter on the invitation display page, which spends nothing — its own budget, so viewing an invitation cannot use up the allowance accepting one needs.
limits.request['max' => 5, 'per_minutes' => 1]Defaults the bundled request limiter uses.
limits.consume['max' => 10, 'per_minutes' => 1]Defaults the bundled consume limiter uses.
limits.invitation_view['max' => 30, 'per_minutes' => 1]Defaults the bundled invitation-display limiter uses. Higher than consume: it guards a page load, not a credential being spent.

Token pruning

KeyDefaultEnvironment variablePurpose
prune.schedulefalseRegister the daily purge in your scheduler. Off by default — see the purge command.
prune.frequency'daily'hourly, daily, weekly or monthly. An unrecognized value falls back to daily.
prune.chunk1000Rows one DELETE removes; the purge loops until nothing is left, so this bounds how long a single statement holds its row locks.

Resend guard

KeyDefaultEnvironment variablePurpose
resend.enabledtrueEMAIL_MAGIC_LINK_RESENDThrottling on this package's request endpoint. Keys your own application guards are unaffected.
resend.cooldown.base30First cooldown in seconds after a send.
resend.cooldown.factor2Multiplier applied to each subsequent cooldown.
resend.cooldown.max900Ceiling for the escalating cooldown, in seconds.
resend.window.minutes60Length of the rolling window.
resend.window.max_sends5Maximum sends within that window.
lock_storenullEMAIL_MAGIC_LINK_LOCK_STORECache store the issuance lock uses; null uses the default. Its locks have to really exclude — the null driver implements the lock contract and hands out a lock that always succeeds, so the package refuses it rather than pretending to serialize. Sibling of resend.store: pointing only that one at a lockable store used to fix the resend guard and leave issuance throwing.
lock_block_seconds5EMAIL_MAGIC_LINK_LOCK_BLOCK_SECONDSHow long a second programmatic issuance for the same address waits for the first to finish. It does not apply to the sign-in request endpoint, which never waits: the lock is taken only for an address that resolves to a user, so queueing for it would be a readable answer to whether that account exists. A latency ceiling for the callers it does govern — a waiter holds a worker for this long.
lock_hold_seconds60EMAIL_MAGIC_LINK_LOCK_HOLD_SECONDSThe lock's TTL — how long it survives a process that died holding it. Not the same budget as the wait: a TTL shorter than the issuance it protects expires mid-work and lets a second request in, which is the one failure the lock exists to prevent and the one that leaves no trace.
resend.storenullCache store for the guard; null uses the default. Must support atomic locks. The guard's state is a cache entry: cache:clear resets every cooldown, and the array store holds it for one process only.

Invitations

KeyDefaultEnvironment variablePurpose
invitations.enabledfalseEMAIL_MAGIC_LINK_INVITATIONS_ENABLEDRegister the invitation channel. Requires handler and view; the package refuses to boot without them.
invitations.ttl604800EMAIL_MAGIC_LINK_INVITATION_TTLInvitation lifetime in seconds (7 days). Floored at 60.
invitations.storenullClass implementing InvitationStore; null uses the bundled Eloquent store.
invitations.handlernullClass implementing InvitationHandler — what accepting an invitation means in your application. Required when enabled.
invitations.viewnullYour acceptance screen. The package ships none. Required when enabled.
invitations.redirect_to'/'Where to send someone whose acceptance produced no session.
invitations.retain_accepted_days30How long accepted and revoked rows survive the purge. They carry the invited address in the clear, so this is a retention decision; 0 deletes them as soon as they settle.