Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Discord Notifications Laravel Package

tomatophp/discord-notifications

Send Discord notifications from your Laravel app with tomatophp/discord-notifications. This package offers a simple integration for delivering messages to Discord channels via webhooks, making it easy to add real-time alerts and updates to your projects.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require tomatophp/discord-notifications
php artisan vendor:publish --tag="discord-notifications-config"   # verify tag in README

2. Set the webhook

Create a channel webhook in Discord (Channel Settings → Integrations → Webhooks → Copy URL) and put it in .env:

DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxxx/yyyy

The package reads from a config file (typically config/discord-notifications.php) that defaults the webhook to that env var. Confirm the exact key name by opening the published config — that file is the single source of truth for every option the package supports.

3. Where to look first

Read, in this order:

  1. The published config file — tells you the routing model (global webhook vs. per-notifiable) and any embed/queue switches.
  2. The channel class (src/Channels/*.php) — send() shows exactly what payload shape is expected and what it does when the route is null.
  3. The message/DTO class (src/DiscordMessage.php or similar) — this is your builder API; its public methods are your whole vocabulary.

4. First use case: an ops alert

The lowest-friction win is a one-way alert, not a conversation. Wire a single notification to a #alerts channel and fire it from your exception handler:

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use TomatoPHP\DiscordNotifications\Channels\DiscordChannel;
use TomatoPHP\DiscordNotifications\DiscordMessage;

class OpsAlert extends Notification
{
    public function __construct(public string $title, public string $body) {}

    public function via(object $notifiable): array
    {
        return [DiscordChannel::class];
    }

    public function toDiscord(object $notifiable): DiscordMessage
    {
        return DiscordMessage::make()
            ->content("**{$this->title}**\n{$this->body}");
    }
}

Then in bootstrap/app.php (Laravel 11) or App\Exceptions\Handler:

Notification::route('discord', config('discord-notifications.webhook'))
    ->notify(new OpsAlert('Deploy failed', $e->getMessage()));

Note the on-demand route name — it must match whatever routeNotificationFor... key the channel looks for (discord here; check the README, some packages use discord-webhook).

Implementation Patterns

Per-user routing. Don't hardcode the webhook in the notification. Put a discord_webhook column on users and implement routeNotificationForDiscord() on the model so the same notification fans out to different channels:

public function routeNotificationForDiscord(): ?string
{
    return $this->discord_webhook;
}

Returning null for users who haven't connected Discord is the clean way to make the channel a no-op per-recipient.

Per-tenant routing. In a multi-tenant app, make the webhook a tenant setting and resolve it in routeNotificationForDiscord() from the current tenant rather than the user. Keeps a single notification class usable across customers.

One central "system event" notification. The most maintainable pattern is a single SystemEvent notification with title, body, level (info/warn/critical), and context (array). Map level to an embed colour and to which webhook to post to. Every call site becomes one line, and formatting changes happen in one place.

Layered notifications for user-facing features. Use the Discord channel as a secondary channel on notifications that already have mail and database:

public function via(object $notifiable): array
{
    return ['mail', 'database', DiscordChannel::class];
}

This keeps Discord as a bolt-on rather than a dependency of the business flow.

Embeds for anything structured. Plain content is one 2000-char string. Embeds carry a title, description, colour, fields, footer, and timestamp — which is what you actually want for alerts. Check the message class for embed()/addEmbed()/fields() methods; if the builder is thin, build the array yourself and pass it through.

Always queue. Discord is a third-party API; a slow or failing webhook must never block a request. Add implements ShouldQueue to notification classes and keep the channel synchronous-but-queued:

class OpsAlert extends Notification implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [10, 60, 300];
}

Scheduler heartbeats. Pair ->onFailure() / ->onSuccess() on scheduled tasks with a SystemEvent notification. This is where Discord notifications earn their keep — dead-man's-switch visibility on crons that normally run silently.

Testing. Use Notification::fake() and assert the notification was dispatched, not the HTTP call:

Notification::fake();

Notification::route('discord', 'https://discord.com/api/webhooks/test')
    ->notify(new OpsAlert('x', 'y'));

Notification::assertSentOnDemand(OpsAlert::class);

If you need to assert payload shape, Http::fake() and inspect Http::recorded() — the channel ultimately issues an HTTP post to the webhook URL, so you can assert the JSON without touching Discord.

Integration tips. Keep notification classes in app/Notifications and treat the package's message object as a thin DTO — don't spread builder calls across services. If you find yourself repeating embed assembly, subclass the message object (see Gotchas) rather than copying arrays.

Gotchas and Tips

Webhook URL is a credential. Anyone with it can post to your channel. Keep it in .env, never in the database in plaintext without encryption, and never log the full URL (log a truncated prefix instead). Rotate it if it leaks.

Null routes fail loudly. Depending on implementation, a missing webhook either throws or silently drops. Decide which you want and guard explicitly: if the route is null, the channel should no-op rather than blow up a queued job that will retry three times into the same failure.

Rate limits are real. Discord throttles webhooks around 30 requests/minute per webhook and roughly 5 per 2 seconds. Bulk notifications (a loop over 500 users) will get 429s. Batch into a single digest embed, or dispatch with ->delay() spacing, and rely on queue backoff so 429s self-heal.

Hard size ceilings. Content is capped at 2000 characters; embeds have their own limits (title 256, description 4096, 25 fields, 10 embeds per message). Truncate aggressively — a Str::limit() in the message builder saves you from silently rejected payloads on the one alert that mattered.

Webhook vs. bot. This package is webhook-based: one-way posting only, no reading messages, no reactions, no slash commands. If your requirement is interactive, you need a bot library, not this. Set expectations early.

Config caching bites. If you change routing in the config file and see stale behaviour, php artisan config:clear. Anything resolved via config() at boot is frozen after config:cache.

Package staleness. Last release 2024-03-12, zero stars, zero dependents. Before adopting:

  • Pin the version in composer.json and vendor-review src/ — it's small enough to read in ten minutes.
  • Verify Laravel 11/12 compatibility explicitly; a 2024 snapshot may not declare support for your framework version.
  • Have an exit plan. Because the integration surface is just "POST a JSON body to a webhook URL," you can swap in laravel-notification-channels/discord or a 30-line custom channel with the same via()/toDiscord() contract. That low switching cost is the real reason this is safe to try.

Extension points. The message object is the intended customisation seam — subclass it, add your own critical()/info() helpers, and have all notifications return the subclass. If the channel class isn't final, you can extend it to intercept send() for logging or redaction (never send stack traces containing credentials or PII into a channel).

Debugging workflow. When a notification doesn't arrive: (1) confirm via() includes the channel, (2) dd() the return of routeNotificationForDiscord(), (3) Http::fake() and dump the recorded payload to confirm shape, (4) curl -X POST -H 'Content-Type: application/json' -d '{"content":"test"}' $WEBHOOK to isolate Discord from Laravel. In that order — it separates routing bugs from payload bugs from network bugs in three steps.

Silent failures in queues. Channel exceptions inside queued notifications land in failed_jobs, where nobody looks. Add a failed() method on alerting notifications that falls back to Log::critical() — otherwise your alerting path can fail without alerting anyone.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo