Skip to main content

npm

Source

See the Plugins guide for how plugins from wolfstar-project/plugins fit alongside the core framework.

Description

@wolfstar/plugin-gateway extends the @wolfstar/http-framework client with Discord gateway events and API access. GatewayClient is a subclass of the framework’s Client. Commands and HTTP interactions keep working exactly as before, and the same process also receives gateway dispatches such as messageCreate or guildMemberAdd. The package connects shards with @discordjs/ws, which handles sharding, reconnects, and session resumes. Actions turn dispatches into events carrying structures such as Message, User, and Guild. Managers such as client.users and client.guilds expose a discord.js-style manager.cache, in memory by default or backed by @wolfstar/plugin-cache, and fetch missing data through @discordjs/core, which is also available as client.api.
A gateway connection is long-lived. A GatewayClient needs a persistent process, unlike a bot that only serves HTTP interactions.

Requirements

  • Node.js >=24.17.0. The package builds on @discordjs/structures, which is only published as dev snapshots that require Node.js 24.17.
  • @wolfstar/http-framework ^3.4.0 or ^5.0.0 as a peer dependency.
  • @wolfstar/plugin-cache if you want to cache Discord entities in Redis or resume sessions across restarts.

No side-effect registration

Unlike most plugins, @wolfstar/plugin-gateway is a library. Its @wolfstar/plugin-gateway/register entry point exists only so the Stars CLI build can import it like every other plugin, and it does nothing. You opt in to the gateway by constructing a GatewayClient instead of the framework’s Client.

Stars module

On framework 6.1 and later, list the ./module entry in modules in stars.config to add the package to the auto imports. It needs the optional @wolfstar/kit peer dependency, at ^0.2.0 || ^0.3.0.
The module registers no plugin and takes no options. It reserves the gateway key of stars.config for future options, so no other module can claim it. You still construct the GatewayClient yourself.

Usage

Create a GatewayClient where you would create the framework’s Client, then call start(). It loads your pieces, starts the HTTP interaction endpoint, and connects the gateway shards in one call:
To control each step yourself, call load(), listen(), and connect() separately. client.gateway exposes the underlying WebSocketManager, for example to send presence updates.

Options

GatewayClient accepts every Client option, plus:

Access the client from pieces

GatewayClient registers itself as container.gatewayClient when you construct it, next to the framework’s container.client. container.gatewayClient is typed as GatewayClient, so any listener, command, or interaction handler can use its managers without a cast:
container.client stays typed as the base Client, because a module augmentation cannot redeclare it with another type. Outside a piece, getGatewayClient() returns the same client.

Events

GatewayClient emits gateway events on the client itself. The GatewayEvents enum lists every event name. Each member’s value is the plain event name, like the framework’s own Events, so it is interchangeable with the string literal:
Update events receive the previous state from the cache, and delete events receive the cached entity. Both are null when the entity was not cached. Delete events also receive data, the raw dispatch data, which always identifies the deleted entity. Every dispatch is also emitted as raw, with the payload and shard ID. entitlementCreate and entitlementDelete receive the Entitlement. entitlementUpdate receives the previous entitlement, or null, and the new one. See Monetization.

Replaying dispatches on another process

A client that never connects to Discord can emit the events of a connected client that shares its cache. The connected client emits dispatch after writing to the cache, with the payload, the shard ID, and the previous state its handler read. client.serializeDispatchState(type, state) turns that state into JSON-safe data, and client.reviveDispatchState(type, serialized, data) rebuilds it on the receiving client. client.replayDispatch(payload, shardId, state) then emits raw and the matching event without touching the cache:
READY and INTERACTION_CREATE are not replayed, so client.user stays null and client.application stays partial until you call client.application.fetch(). client.replayDispatchTypes lists the types replayDispatch handles. On READY, the client drops the cached guilds of that shard which READY no longer lists, and emits guildDelete for each. These are guilds the bot left while disconnected, or while the process was down with a persistent cache. Discord does not replay those removals. Each drop also emits dispatch as a GUILD_DELETE (with s: 0 and no raw), so a process replaying this client’s dispatches sees these deletions too. This is best effort: a failure, such as an unreachable cache or an unknown shard count, is reported through error, and the remaining guilds are kept.

