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 Php Laravel Package

team-reflex/discord-php

DiscordPHP is a PHP wrapper for Discord's REST, gateway, and voice APIs, letting you build feature-rich Discord bots that run in CLI environments. It's framework-agnostic, with community integrations like Laracord for Laravel.

View on GitHub
Deep Wiki
Context7

Getting Started

First, decide whether you need the gateway at all. DiscordPHP is a long-running CLI client for Discord's Gateway (WebSocket) API, not an HTTP integration. As a Laravel developer, most of what you'll be asked to do falls into two buckets:

  • Outbound only (deploy alerts, order notifications, error reports, "new lead" pings): do not install this package. Create a channel webhook and use Http::post($webhookUrl, ['content' => ...]). Twenty lines, no daemon, no event loop, no supervisor config.
  • Bidirectional (slash commands, message triggers, reactions, moderation, ticket triage, voice): this is where DiscordPHP earns its keep — and where every Laravel instinct you have about request/response will fight you.

Minimum viable setup

  1. Create an application + bot user in the Discord Developer Portal, copy the token, and enable the intents you need (Message Content and Server Members are privileged and must be toggled in the portal first).
  2. composer require team-reflex/discord-php on PHP 8.1.2+ with ext-json and ext-zlib; add ext-uv (or ext-ev/ext-event) and ext-mbstring if you handle non-Latin text.
  3. Put the token in .env as DISCORD_TOKEN and treat it like any other secret — it is a full bot credential, not a public key.
  4. Boot the client from an Artisan command, not from a controller or middleware.
// app/Console/Commands/DiscordServe.php
public function handle(): int
{
    $discord = new Discord([
        'token'   => config('services.discord.token'),
        'intents' => Intents::getDefaultIntents() | Intents::MESSAGE_CONTENT,
        'logger'  => Log::channel('discord')->driver(), // Monolog instance
    ]);

    $discord->on('ready', fn (Discord $d) => $this->info("Logged in as {$d->username}"));

    $discord->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) {
        // keep this thin — see Implementation Patterns
    });

    $discord->run(); // blocks forever; owns the process

    return self::SUCCESS;
}

Where to look first, in order: the README's basic example (above) → the examples/ folder in the repo (this is the real documentation) → the guide at discord-php.github.io/DiscordPHP/guide → the wiki → the Discord server for anything the docs omit. The class reference matters more than the prose docs; you'll spend most of your time reading src/Discord/Parts/** and src/Discord/Repository/** source.

If you want Laravel idioms for free, evaluate laracord/laracord — a community (not core) integration that gives you container bindings, a config file, and Artisan-driven lifecycle. It saves you the plumbing below; read its source before adopting it, since it lags core releases.

First real use case: an internal ops bot in one guild — /status, /deploy, /whois slash commands plus a scheduled nightly digest posted to #ops. It exercises intents, interactions, builders, and persistence without needing sharding or voice.

Implementation Patterns

Own the process lifecycle with an Artisan command + Supervisor. $discord->run() blocks and never returns until the connection dies, so the command is your daemon. Register it in Supervisor/systemd with autorestart=true, and php artisan queue:restart-equivalent on deploy. Never run it under php-fpm, Octane, or a queued job.

Bind the client as a singleton, but only inside the bot process. Constructing a Discord in AppServiceProvider::register() would connect on every web request. Instead:

// app/Providers/DiscordServiceProvider.php
$this->app->singleton(Discord::class, fn () => new Discord([...]));

…and only resolve it from the bot command and from bot-process-only services.

Keep handlers thin and move logic into invokable classes. Gateway callbacks are the worst place to put business logic: untestable, no DI visibility, one thrown exception can take down the loop.

// config/discord.php
'listeners' => [
    Event::MESSAGE_CREATE     => Handlers\OnMessage::class,
    Event::INTERACTION_CREATE => Handlers\OnInteraction::class,
    Event::GUILD_MEMBER_ADD   => Handlers\OnMemberJoin::class,
],

// in the serve command
foreach (config('discord.listeners') as $event => $class) {
    $discord->on($event, function ($payload, Discord $discord) use ($class) {
        try {
            app($class)($payload, $discord);
        } catch (Throwable $e) {
            report($e); // your normal Laravel reporting still works
        }
    });
}

This single wrapper gives you: exception isolation, DI, report() into your existing channels, and unit-testable handler classes (new OnMessage($service) — no gateway required).

Bridge the web tier to the bot explicitly. The Laravel app and the bot are separate processes with no shared memory. To make POST /orders announce in Discord, pick one:

  • Webhook (best for fire-and-forget): Http::post() straight from the controller/job. Bypasses DiscordPHP entirely.
  • Queue + bot-side consumer: job writes to Redis; bot subscribes with a ReactPHP Redis client off $discord->getLoop().
  • Internal HTTP endpoint: run react/http on localhost inside the bot process (the README explicitly suggests this) and Http::post() to it.
  • Periodic timer: $discord->getLoop()->addPeriodicTimer(5, fn () => ...) polling a table — simplest, and fine for low-volume internal bots.

Slash commands and interactions. Register commands via the interactions API, then acknowledge immediately and defer expensive work:

$interaction->acknowledge();                     // beats the 3-second deadline
dispatch(new BuildReportJob($interaction->id));  // then follow up via webhook/token

Store the interaction token and message id if the work outlives the callback — you have 15 minutes to edit the original response.

Use the builders for anything structured. MessageBuilder, embeds, and action rows/components exist so you don't hand-assemble payload arrays; they validate and serialize. Reach for $channel->sendMessage($builder) and $message->reply() / ->react() rather than repositories. For conversational flows, Channel::createMessageCollector() and Message::createReactionCollector() are the idiomatic "await next user input" primitive — but treat them as short-lived and always give them a timeout.

Persistence discipline. Persist only scalars you need (guild_id, channel_id, message_id, user_id, your own domain rows). Never serialize a Part into Eloquent or the cache; re-fetch via the repository when you need it. Part::save($reason) is the modern write path — prefer it over $repository->save($part).

Testing. Your handlers, services, and builders are ordinary PHP classes — test them directly. For payload shapes, construct parts through the factory rather than new. Anything that actually touches the gateway belongs in a manually-run staging bot in one throwaway guild, because there's no meaningful "integration test in CI" story for a persistent WebSocket session.

Gotchas and Tips

CLI-only, non-negotiable. The README says this explicitly. No Apache/nginx, no Octane, no php artisan serve. If someone asks "can we run the bot inside the app server?" the answer is no — plan for a second container/process in your deployment topology from day one.

Blocking the loop is the #1 production failure. A slow Eloquent query, a synchronous Http::get(), sleep(), or a big dd() inside a handler stalls the ReactPHP loop, which stalls the gateway heartbeat, which gets you a zombie connection or a hard disconnect with a cryptic close code. Rules: keep DB queries indexed and single-digit milliseconds, push anything heavier to a queue, and never use Laravel's default sync queue driver in this process.

Long-running process, stale Laravel config. Configuration is read once at boot. After a deploy you must restart the bot (supervisorctl restart discord-bot) — config:cache alone changes nothing in a running process. Same for autoloaded classes and route/event caches.

Database connections die before your bot does. MySQL wait_timeout (often 8h) will silently kill an idle connection, and the next query in a message handler throws. Either keep the connection alive with a periodic DB::connection()->getPdo() ping on $discord->getLoop(), or catch QueryException with driver error 2006 and DB::reconnect().

Unhandled promise rejections terminate the loop. Every ->done(), ->then(), ->then(...)->otherwise(...) chain needs a ->otherwise() that logs. A rejected promise with no handler is a silent bot death with no stack trace in your logs — one of the most common "it just stopped" reports.

Do not exit() or die() in a handler. That kills the process mid-session without closing the gateway cleanly. To stop, call $discord->close() and let the loop drain, and wire SIGTERM/SIGINT to that path so Supervisor restarts are graceful.

Intents: two separate switches. MESSAGE_CONTENT and GUILD_MEMBERS must be enabled in the developer portal and included in the intents bitmask. Forget the portal and you get an empty $message->content for guild messages (DMs and mentions still work, which makes it look intermittently broken). loadAllMembers requires GUILD_MEMBERS; large guilds will hit the member-fetch rate limit on boot.

Memory grows and the README's advice is a painkiller, not a cure. ini_set('memory_limit', '-1') is literally the FAQ answer, but the underlying issue is DiscordPHP caching guilds, channels, and members in-process forever. Cap the number of guilds, be deliberate with loadAllMembers, disable query logging (DB::connection()->disableQueryLog()), and watch RSS over days, not hours. Also purge long-lived model instances — Eloquent's static state is not designed for month-long processes.

Rate limits are handled but not free. DiscordPHP-Http buckets requests by major parameters and queues them. The trap is fan-out: iterating 500 members and calling sendMessage() per member will take minutes and can trip global limits. Batch, or use a single message with components.

Windows TLS silently fails. PHP on Windows doesn't read the Windows cert store; download a CA extract and set openssl.cafile in php.ini. The symptom is the script exiting after one loop turn with no error — remember that pattern before you spend hours debugging your code.

Version drift is severe. This library has had long quiet periods and large breaking releases: Collection no longer extends Illuminate's (so no ->map()/->filter() you're used to), cachePool/logging/loggerLevel options were removed in favor of a Monolog instance, and the voice client's API changed from promises to throwing void methods. Pin a minor version, read changelog/ before upgrading, and don't copy snippets from old tutorials or Stack Overflow.

Voice is a separate install and a separate skill. It needs discord-php/discord-php-voice, FFmpeg, platform-specific DCA binaries (AMD64 only on Win/macOS/Linux), and it's the least stable surface in the ecosystem. If voice isn't core to the product, scope it out of v1.

Extension points worth knowing. Custom Part subclasses registered through the factory, custom Repository classes for your own endpoint wrappers, and Builder classes for outbound payloads with real validation. The repo's own agent guide names the invariant: builders own outbound shape, repositories own persistence, gateway handlers own cache coherence — respect those boundaries and upgrades stay sane.

Debugging toolkit: set a dedicated Monolog channel at debug, log the raw event name and payload id in your wrapper, and keep a #bot-dev channel where the bot posts its own exceptions via report(). Because there's no request context, structured logging with guild_id/channel_id/user_id in the context array is the only way to trace anything after the fact.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log