PHPackages                             more-cores/discord-commands - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. more-cores/discord-commands

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

more-cores/discord-commands
===========================

1.2.0(3w ago)26.7k↑3000%[1 PRs](https://github.com/more-cores/discord-commands/pulls)MITPHPPHP &gt;=8.3CI passing

Since Oct 20Pushed 3w agoCompare

[ Source](https://github.com/more-cores/discord-commands)[ Packagist](https://packagist.org/packages/more-cores/discord-commands)[ Docs](https://github.com/more-cores/discord-commands)[ RSS](/packages/more-cores-discord-commands/feed)WikiDiscussions main Synced yesterday

READMEChangelog (10)Dependencies (6)Versions (23)Used By (0)

Discord Commands
================

[](#discord-commands)

Using this library you can build Discord bot commands, process inbound interactions from Discord and respond with messages.

Table of contents
=================

[](#table-of-contents)

- [Installation](#installation)
- [Creating Messages](#creating-messages)
    - [Mentioning](#mentioning)
    - [Enriching](#enriching)
    - [Author](#author)
    - [Fields](#fields)
    - [Image](#image)
    - [Thumbnail](#thumbnail)
    - [Footer](#footer)
    - [Components](#components)
        - [Buttons](#buttons)
        - [SelectMenus](#selectmenus)
            - [Role Select Menus](#role-select-menus)
            - [Mentionable Select Menus](#mentionable-select-menus)
            - [Mentionable Select Menus](#mentionable-select-menus)
            - [Channel Select Menus](#channel-select-menus)
        - [Text Input](#text-input)
- [Creating Commands](#creating-commands)
    - [Chat commands](#chat-commands)
- [Handling Command Interactions](#handling-command-interactions)
    - [Request verification](#request-verification)
        - [Laravel middleware](#laravel-middleware)
    - [Responding to Interactions](#responding-to-interactions)
        - [Modals](#modals)

Installation
============

[](#installation)

```
composer require more-cores/discord-commands

```

Creating Messages
=================

[](#creating-messages)

Creating webhook messages (they have higher rate limits):

```
$message = new WebhookMessage();
$message->setContent($content);

$embed = new Embed();
$embed->setTitle($title);
$embed->setDescription($description);
$embed->setUrl($url);
$embed->setTimestamp($dateTime);
$embed->setColor($color);
$message->addEmbed($embed);

$message->toJson(); // valid json ready to be sent to Discord via a Webhook
```

Or using standard messaging:

```
$message = new Message();
$message->setContent($content);
$message->addEmbed($embed);
```

### Mentioning

[](#mentioning)

Both `Message` and `WebhookMessage` offer the ability to mention roles.

```
// appends the mention to the previously set content.  Setting the content again overrides mentions
$message->mentionRole($roleId);

$message->isRoleMentioned($roleId);
$message->hasMentions();
```

#### Controlling who gets pinged

[](#controlling-who-gets-pinged)

Mention syntax placed in a message's content notifies by default. Use an `AllowedMentions` object to restrict (or fully suppress) those notifications:

```
use \DiscordCommands\Messages\AllowedMentions;

// suppress every mention - nobody gets pinged
$message->setAllowedMentions(AllowedMentions::none());

// or only allow specific roles/users to ping
$allowed = new AllowedMentions();
$allowed->allowRoles([$roleId]);
$allowed->allowUsers([$userId]);
$message->setAllowedMentions($allowed);
```

### Delivery options

[](#delivery-options)

```
// send without triggering a push/desktop notification (a "silent" message)
$message->sendSilently();

// read the message aloud with text-to-speech
$message->asTextToSpeech();

// don't expand links/embeds in the message
$message->withoutExpandingEmbeds();
```

These are available on `Message`, `WebhookMessage`, and `ReplyWithMessage`.

### Enriching

[](#enriching)

Certain information can be enriched on the Discord side, such as linking to channels and timestamps in the user's local time.

**Linking to a Channel**

```
use \DiscordCommands\Messages\Message;

$message->setContent('Instead of this channel, go to '.Message::linkChannel(34835835834));
```

**Linking to a Special Channel**

```
use \DiscordCommands\Messages\Message;
use \DiscordCommands\Messages\GuildNavigation;

$message->setContent('Help find new channels in '.Message::linkInGuild(GuildNavigation::CHANNEL_BROWSER));
```

**Embedding Timestamps**

```
use \DiscordCommands\Messages\Message;
use \DiscordCommands\Messages\TimestampFormat;

$message->setContent('The contest runs until '.Message::timestamp(time()+6000));

// You can also customize formatting
$message->setContent('The contest runs until '.Message::timestamp(time()+600, TimestampFormat::SHORT_TIME));
```

Author
------

[](#author)

```
// define an embed author using shorthand
$embed->setAuthor($name);

// and optionally specify specific attributes
$embed->setAuthor($name, $url);

// define an embed author by object
$author = new Author();
$author->setName($name);
$embed->setAuthor($author);
```

Fields
------

[](#fields)

```
// define an embed video using shorthand
$embed->addField($fieldName, $fieldValue);

// and optionally specify whether it's inline (default to false)
$embed->addField($fieldName, $fieldValue, $inline = true);

// define an embed field by object
$field = new Field();
$field->setName($name);
$field->setValue($value);
$embed->setVideo($field);
```

Image
-----

[](#image)

```
$embed->setImageUrl($imageUrl);
```

Thumbnail
---------

[](#thumbnail)

```
$embed->setThumbnailUrl($thumbnailUrl);
```

Footer
------

[](#footer)

```
// define an embed footer using shorthand
$embed->setFooter($text, $iconUrl);

// and optionally specify  specific attributes
$embed->setFooter($urlToImage, $width, $height);

// define an embed thumbnail by object
$thumbnail = new Thumbnail();
$thumbnail->setText($text);
$thumbnail->setUrl($urlToImage);
$embed->setFooter($thumbnail);
```

Components
----------

[](#components)

### Buttons

[](#buttons)

Use dedicated classes for each button type. Because buttons are non-interactive, you'll need to wrap them with an `ActionRow` when adding them to a message. You can use the shorthand `actionRow()` to accomplish this.

```
$message->actionRow(new SuccessButton('button-id', 'Approve it'));
$message->actionRow(new DangerButton('button-id', 'Reject it'));
$message->actionRow(new PrimaryButton('button-id', 'Something else'));
$message->actionRow(new SecondaryButton('button-id', 'Something else'));
$message->actionRow(new LinkButton('https://mysite.com', 'My Site'));
$message->actionRow(new PremiumButton('sku-id')); // links to a purchasable SKU
```

### SelectMenus

[](#selectmenus)

```
$message->addComponent(new StringSelectMenu($menuId, [
    new Option('Option 1', 'option-1'),
    new Option('Option 2', 'option-2'),
]));
```

#### Role Select Menus

[](#role-select-menus)

```
$message->addComponent(new RoleSelectMenu($menuId));
```

#### Mentionable Select Menus

[](#mentionable-select-menus)

```
$message->addComponent(new MentionableSelectMenu($menuId));
```

#### Mentionable Select Menus

[](#mentionable-select-menus-1)

```
$message->addComponent(new UserSelectMenu($menuId));
```

#### Channel Select Menus

[](#channel-select-menus)

```
$message->addComponent(new ChannelSelectMenu($menuId));
```

You can also limit which channel types can be selected

```
$message->addComponent(new ChannelSelectMenu($menuId, [
    channelTypes: [
        Channel::TYPE_GUILD_TEXT,
        Channel::TYPE_GUILD_VOICE,
    ],
]));
```

The auto-populated select menus (user, role, mentionable, and channel) can be pre-populated with default selections:

```
$menu = new UserSelectMenu($menuId);
$menu->addDefaultUser($userId);

$menu = new RoleSelectMenu($menuId);
$menu->addDefaultRole($roleId);

$menu = new ChannelSelectMenu($menuId);
$menu->addDefaultChannel($channelId);
```

### Text Input

[](#text-input)

```
$message->addComponent(new ShortInput($fieldId, 'First Name'));
$message->addComponent(new ParagraphInput($fieldId, 'Dating Profile'));
```

Creating Commands
=================

[](#creating-commands)

Chat commands
-------------

[](#chat-commands)

To create a chat command, create a class like this:

```
class MyCommand extends ChatInputCommand
{
    public const NAME = 'my-command';

    public function __construct()
    {
        parent::__construct(
            name: self::NAME,
            description: 'This is executable by the higher ups in the guild',
            availableInDms: false,
            defaultMemberPermissions: [
                Permission::ADMINISTRATOR,
                Permission::MANAGE_GUILD,
            ]
        );
    }
}
```

You can then simply run `(new MyCommand())->jsonSerialize()` to generate the json payload needed to sync your command with Discord.

Handling Command Interactions
=============================

[](#handling-command-interactions)

You can use our factory to hydrate objects that represent incoming interactions within Discord. Here's an example using a Laravel request object:

```
$factory = new InteractionTypeFactory();
$interaction = $factory->make($request->json('type'), $request->json());
```

Request verification
--------------------

[](#request-verification)

Discord requires that you verify the signature of inbound requests from their system. You'll need to have this in place in order to even configure your interactions endpoint.

You'll need to run `composer require simplito/elliptic-php` to add the necessary dependency, then you can verify the request like this:

```
$verifier = new SignatureVerifier();

if (!$verifier->verify(
    rawBody: file_get_contents('php://input'),
    publicKey: getenv('DISCORD_BOT_PUBLIC_KEY'),
    signature: $_SERVER['HTTP_X_SIGNATURE_ED25519'],
    timestamp: $_SERVER['HTTP_X_SIGNATURE_TIMESTAMP'],
)) {
    return http_response_code(401);
}

// Everything's good, do the rest here
```

### Laravel middleware

[](#laravel-middleware)

For Laravel applications, we've included a middleware out of the box to assist with this. You can configure a route in `routes/api.php` and wrap it with our verification middleware:

```
Route::group([
    'prefix' => 'discord',
    'namespace' => 'Api\Discord',
    'middleware' => LaravelDiscordSignatureVerificationMiddleware::class,
], function () {
    Route::post(
        'webhook/command/interactions',
        'CommandInteractionController@userInteracted'
    );
});
```

Then in your controller, make sure you respond to `Ping` with a `Pong`...

```
    public function userInteracted(
        Request $request,
        InteractionTypeFactory $interactionTypeFactory
    ) {
        $interaction = $interactionTypeFactory->make($request->json('type'), $request->all());

        if ($interaction instanceof Ping) {
            return new Pong();
        }

        // generate other responses based on the inbound interaction
    }
```

Responding to Interactions
--------------------------

[](#responding-to-interactions)

You can respond to interactions by sending messages, showing modals, etc. Make sure to give the [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding) a read (e.g. your app has 3 seconds to respond to an interaction).

### Modals

[](#modals)

To show a modal, you can simply respond to the interaction http request with your modal object:

#### Showing the modal

[](#showing-the-modal)

```
$modal = new ShowModal(
    id: 'something',
    title: "Add new game/software for voting",
);
$modal->actionRow(
    new ShortInput(
        id: 'field-1',
        label: 'My Field',
    )
);
$modal->actionRow(
    new ShortInput(
        id: 'field-2',
        label: 'My Field 2',
    ),
);
return $modal->jsonSerialize();
```

#### Processing modal feedback

[](#processing-modal-feedback)

To process processing modal feedback, make sure you're handling `ModalSubmitted` events.

In the below example, `$commandFactory` is not provided by this package, but relates to your domain. You can customize that process however you'd like.

```
public function interactions(
    Request $request,
    InteractionTypeFactory $interactionTypeFactory,
    CommandFactory $commandFactory,
) {
    $interaction = $interactionTypeFactory->make($request->json('type'), $request->all());

    if ($interaction instanceof Ping) {
        return new Pong();
    }

    if ($interaction instanceof ModalSubmitted) {
        $command = $commandFactory->makeByModal($interaction->modal()->id());
        return $command->whenSubmitted($interaction, $interaction->modal());
    }

    throw new RuntimeException('Command interaction went unhandled');
}
```

In my case, I like for a single class to contain all the details about what happens on a Command. So I added a `whenSubmitted` method that looks something like this:

```
public function whenSubmitted(
    ModalSubmitted $interaction,
    SubmittedModal $modal,
): CommandResponse {
    Log::info('modal submitted', [
        'modal' => $modal->id(),
        'value' => $modal->fieldValue('field-2'),
    ]);

    return new ReplyWithMessage(
        content: "You're all done!",
        onlyVisibleToCommandIssuer: true,
    );
}
```

The key takeaway here is that Discord *requires* an interaction in response to the modal submission. So you'll need to respond with something.

### Component Interactions

[](#component-interactions)

When a user clicks a button or makes a selection in a select menu, Discord sends a `ComponentInteracted` interaction. You can identify which component was used via its `custom_id`, and read any selected values.

```
if ($interaction instanceof ComponentInteracted) {
    $interaction->customId();    // the custom_id of the button/menu that was used
    $interaction->isButton();    // true for buttons
    $interaction->isSelectMenu();// true for any select menu
    $interaction->values();      // the selected values (empty for buttons)
    $interaction->message();     // the original message the component is attached to

    // respond with one of the responses below
}
```

#### Updating the original message

[](#updating-the-original-message)

Instead of sending a new message, you can edit the message the component is attached to (for example, to disable a button after it's clicked) by responding with an `UpdateMessage`:

```
return new UpdateMessage(
    content: 'Thanks, recorded your vote!',
    components: [], // pass an empty array to remove the components
);
```

#### Deferring a response

[](#deferring-a-response)

Discord requires a response within 3 seconds. If you need longer, acknowledge the interaction first and follow up later:

```
// for a command interaction - shows a loading state, then you edit the reply later
return new DeferReply(onlyVisibleToCommandIssuer: true);

// for a component interaction - acknowledges without showing anything, then you edit the message later
return new DeferUpdate();
```

###  Health Score

54

—

FairBetter than 96% of packages

Maintenance94

Actively maintained with recent releases

Popularity27

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity71

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~83 days

Recently: every ~239 days

Total

17

Last Release

27d ago

Major Versions

0.5.3 → 1.0.02025-11-25

PHP version history (2 changes)0.1.0PHP &gt;=8.1

1.0.0PHP &gt;=8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/524933?v=4)[Ben Kuhl](/maintainers/bkuhl)[@bkuhl](https://github.com/bkuhl)

---

Top Contributors

[![bkuhl](https://avatars.githubusercontent.com/u/524933?v=4)](https://github.com/bkuhl "bkuhl (37 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/more-cores-discord-commands/health.svg)

```
[![Health](https://phpackages.com/badges/more-cores-discord-commands/health.svg)](https://phpackages.com/packages/more-cores-discord-commands)
```

###  Alternatives

[illuminate/support

The Illuminate Support package.

630113.0M41.3k](/packages/illuminate-support)[livewire/flux

The official UI component library for Livewire.

9527.8M128](/packages/livewire-flux)[sanmai/pipeline

General-purpose collections pipeline

7527.3M28](/packages/sanmai-pipeline)[masoudi/laravel-visitors

Laravel package for tracking visitors

373.2k](/packages/masoudi-laravel-visitors)[qalainau/filament-inbox

Email-like inbox messaging plugin for Filament v5 — threads, starring, trash, forwarding, read receipts, and multi-tenancy out of the box.

171.5k](/packages/qalainau-filament-inbox)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