The clientReady event

clientReady is emitted once, like discord.js’s Client#clientReady. It fires after every shard this client manages has connected and every guild that READY listed as unavailable became available. If those guilds take longer than waitGuildTimeout (15 seconds by default), clientReady fires anyway. The timeout only bounds the wait on guilds, never the wait on shards connecting.
After the fact, client.isClientReady() returns whether the event fired, and client.clientReadyAt holds the Date it fired at, or null.

Dispatch ordering

GatewayClient processes dispatches of the same guild, or the same direct message channel, in order, so an asynchronous cache never reorders them. Different guilds proceed concurrently, so a slow guild does not hold the others back. Dispatches that belong to no guild, such as READY or USER_UPDATE, wait for everything queued before them on their shard. client.queueStats reports the pending dispatches.

Listeners

EventGatewayListener is a listener piece for gateway events. It pins the emitter to the client and types run after the event. Place it in your listeners directory:
To skip the constructor, use the RegisterAsGatewayListener decorator. With once: true, the listener unloads itself after its first run:

Run a listener on one process

With @wolfstar/plugin-broker, the gateway process handles each dispatch, and every worker handles it again when it replays it. Pieces loaded on both processes would run twice. Use the RegisterAsDispatchListener decorator instead of RegisterAsGatewayListener to run a listener on one process only. It takes the same arguments:
  • While the gateway client forwards its dispatches, the listener runs on the workers and stays idle on the gateway process. forwardGatewayDispatches marks the client as forwarding, which client.forwardsDispatches reports.
  • Otherwise, the listener runs on the gateway client itself, so it also works on a bot that does not use the broker.
The decorator checks forwardsDispatches on every emit, so the order in which forwarding starts and pieces load does not matter, and stopping the forwarding brings the listener back. A once listener is not spent while it idles. Only the gateway client emits clientReady, shardReady, shardResume, shardClose, shardError, dispatch, cacheError, and cacheSweep, so they never reach a worker. RegisterAsDispatchListener rejects them in its types and throws a TypeError at runtime. Keep RegisterAsGatewayListener for those events.
With events set on forwardGatewayDispatches, a listener of an event built from a dispatch that is not forwarded runs nowhere. Workers receive dispatches at least once, so keep the listener idempotent.

Caching

Every manager exposes its cache as manager.cache, the Cache of discord.js RFC #11426. manager.fetch(...) reads the cache first and falls back to the REST API:
By default, CollectionCache keeps entities in memory as a Collection of structure instances. Dispatches patch the cached instance in place, as in discord.js, and every method is synchronous. Choose another mode with the client options:
The default cache keeps every entity the gateway sends in memory until a dispatch such as MESSAGE_DELETE or GUILD_DELETE removes it. Nothing expires, so messages, users, and presences grow for as long as the process runs. See Bounding memory.
manager.cache is keyed by a single ID for most managers. Client managers that take more than one ID build the key with manager.resolveKey(...). For those, prefer the guild- or channel-scoped managers in Guild-scoped managers:
  • cache.get only reads the cache and returns undefined on a miss.
  • fetch reads the cache and falls back to the REST API, caching the result. Pass { force: true } after the IDs to always hit the API, or { cache: false } to skip storing the result.
  • refresh is fetch with { force: true }.
  • resolve takes a structure, returned as is, or a cache key, like discord.js’s resolve. resolveId returns the ID.
  • guild.members.me returns the bot’s own member from the cache, like discord.js. It is null when the member is not cached. Without a Guild, call client.members.me(guildId). client.members.fetchMe(guildId) falls back to the API.
  • manager.valueOf() returns manager.cache, like discord.js’s DataManager#valueOf.
Only manager.cache.get, and fetch and resolve, which read it, return the cached instance. Structures delivered by events are freshly built from the cache, and later dispatches do not patch them. The previous state of update and delete events is a copy of the cached instance taken before the write. Relations are resolved from the cache too. message.author is the entry of client.users, message.member the one of client.members, and every guild structure has guild. These getters return null when the entity is not cached. fetchGuild() and fetchChannel() always get it. guild is a copy of the cached guild, so compare guilds by id.

Guild-scoped managers

Like discord.js, a guild, channel, or thread hands out its own managers. The first ID is filled in, so cache is keyed by the entity’s ID alone. Use them instead of building keys with resolveKey, which keeps working:
Call client.guilds.members(guildId) and the other client methods when you only hold the guild’s ID:
The guild managers are the client’s managers built for one guild. guild.members is a GuildMemberManager<true>, and guild.roles, guild.voiceStates, and guild.presences are typed the same way. They have every method of the client’s manager with the guild’s ID filled in, and a guildId property. Their cache is a Cache like the client’s. It counts and clears the guild’s entries alone, and is synchronous when the client’s is. The cache of channel.messages and thread.members only has get, has, and delete. On the client’s GuildMemberManager, me is now a property holding a function instead of a prototype method. You still call it as client.members.me(guildId). Other managers follow discord.js too:
  • category.children is a CategoryChannelChildManager. Its cache lists the cached channels of the category, and is a promise with an asynchronous store. create(options) makes a channel inside the category, and resolve() and resolveId() look one up. cache is read from the channel store on each access, so a store that cannot enumerate its entries throws CacheNotIterable. create() rejects a category as the channel type.
  • answer.voters is a PollAnswerVoterManager. fetch() lists the users who voted for the poll answer, and answer.fetchVoters() now goes through it. It has no cache.
  • guild.channels.channelCountWithoutThreads counts the guild’s cached channels, threads excluded. It throws CacheNotIterable when the channel store cannot enumerate its entries.
  • guild.emojis.resolveIdentifier(emoji) returns the identifier used in reactions. Unlike discord.js, it throws for a value it cannot resolve. An uncached emoji ID still returns null.
  • client.guilds.widgetImageURL(guild, style?) returns the URL of a guild’s widget image. style defaults to GuildWidgetStyle.Shield.
  • client.guilds.fetchSoundboardSounds(guildIds, { time }) requests the soundboard sounds of several guilds over the gateway, caches them, and resolves with a Collection of each guild’s sounds, keyed by guild ID. It sends one request per shard, so the client must run every shard of the guilds. It rejects with a GuildSoundboardSoundsTimeoutError when no reply arrives for time milliseconds (10 seconds by default). Each reply restarts the timer. The replies carry no nonce, so only the process that sent the request resolves it.

Migrating from get and cached

Earlier releases cached nothing by default and exposed get and cached on each manager. Both are removed: client.presences.resolve(id) now takes a user ID instead of a cache key. Read a presence with guild.presences.cache.get(userId).

Bounding memory

With the default cache, nothing is evicted unless a dispatch removes it. You can bound it in four ways:
  • cacheOptions sets a maxSize per entity. Once the cache reaches it, CollectionCache evicts the oldest entry for each new one, and 0 holds nothing. The bound applies to the whole entity, so a busy channel can evict the messages of a quiet one.
  • policies.filter decides entry by entry. A rejected entry is not cached, and the entry already cached under its key is deleted.
  • cache: null caches nothing. cache.get returns undefined and every fetch hits the API.
  • sweepers evict entries on a timer.
cacheOptions also takes keepOverLimit(value, key, cache), like discord.js’s LimitedCollection. Once maxSize is reached, the cache evicts the oldest entry it returns false for, and grows past maxSize if it keeps them all. cacheWithLimits builds cacheOptions from plain numbers:
client.channels.cache spans the channel and thread caches. It is a Cache but not a Collection, and maxSize applies to channels and threads separately. Combining cacheConstructor or cacheOptions with cache or makeCache throws. With cache: null, both are ignored.

Sweepers

Like discord.js’s, sweepers evict the entries of a cache every interval seconds. Each entity takes either a lifetime in seconds or a filter factory that returns the predicate of that sweep, or null to skip it. Only invites, messages, and threads take a lifetime. A message is swept when it was last edited or created longer ago than that, a thread when it has been archived for longer, and an invite when it has expired for longer.
DefaultSweeperSettings sweeps messages untouched for 30 minutes and threads archived for four hours, every hour. Unlike discord.js’s, it is not empty. Every sweep emits cacheSweep(entity, swept). A failing filter emits cacheError with operation: "sweep" instead of throwing. client.destroy() stops the timers. Sweepers walk the caches of structure instances, so combining them with cache or makeCache throws. Set the ttl of policies for those stores instead.

Custom caches

cacheConstructor takes a class implementing Cache. The client instantiates it once per entity with (creator, name, options). options is a CacheConstructorOptions holding keyOf(data), the cache key of raw data, refresh(value), which resolves an instance’s relations again, and the entity’s cacheOptions. Extend CollectionCache to get all of this:
The cache must be synchronous. A cache that is not a Map cannot be enumerated, so it gets no dispatch cascades on GUILD_DELETE and CHANNEL_DELETE, no reconciliation of guilds left while offline, no granular emoji and sticker diff events, and no listCached.

Store-backed caches

cache and makeCache back the managers with raw @wolfstar/plugin-cache stores, in memory or Redis, instead of CollectionCache. Each read builds a new structure. makeCache takes precedence over cache, and returns a store per entity kind, or null not to cache it:
policies apply to every write in every mode, but ttl only applies to these stores. Every feature works without a store for an entity kind: every event is still emitted, and the previous state of update and delete events is null. Some features need a store. Without emojis or stickers, you only get guildEmojisUpdate and guildStickersUpdate instead of granular diff events. Without presences, presences.fetch rejects and presences.resolve returns null.

Synchronous reads

manager.cache methods return a value with the default CollectionCache and a promise with an asynchronous store such as Redis. manager.cache.synchronous tells them apart, so a hot path can skip await. await works either way:
Structures also have discord.js’s synchronous getters, which read the cache alone and never call the API:
  • GuildMember: permissions, permissionsIn(channel), manageable, kickable, bannable, moderatable, displayColor, and displayHexColor.
  • Message: editable, deletable, bulkDeletable, pinnable, crosspostable, and threadable. threadable is true when the bot can start a thread from the message: a text or announcement channel, no thread yet, and ViewChannel plus CreatePublicThreads. Unlike discord.js, an administrator bot can always start one.
  • Role: editable and permissionsIn(channel). Guild channels: permissionsFor(target).
  • GuildEmoji#deletable and GuildInvite#deletable.
When an entity a getter needs is missing from the cache, it throws a GatewayError with the code GuildUncached, GuildUncachedMe, ChannelUncached, or GuildMemberUncached. Uncached roles are skipped without an error, as in discord.js. See Checks on a cache miss. If your cache may lack an entity, for example because it is size-limited or filtered, fetch what is missing, then read the getter:
With an asynchronous store, the same getters return promises. Declare the cache asynchronous once so TypeScript types them as promises:
Without the declaration, the getters are typed as plain values. A promise is always truthy, so if (member.kickable) passes for every member.
Relation getters such as message.guild, member.voice, and channel.parent stay null with an asynchronous store and never throw. Use the entity fetches, such as fetchGuild(), fetchChannel(), and member.fetchPresence().

Checks on a cache miss

The getters skip a role that is not in the cache without an error. member.roles.highest returns the highest role the cache holds, and member.permissions and permissionsIn(channel) combine only the cached roles. With a filtered or size-limited cache, or on a plugin-broker worker, a role-hierarchy or permission check can under-report. manageable, kickable, bannable, and moderatable read roles.highest, so they under-report too. When a check must be right on a cache miss, use the calls that fetch the missing roles from the API. With a complete cache, they make no request:
  • member.roles.fetchHighest() instead of member.roles.highest.
  • member.fetchPermissions() instead of member.permissions.
  • member.fetchPermissionsIn(channel) instead of member.permissionsIn(channel). It also fetches the channel.
  • For manageable, kickable, bannable, and moderatable, call await member.roles.fetch() and await me.roles.fetch() for the bot’s member before you read the getter.

Deprecated fetch* twins

The other derived getters also have a fetch* twin that asks the API for what is not cached, such as member.fetchKickable() or message.fetchDeletable(). These twins are deprecated and will be removed in a later release, so use the getter instead. Message#fetchEditable() is removed. roles.fetchHighest(), fetchPermissions(), and fetchPermissionsIn() are not deprecated. Entity fetches such as fetchGuild, fetchMember, client.members.fetchMe, roles.fetch(), and member.fetchPresence() stay.

Requesting guild members

client.members.request(guildId, options), or guild.requestMembers(options), asks the guild’s shard for its members over the gateway instead of REST. By default, it requests every member. Pass query with a limit to search by name, or up to 100 userIds. Set presences: true to include presences.
The promise resolves with the GuildMembers once the last GUILD_MEMBERS_CHUNK for the request is cached, so client.members.cache.get sees them. It rejects with:
  • GuildMembersTimeoutError when no chunk arrives for time milliseconds (120 seconds by default).
  • GuildMembersRateLimitError when Discord answers with RATE_LIMITED. Its retryAfter holds the delay.
Every chunk is also emitted as guildMembersChunk. Its data.not_found lists the requested IDs that are not members. Requesting every member or a query needs the GuildMembers intent, and presences need the GuildPresences intent.

Resuming sessions across restarts

@discordjs/ws resumes a dropped connection within the same process. Without a sessionStore, every deploy or crash identifies every shard again, spends identify quota, misses the events sent meanwhile, and triggers a full GUILD_CREATE burst. A sessionStore keeps sessions outside the process, so the next process resumes them and Discord replays what it missed:
  • Pair it with a persistent cache. A resumed session only replays missed events, not guilds. With the default in-memory cache, a restarted process resumes with an empty cache that GUILD_CREATE never refills.
  • Shut down with destroy({ resumable: true }). By default, destroy() closes connections with code 1000, which makes Discord invalidate the sessions. With resumable, shards close with code 4200 and their sessions stay stored.
  • Reads never stall a shard. Each shard reads the store once when it first connects. A read that fails or takes longer than sessionStoreTimeout is reported as a GatewaySessionStoreError through error, and the shard identifies.
  • Writes run in the background. After a crash, the stored sequence may be a few dispatches behind. Discord replays those dispatches, so listeners can see them twice.
sessionStore replaces the gateway.retrieveSessionInfo and gateway.updateSessionInfo options. Passing either alongside it throws.

Structures

Structures extend the classes @discordjs/structures ships, such as User, Message, Emoji, Invite, Webhook, and every channel type. They add relations resolved from the cache, CDN URLs, and discord.js-style actions. Structures without a counterpart yet, such as Guild, GuildMember, and Role, extend its base Structure. Every structure has client, like discord.js’s Base#client: the client whose manager built it. It is not part of the structure’s data, so toJSON() never serializes it and the cache never stores it. Channels get one class per type (TextChannel, VoiceChannel, ForumChannel, PublicThreadChannel, DMChannel, and more), and instanceof Channel matches any of them:
Guilds, members, roles, channels, threads, messages, webhooks, and moderation structures follow discord.js’s API. Getters that discord.js reads from its cache, such as member.permissions or member.kickable, read manager.cache here too. See Synchronous reads. Position setters such as setPosition sort siblings the way Discord does. Every structure is valued by its ID through valueOf(), like discord.js’s Base. equals() compares structures the way discord.js v14 and v15 do. GuildChannel#equals, on every guild channel and thread, and SoundboardSound#equals are new, and both accept the structure or its raw payload.
equals() compares different fields than in earlier releases:
  • Message#equals(message, rawData?) compares the ID, author, content, nonce, tts, attachments by ID, and embeds through Embed#equals. With rawData, it also compares mention_everyone and the timestamps. pinned and the content of attachments are no longer compared.
  • Embed#equals deep-compares two embeds, and compares a raw embed field by field, so type and proxy URLs no longer matter.
  • Guild#equals no longer compares the AFK and system channels, content filter, MFA level, banner, description, or vanity code. It compares features in order.
  • GuildMember#equals also compares partial, banner, pending, the avatar decoration, and the nameplate, and compares roles in order.
  • Role#equals compares the three colors instead of color.
  • User#equals also compares the avatar decoration SKU, the nameplate, and the primary guild.
  • Sticker#equals also compares the type, pack, and sort value. GuildEmoji#equals and Sticker#equals compare fewer fields against raw data.
  • GuildChannel#equals compares the ID, type, name, topic, position, and permission overwrites in any order. It does not compare the category.
Guild, GuildMember, Sticker, GuildEmoji, GuildChannel, and SoundboardSound return false for a foreign argument instead of throwing. Role, User, Embed, and Message accept null and undefined. Update events such as messageUpdate do not rely on equals(), but emojiUpdate and stickerUpdate still do.

