npm
Source
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 asdevsnapshots that require Node.js 24.17. @wolfstar/http-framework^3.4.0or^5.0.0as a peer dependency.@wolfstar/plugin-cacheif 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.
gateway key of stars.config for future options,
so no other module can claim it. You still construct the GatewayClient yourself.
Usage
Create aGatewayClient 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:
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:
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 emitsdispatch 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.
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:
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.
forwardGatewayDispatchesmarks the client as forwarding, whichclient.forwardsDispatchesreports. - Otherwise, the listener runs on the gateway client itself, so it also works on a bot that does not use the broker.
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.
Caching
Every manager exposes its cache asmanager.cache, the Cache of
discord.js RFC #11426. manager.fetch(...) reads the cache first
and falls back to the REST API:
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:
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.getonly reads the cache and returnsundefinedon a miss.fetchreads 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.refreshisfetchwith{ force: true }.resolvetakes a structure, returned as is, or a cache key, like discord.js’sresolve.resolveIdreturns the ID.guild.members.mereturns the bot’s own member from the cache, like discord.js. It isnullwhen the member is not cached. Without aGuild, callclient.members.me(guildId).client.members.fetchMe(guildId)falls back to the API.manager.valueOf()returnsmanager.cache, like discord.js’sDataManager#valueOf.
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, socache 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:
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.childrenis aCategoryChannelChildManager. Itscachelists the cached channels of the category, and is a promise with an asynchronous store.create(options)makes a channel inside the category, andresolve()andresolveId()look one up.cacheis read from the channel store on each access, so a store that cannot enumerate its entries throwsCacheNotIterable.create()rejects a category as the channel type.answer.votersis aPollAnswerVoterManager.fetch()lists the users who voted for the poll answer, andanswer.fetchVoters()now goes through it. It has nocache.guild.channels.channelCountWithoutThreadscounts the guild’s cached channels, threads excluded. It throwsCacheNotIterablewhen 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 returnsnull.client.guilds.widgetImageURL(guild, style?)returns the URL of a guild’s widget image.styledefaults toGuildWidgetStyle.Shield.client.guilds.fetchSoundboardSounds(guildIds, { time })requests the soundboard sounds of several guilds over the gateway, caches them, and resolves with aCollectionof 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 aGuildSoundboardSoundsTimeoutErrorwhen no reply arrives fortimemilliseconds (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:cacheOptionssets amaxSizeper entity. Once the cache reaches it,CollectionCacheevicts the oldest entry for each new one, and0holds nothing. The bound applies to the whole entity, so a busy channel can evict the messages of a quiet one.policies.filterdecides entry by entry. A rejected entry is not cached, and the entry already cached under its key is deleted.cache: nullcaches nothing.cache.getreturnsundefinedand everyfetchhits the API.sweepersevict 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:
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:
GuildMember:permissions,permissionsIn(channel),manageable,kickable,bannable,moderatable,displayColor, anddisplayHexColor.Message:editable,deletable,bulkDeletable,pinnable,crosspostable, andthreadable.threadableistruewhen the bot can start a thread from the message: a text or announcement channel, no thread yet, andViewChannelplusCreatePublicThreads. Unlike discord.js, an administrator bot can always start one.Role:editableandpermissionsIn(channel). Guild channels:permissionsFor(target).GuildEmoji#deletableandGuildInvite#deletable.
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:
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 ofmember.roles.highest.member.fetchPermissions()instead ofmember.permissions.member.fetchPermissionsIn(channel)instead ofmember.permissionsIn(channel). It also fetches the channel.- For
manageable,kickable,bannable, andmoderatable, callawait member.roles.fetch()andawait 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.
GuildMembers once the last GUILD_MEMBERS_CHUNK for the request is cached, so
client.members.cache.get sees them. It rejects with:
GuildMembersTimeoutErrorwhen no chunk arrives fortimemilliseconds (120 seconds by default).GuildMembersRateLimitErrorwhen Discord answers withRATE_LIMITED. ItsretryAfterholds the delay.
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_CREATEnever refills. - Shut down with
destroy({ resumable: true }). By default,destroy()closes connections with code1000, which makes Discord invalidate the sessions. Withresumable, shards close with code4200and 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
sessionStoreTimeoutis reported as aGatewaySessionStoreErrorthrougherror, 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:
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.
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.
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:
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.
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.
fetchandhasuse the bot’s authorization. For one command, they read theapplicationCommandPermissionscache, whichAPPLICATION_COMMAND_PERMISSIONS_UPDATEkeeps up to date. Passforce: truetofetchto skip the cache.set,add, andremoveneed an OAuth2 bearertokenof a user who can manage the guild, with theapplications.commands.permissions.updatescope. Discord rejects the bot’s token for these writes. Withouttoken, they throwApplicationCommandPermissionsTokenMissing. 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:
ThreadOptionsPostOnlyforappliedTagswithoutmessage, ormessagein a text or announcement channel.ThreadOptionsThreadOnlyfortypeorinvitabletogether withmessage.ThreadPostMessageRequiredfor a forum or media channel withoutmessage.
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.
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), andsetDescription(description).setSpoiler(spoiler = true)adds or removes theSPOILER_prefix of the name. Thespoilergetter reads it.setTitle(title), plussetDuration(seconds)andsetWaveform(waveform)for voice messages.
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’sResolvable 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 aname:idpair. It returnsnullwhen the string holds a colon but no custom emoji.resolvePartialEmoji(emoji)resolves anyEmojiIdentifierResolvablewithout 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’sresolveGuildEmoji. It resolves to theGuildEmoji, ornullwhen 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 aTypeErrorwhen the store cannot.- DataResolver functions:
resolveFile,resolveBase64,resolveImage,resolveInviteCode, andresolveGuildTemplateCode. - Transformers such as
toSnakeCaseand thetransformAPI*functions convert between API payloads and discord.js-style camel case objects. - Util functions such as
resolveColor,cleanContent,parseWebhookURL,discordSort, andtransformResolved. transformResolved({ client, guildId, channelId }, data)resolves theusers,members,roles,channels,messages, andattachmentsof data such as an interaction’sresolvedinto maps of structures. It writes nothing to the cache. Message IDs are read from the cache of thechannelIdchannel, so withoutchannelIdonly raw messages are resolved. Raw messages resolve to the cached message or are built from the data. Attachments are always built asAttachments.
Errors
Like discord.js’sDiscordjsError, 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].
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
GatewayClientconnects its shards from a single process. You can set@discordjs/ws’sWorkerShardingStrategythroughgateway.buildStrategy. - Interaction payloads are handled by the HTTP endpoint as before. They do not read through
client.usersand the other managers.