jeffersongoncalves/laravel-settings-discord-logger
A Laravel package that adds a Discord logging channel configured through Laravel Settings, making it easy to send application logs to Discord webhooks with minimal setup and runtime-configurable options.
This package is a thin but opinionated bridge: it moves the configuration of jeffersongoncalves/laravel-discord-logger out of config/logging.php and .env and into a database row managed by spatie/laravel-settings. You still log through Laravel's normal logging facade — the package just changes where the discord channel gets its values.
Minimum steps:
spatie/laravel-settings and laravel-discord-logger). Confirm your app is on PHP 8.2+ and Laravel 12.61+/13 — this is not a package you can drop into a Laravel 10/11 app.LOG_DISCORD_WEBHOOK_URL setup keeps working with zero code changes after install.LOG_DISCORD_WEBHOOK_URL in .env for the first deploy. Because an empty stored webhook falls back to the env var, you can install now and move the URL into the database later, per environment.php artisan discord-logger:test. This command resolves the stored webhook, so a passing test proves the DB → channel path is wired up, not just the config path.Where to look first: open DiscordLoggerSettings and read the property list before touching anything else. That class is the package's public API — every knob you'll ever tune day-to-day lives there, and the channel factory is just a mapper from those properties onto the underlying logger's config keys. Second, look at the SettingsLogger channel factory to understand the merge order, because that determines which of the two config sources wins for any given key.
First real use case: replace a hardcoded #alerts webhook with a per-level route — errors to the on-call channel, warnings to a triage channel — configured by an admin screen or a seeder rather than a redeploy. That is the single highest-value thing this package unlocks.
Merge semantics over config. The stored settings are merged over the config array, with null/empty treated as "not set". Treat the config file as your defaults file and the DB row as an override layer. A practical workflow: keep sane defaults committed in config/logging.php, and only ever write non-null values to the settings row for the things that genuinely differ per environment (webhook, level threshold, queue on/off).
Per-environment rows. Since spatie/laravel-settings is per-application, not per-environment, be deliberate about what a shared database means. If staging and production share a DB (they shouldn't, but it happens), the webhook stored in that row is the one that gets used everywhere. Safer pattern: seed the settings row in a deploy step per environment rather than editing it in a UI that points at a shared database.
Admin/ops surface. The natural integration is a small settings page (Filament, Nova, or a hand-rolled controller) bound to DiscordLoggerSettings. The encrypted properties mean you can let non-developers rotate a webhook without exposing it back to them — render a masked placeholder and only write when the submitted value is non-empty. Keep the enabled boolean as a kill switch on that page; it's the fastest way to stop a webhook from being rate-limited or from spamming a channel during an incident.
Noise control as a first-class concern. grouping, deduplication, and rate limits are the answer to "our Discord channel is unreadable." Practical recipe: turn on grouping and deduplication for high-volume channels (queue failures, 4xx storms), and turn on rate limiting for anything that can fire in a loop. The stacktrace and runtime context toggles are the cost/readability dial — keep stacktraces on for error and above, off for warning, or the messages get truncated by Discord's 2000-character limit anyway.
Queue integration. Queue the channel for anything on a request path that must not block. The queue settings here are channel-level, so you can have a synchronous discord channel for critical alerts and a queued variant for chatty operational logging — or simply flip queue on in production and off locally.
Testing. In tests, the settings table is usually absent or empty, which exercises the config fallback path automatically — convenient, but it means your tests are not covering the DB-backed path unless you explicitly create the settings row. Do both: one test asserting fallback behaviour, one with DiscordLoggerSettings::fake()-style overrides (or a persisted row) asserting the merged result. Use Http::fake() to assert the actual webhook payload rather than asserting on the settings object.
Migration path for existing apps. Because the channel factory upgrades an existing channel that used the plain Logger, you can install this on an app that already has a discord channel defined and it will be replaced at resolution time. Remove the old array from config/logging.php once you trust the DB path, or you'll have a stale config that misleads the next reader.
discord channel is first used" means changes made during a request (or between jobs in a long-running worker) won't be picked up. If you build an admin UI, redirect after save rather than expecting the next log line in the same request to use the new webhook. Long-lived queue workers may hold a stale channel for their whole lifetime — restart workers after a settings change you need to take effect immediately.APP_KEY. The webhook and per-level webhooks are encrypted at rest. Rotating APP_KEY without re-encrypting (php artisan key:rotate-style migration) will make those values undecryptable, and the failure mode is a silent fallback to the env var or a failed send — not an obviously decryption-shaped error. Back up the row before any key rotation.LOG_DISCORD_WEBHOOK_URL." That's helpful, but it also means you cannot disable the env webhook by blanking the DB value. If you need to point at a different webhook you must write a real URL; if you need to stop sending, use enabled.enabled = false is silent. Logging a message to a disabled channel produces no output and no error. That's the point, but it's a classic "why is nothing in Discord?" cause. Check enabled before you check the webhook, the queue, and the network — in that order.config:cache with the wrong .env, the seeded row inherits those values permanently. Seed before caching, or seed explicitly from a deploy script with known-good values.level => 'error' but the stored row sets debug, the stored value wins and your channel suddenly gets loud. When investigating a volume spike, compare both sources rather than trusting config/logging.php.sender, mentions, and runtime context land in a Discord message and in your logs; the encryption only protects the webhook URLs. Mention strings that interpolate user input are also an @-mention injection vector — resolve user IDs, don't pass raw text.composer.json until you've read the changelog for whichever release touches DiscordLoggerSettings, since property renames there are effectively breaking API changes.enabled true → does the stored webhook decrypt (check APP_KEY history) → did the settings table migration run → is the channel queued and is the worker running → is the level threshold excluding the message → is the rate limit or deduplication swallowing it. The discord-logger:test command short-circuits the first four.How can I help you explore Laravel packages today?