Members, roles, and users

member.roles and emoji.roles are discord.js’s GuildMemberRoleManager and GuildEmojiRoleManager. add, remove, and set take a Role, an ID, an array, or a Collection, and resolve to the updated member or emoji. Their cache is a Collection of the cached roles, @everyone included. member.roles also has highest, hoist, color, icon, premiumSubscriberRole, and botRole. Unlike discord.js, cache and these getters return a promise with an asynchronous store, so await them. highest is null when no role is cached.
With Partials.GuildMember, guildMemberUpdate, guildMemberRemove, and typingStart can hand out a member built from its IDs alone. Those payloads are typed as GuildMember | PartialGuildMember | null. Narrow them with member.isPartial() before passing them to code that expects a GuildMember. member.roles.partial reads member.partial. client.users accepts any UserResolvable: a User, a GuildMember, a ThreadMember, a Message for its author, or an ID. client.users.createDM(user) returns the cached direct message channel unless you pass { force: true }. client.users.dmChannel(user) and user.dmChannel read the cached channel, or null. client.users.deleteDM(user) throws UserNoDMChannel when no channel is cached. With cache: null or a Redis store, dmChannel is null and createDM always calls the API.
client.presences.resolve(value, guildId?) and resolveId(value) accept a PresenceResolvable: a Presence, a GuildMember, a User, a ThreadMember, a Message, or a user ID. resolve reads the cached presence of the user in the member’s, message’s, or given guild, and returns null on a miss. Structure, StructureMixin, initStructure, Mixin, and the kData, kPatch, and kClone symbols are exported, so you can subclass structures and write new mixins.

Application

client.application is a ClientApplication, like discord.js’s Client#application. Use it for what the application owns rather than a guild, such as its profile, owner, and role connection metadata. It lives on the client, not in the cache. Unlike discord.js, where it is null until READY, client.application is never null. The client builds it from clientId, so it exists before READY and without a gateway connection. Until you fetch or edit it, partial is true. Before READY, only id is known. READY patches the same instance with the application’s flags, so references you hold stay valid. fetch() calls GET /applications/@me and patches in everything else:
After a fetch, ClientApplication exposes tags, installParams, integrationTypesConfig, customInstallURL, botPublic, botRequireCodeGrant, guildId and the cached guild, approximateGuildCount, approximateUserInstallCount, interactionsEndpointURL, roleConnectionsVerificationURL, and the event webhook settings eventWebhooksURL, eventWebhooksStatus, and eventWebhooksTypes. owner is a Team when a team owns the application, with its members as TeamMembers. Otherwise it is the owner User. It is null until the application is fetched. Application, ClientApplication, Team, and TeamMember are exported.

Application commands

client.application.commands is an ApplicationCommandManager for global commands. Pass a guildId option to its methods to reach the commands of a guild. guild.commands, or client.guilds.commands(guildId) when you only hold the ID, is a GuildApplicationCommandManager for the commands of one guild. Each access returns a new manager. These managers are fetch-only. Discord sends no event when a command changes, so the commands are never cached. Every method calls the API and returns new ApplicationCommand structures. The managers have fetch, create, set, edit, and delete. create and edit accept camel case data, raw payloads, and builders, or anything with toJSON().
ApplicationCommand has the command’s fields, edit(), the set*() helpers, delete(), and permissions. command.equals(other) compares every field other has, options included, and command.equals(other, true) also compares the order of the options. It returns false for a foreign argument.
set(commands) replaces every command of the application, or of the guild, with the given ones. @wolfstar/http-framework registers the commands of your pieces with the same API call, so set() deletes the commands the framework registered. Nothing in this package calls set() for you. Use it only for commands the framework does not manage.
ApplicationCommandPermissionsManager manages the permissions of commands in a guild. It is client.application.commands.permissions (pass guild and command), guild.commands.permissions (pass command), and command.permissions. Without a guild, global command permissions throw GlobalCommandPermissions.
  • fetch and has use the bot’s authorization. For one command, they read the applicationCommandPermissions cache, which APPLICATION_COMMAND_PERMISSIONS_UPDATE keeps up to date. Pass force: true to fetch to skip the cache.
  • set, add, and remove need an OAuth2 bearer token of a user who can manage the guild, with the applications.commands.permissions.update scope. Discord rejects the bot’s token for these writes. Without token, they throw ApplicationCommandPermissionsTokenMissing. The package has no OAuth2 flow, so you obtain the token yourself.

