Runtime Hooks
Plugins can hook specific runtime paths. Use these only when your package needs framework-level behavior.
Autocomplete
api.autocomplete.wrap(...) wraps command option autocomplete execution.
import { createPlugin } from 'seyfert';
import { metrics } from './metrics';
export const metricsPlugin = createPlugin({
name: 'autocomplete-metrics',
register(api) {
// wrap autocomplete execution to time it
api.autocomplete.wrap(async ({ command, interaction, optionsResolver }, next) => {
const startedAt = Date.now();
try {
// run the original autocomplete handler
await next();
} finally {
// record metrics whether or not it threw
metrics.recordAutocomplete({
command: command?.name,
fullCommandName: optionsResolver.fullCommandName,
interactionId: interaction.id,
duration: Date.now() - startedAt,
});
}
});
},
});Wrappers compose around the original autocomplete handler. Call next() when the original autocomplete logic should continue.
Gateway Intents
Plugins can contribute gateway intents.
register(api) {
// contribute gateway intents
api.gateway.addIntents('Guilds');
}Intent names are resolved from GatewayIntentBits. Numeric values are also accepted.
Gateway Send Payload Wrappers
api.gateway.wrapSendPayload(...) runs before gateway send payloads leave the client.
register(api) {
// intercept payloads before they leave the client
api.gateway.wrapSendPayload(({ payload, shardId }) => {
// return null to consume the payload and stop sending it
if (shouldDropHeartbeat(payload, shardId)) return null;
// return a payload to replace the current one
return {
...payload,
d: patchPayload(payload.d),
};
});
}Return behavior:
- return a payload to replace the current payload
- return
undefinedto keep the current payload - return
nullto consume the payload and stop sending it (agateway-send-payload-vetodiagnostic is recorded)
This is a low-level hook. Prefer normal events or shared state unless your package specifically needs to modify gateway send behavior.
Inbound Dispatch Interceptors
api.gateway.onDispatch(...) sees inbound gateway packets before cache and events process them. Call next(packet) to continue the chain, or return null to veto the packet. It returns a disposer.
register(api) {
// see inbound packets before cache and events; returns a disposer
const dispose = api.gateway.onDispatch((packet, next, meta) => {
metrics.increment('gateway.dispatch', {
event: packet.t ?? 'unknown',
shardId: meta.shardId,
});
// continue the chain (return null instead to veto the packet)
return next(packet);
});
}Return null and the packet is dropped before cache and events ever run, with a gateway-dispatch-veto diagnostic recorded — so a dropped packet is a decision, not a mystery.
REST Observers
api.rest.observe(...) watches every REST request with read-only payloads. Observers stack, so multiple plugins can instrument REST without clobbering each other. It returns a disposer.
register(api) {
// watch every REST request with read-only payloads
api.rest.observe({
onRequest({ method, url }) { /* about to fire */ },
onSuccess({ method, url, response }) {
metrics.increment('rest.success', { method, url, status: response.status });
},
onFail({ method, url, error }) { /* it didn't go well */ },
onRatelimit({ route }) { /* Discord said slow down */ },
});
}A throwing observer is isolated — it doesn't take down the others. Ordering is passed through the options object: api.rest.observe(observer, { order: PluginOrder.Before }).
Language Overlays
api.langs.contribute(...) merges translations under a namespace prefix, so an i18n package can ship its own keys without colliding with the app's.
register(api) {
// merge translations under a namespace prefix to avoid collisions
api.langs.contribute('en-US', { balance: 'Balance' }, { prefix: 'plugins.economy' });
}App code then reads ctx.t.plugins.economy.balance.
Upload Commands Metadata
Plugins can observe command uploads through the custom uploadCommands event.
register(api) {
// observe command uploads via the custom uploadCommands event
api.events.on('uploadCommands', (metadata) => {
metadata.applicationId;
metadata.commands;
metadata.scope; // "global" or "guild"
metadata.status; // "uploaded" or "skipped"
metadata.reason; // "cache-hit", "cache-miss", or "forced"
});
}This is useful for metrics, audit logs, or deployment tooling.