Application emojis

client.application.emojis is an ApplicationEmojiManager for the emojis the application owns. The bot can use them in any guild. Discord sends no event for these emojis, so the manager is fetch-only and caches nothing. fetch() lists them in a Collection, and fetch(id) gets one. The manager also has create, edit, delete, and fetchAuthor. ApplicationEmoji extends Emoji with author, application, edit, setName, delete, and fetchAuthor. Unlike GuildEmoji, it has no roles, managed, available, or requiresColons, because the API does not send them. equals() compares only the ID, name, and animated.

Monetization

client.application.entitlements is an EntitlementManager, a cached manager like the others. ENTITLEMENT_CREATE, ENTITLEMENT_UPDATE, and ENTITLEMENT_DELETE keep entitlements.cache up to date and emit entitlementCreate, entitlementUpdate, and entitlementDelete. Processes that share a store share the cache. fetch takes an entitlement or its ID and reads the cache first, or takes filters and lists entitlements from the API:
createTest takes either a guild or a user. Passing both or neither throws EntitlementCreateInvalidOwner. client.application.fetchSKUs() resolves to a Collection of SKUs. Each SKU has flags, a SKUFlagsBitField, and subscriptions, the SubscriptionManager of that SKU. When you only hold the SKU’s ID, use client.application.subscriptions(skuId). Subscriptions are fetch-only and never cached, because nothing keeps them up to date for the bot:
Entitlement, SKU, and Subscription are exported. EntitlementResolvable, SKUResolvable, and SubscriptionResolvable accept the structures or their IDs.

Components

Message components are structures too, like in discord.js: ActionRow, InteractiveButtonComponent, LinkButtonComponent, the select menus, TextInputComponent, and the Components v2 layout classes such as ContainerComponent, SectionComponent, TextDisplayComponent, and MediaGalleryComponent. createComponent() builds the matching class from raw component data, and findComponentByCustomId() finds a component in a tree.

Messages and MessagePayload

Message follows discord.js, with reply, edit, delete, forward, pin, react, and more. As in discord.js, attachments, stickers, messageSnapshots, and reactions.cache are Collections keyed by ID. Reactions are keyed by the ID of a custom emoji or the name of a Unicode one. react() resolves to the MessageReaction, counting the bot. partial is true for a message that lacks its content or its author.
Message is generic like discord.js’s. Message<true> has a guildId string and a guild text-based channel, and inGuild() narrows to it. reply, edit, and the other actions resolve to messages whose channel is never a group DM. message.fetch(false) answers from the cache. Text channels have send and sendTyping. Guild text-based channels also have bulkDelete, which takes messages, their IDs, a Collection, or a count. Direct message channels do not have it, as in discord.js. bulkDelete resolves to a Collection of the deleted messages by ID. Each value is the cached Message, a PartialMessage with Partials.Message, or undefined. Narrow a Message | PartialMessage with partial:
channel.messages is one ChannelMessageManager for every kind of channel, where discord.js has a manager per kind. In a direct message or group direct message, its guild-only methods crosspost, bulkDelete, and startThread throw ChannelMessagesGuildOnly before any request. The other methods work in every channel. crosspost takes a message or its ID. client.channels.createMessage(channel, options) sends a message to a channel or its ID, like discord.js. client.messages.send(channelId, options) and channel.send(options) do the same.

Creating threads

channel.threads.create(options) takes ThreadCreateOptions, which is TextThreadCreateOptions or ForumThreadCreateOptions. Pass type and invitable for a thread in a text or announcement channel. Pass message and appliedTags for a post in a forum or media channel. name, autoArchiveDuration, and rateLimitPerUser apply to both. Mixing the two is a compile error, and create throws a GatewayTypeError before any request:
  • ThreadOptionsPostOnly for appliedTags without message, or message in a text or announcement channel.
  • ThreadOptionsThreadOnly for type or invitable together with message.
  • ThreadPostMessageRequired for a forum or media channel without message.
To start a thread from an existing message, call message.startThread(). Unlike discord.js, an explicit type is not overridden in an announcement channel.

Searching guild messages

guild.searchMessages(options), or client.guilds.searchMessages(guildId, options), runs Discord’s guild message search. Options include content, authorIds, channelIds, mentions, has, pinned, sortBy, sortOrder, limit, and offset. The ID options take the usual resolvables. The result holds Collections of messages, threads, and threadMembers, plus totalResults. Results are cached unless you pass cache: false.
While Discord indexes the guild, the search waits the retry_after delay and retries. Pass retryOnMissingIndex: false to throw SearchIndexNotYetAvailable instead, and signal to cancel. A query over Discord’s documented limits throws a GatewayRangeError before the request. MessagePayload normalizes message options into the REST body and files Discord expects, like discord.js’s class of the same name. It accepts camel case options, builders, reply and forward shortcuts, stickers, polls, and files from paths, URLs, streams, or buffers:

AttachmentBuilder

Files to send are AttachmentBuilders, as in discord.js v14. new AttachmentBuilder(file, { name, description }) takes a buffer, a path, a URL, a stream, or a blob. These methods chain:
  • setFile(file, name?), setName(name), and setDescription(description).
  • setSpoiler(spoiler = true) adds or removes the SPOILER_ prefix of the name. The spoiler getter reads it.
  • setTitle(title), plus setDuration(seconds) and setWaveform(waveform) for voice messages.
Pass builders in the files of a message. AttachmentBuilder.from(other) copies a builder, an attachment payload, or a received Attachment. Attachment#attachment returns the URL of a received attachment, as in discord.js, so you can send it again:

Resolvable types

Methods accept discord.js’s Resolvable types, so you can pass either a structure or an ID. Examples include UserResolvable, GuildResolvable, ChannelResolvable, RoleResolvable, MessageResolvable, EmojiResolvable, EmojiIdentifierResolvable, BufferResolvable, AttachmentResolvable, and MessagePayloadResolvable.

Utilities

The package exports the discord.js helpers its structures use:
  • parseEmoji(text) parses a Unicode emoji, a custom emoji mention such as <a:name:id>, or a name:id pair. It returns null when the string holds a colon but no custom emoji.
  • resolvePartialEmoji(emoji) resolves any EmojiIdentifierResolvable without a cache lookup. A bare ID resolves to { id }.
  • resolveGuildEmoji(client, emojiId) finds a custom emoji by ID across every cached guild, like discord.js’s resolveGuildEmoji. It resolves to the GuildEmoji, or null when no cached guild has it. It never calls the API. It enumerates the whole emoji cache, so it needs a store that can list its entries, and throws a TypeError when the store cannot.
  • DataResolver functions: resolveFile, resolveBase64, resolveImage, resolveInviteCode, and resolveGuildTemplateCode.
  • Transformers such as toSnakeCase and the transformAPI* functions convert between API payloads and discord.js-style camel case objects.
  • Util functions such as resolveColor, cleanContent, parseWebhookURL, discordSort, and transformResolved.
  • transformResolved({ client, guildId, channelId }, data) resolves the users, members, roles, channels, messages, and attachments of data such as an interaction’s resolved into maps of structures. It writes nothing to the cache. Message IDs are read from the cache of the channelId channel, so without channelId only raw messages are resolved. Raw messages resolve to the cached message or are built from the data. Attachments are always built as Attachments.

Errors

Like discord.js’s DiscordjsError, every error the package throws or emits carries a code from GatewayErrorCodes, and its message comes from GatewayErrorMessages. GatewayError, GatewayTypeError, and GatewayRangeError extend Error, TypeError, and RangeError, and are named after their code, for example GatewayError [WebhookTokenUnavailable].
These errors extend GatewayError and carry extra data: The client reports dispatch, cache, and session store failures through its error event. Codes thrown by the application, monetization, and message APIs include:

Limitations

  • A GatewayClient connects its shards from a single process. You can set @discordjs/ws’s WorkerShardingStrategy through gateway.buildStrategy.
  • Interaction payloads are handled by the HTTP endpoint as before. They do not read through client.users and the other managers.