npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

djs-forge

v1.0.0

Published

The missing toolkit for discord.js — Interaction routing, pagination, cooldowns, confirmations, embed presets, webhooks and full Discord API coverage.

Downloads

18

Readme

djs-forge 🔨

The missing toolkit for discord.js — Interaction routing, pagination, cooldowns, confirmations, embed presets, enhanced webhooks and full Discord API coverage.

npm version discord.js license


Why djs-forge?

discord.js is amazing — but every bot project ends up copy-pasting the same boilerplate:

| Pain point | djs-forge solution | |---|---| | Giant if/else chains for component customIds | InteractionRouter with strings, globs and regex | | Building pagination from scratch every project | Paginator — one line to paginate anything | | Reinventing cooldown handling every time | CooldownManager with per-user, guild, channel, global scopes | | await confirm() that works reliably | ConfirmationManager | | Webhook URL sending without a WebhookClient | WebhookManager with auto-batching | | Rebuilding the same success/error embed styles | EmbedPresets | | Forgetting Discord timestamp format codes | Timestamp.relative(date), .full(date), etc. | | Soundboard / Poll / Monetization APIs not in djs | All covered via sub-managers |


Installation

npm install djs-forge
# or
yarn add djs-forge
# or
pnpm add djs-forge

Peer dependency: discord.js >= 14.0.0


Quick Start

import { Client, GatewayIntentBits } from 'discord.js';
import { DjsForge, EmbedPresets } from 'djs-forge';

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const forge  = new DjsForge(client);

// Route ALL component interactions — no more if/else
forge.router
  .on('open_ticket',            async (i) => { /* ... */ })
  .on('close_ticket',           async (i) => { /* ... */ })
  .on(/^ban_(?<userId>\d+)$/,   async (i, { userId }) => { /* ... */ })
  .on('page_*',                 async (i, { wildcard }) => { /* ... */ })
  .attach(client);

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  // Cooldown: 10s per user
  try {
    forge.cooldowns.scope.user('ping', interaction, 10_000);
  } catch {
    return interaction.reply({ content: '⏳ You are on cooldown!', ephemeral: true });
  }

  // Reply with a preset embed
  await interaction.reply({ embeds: [EmbedPresets.success('Pong!', 'Everything is working.')] });
});

client.login('TOKEN');

Sub-path Imports

For tree-shaking and clarity, every feature has its own sub-path:

import { InteractionRouter }   from 'djs-forge/routing';
import { Paginator }           from 'djs-forge/pagination';
import { CooldownManager }     from 'djs-forge/cooldowns';
import { ConfirmationManager } from 'djs-forge/confirmations';
import { WebhookManager }      from 'djs-forge/webhooks';
import { EmbedPresets, Timestamp, Perms, Strings, Mention } from 'djs-forge/utils';

InteractionRouter

import { InteractionRouter } from 'djs-forge/routing';

const router = new InteractionRouter();

// Exact match
router.on('open_ticket', async (interaction) => {
  await interaction.reply('Ticket opened!');
});

// Wildcard glob  →  "page_*"  matches  "page_1", "page_next", "page_abc"
router.on('page_*', async (interaction, { wildcard }) => {
  await interaction.reply(`You pressed page ${wildcard}`);
});

// Named-group regex  →  extract dynamic IDs
router.on(/^confirm_delete_(?<itemId>\d+)$/, async (interaction, { itemId }) => {
  await deleteItem(itemId);
  await interaction.reply('Deleted!');
});

// Fallback — fires when nothing matches
router.fallback(async (interaction) => {
  await interaction.reply({ content: 'Unknown action.', ephemeral: true });
});

// Attach to your client (one line)
router.attach(client);

Paginator

import { Paginator } from 'djs-forge/pagination';

// Any array of message payloads
const pages = Array.from({ length: 10 }, (_, i) => ({
  embeds: [new EmbedBuilder().setTitle(`Page ${i + 1}`).setDescription(`Content ${i + 1}`)]
}));

const paginator = new Paginator(pages, {
  timeout:       120_000,   // 2 minutes
  showPageCount: true,      // shows "3 / 10" button
  ephemeral:     false,
});

// Reply to a slash command
await paginator.reply(interaction);

// Or send to a channel
await paginator.send(channel, interaction.user.id);

CooldownManager

import { CooldownManager } from 'djs-forge/cooldowns';

const cooldowns = new CooldownManager();

// In your command handler:

// Option 1: throw-based (simplest)
try {
  cooldowns.scope.user('ban', interaction, 10_000);   // 10s per user
} catch (err) {
  return interaction.reply({ content: `⏳ ${err.message}`, ephemeral: true });
}

// Option 2: check-then-set
const result = cooldowns.check('ban', interaction.user.id, 10_000);
if (result.onCooldown) {
  return interaction.reply({ content: `⏳ Wait **${result.remainingText}**`, ephemeral: true });
}
cooldowns.set('ban', interaction.user.id, 10_000);

// Scopes: .scope.user | .scope.guild | .scope.channel | .scope.global

ConfirmationManager

import { ConfirmationManager } from 'djs-forge/confirmations';

const confirm = new ConfirmationManager({
  confirmLabel: '🗑️ Delete',
  cancelLabel:  'Cancel',
  timeout:      20_000,
});

// In a slash command:
let confirmed;
try {
  confirmed = await confirm.ask(interaction, {
    content: '⚠️ Are you sure you want to **delete all data**? This cannot be undone.',
  });
} catch {
  return; // Timed out — message is already updated
}

if (!confirmed) return; // User clicked Cancel — message is already updated

// Proceed with deletion...
await interaction.editReply({ content: '✅ All data deleted.', components: [] });

WebhookManager

import { WebhookManager } from 'djs-forge/webhooks';

const webhooks = new WebhookManager(client);
const url = process.env.LOG_WEBHOOK_URL;

// Send a message
await webhooks.send(url, {
  username: 'Logger',
  content:  `✅ User ${user.tag} was banned.`,
});

// Send to a thread
await webhooks.send(url, { content: 'Thread update' }, { threadId: '1234567890' });

// Auto-batch many embeds (never crashes on the 10-embed limit)
await webhooks.sendBatch(url, { embeds: fiftyEmbeds });

// Edit a sent message
const msg = await webhooks.send(url, { content: 'loading...' });
await webhooks.edit(url, msg.id, { content: 'done!' });

Utils

import { EmbedPresets, Timestamp, Perms, Strings, Mention } from 'djs-forge/utils';

// EmbedPresets
interaction.reply({ embeds: [EmbedPresets.success('Done!', 'User was banned.')] });
interaction.reply({ embeds: [EmbedPresets.error('Failed', 'Missing permissions.')] });
interaction.reply({ embeds: [EmbedPresets.warning('Warning', 'This is irreversible.')] });
interaction.reply({ embeds: [EmbedPresets.info('Info', 'Bot version: 2.0.0')] });

// Timestamps (no more Googling the format)
`Joined: ${Timestamp.relative(member.joinedAt)}`    // "3 days ago"
`Created: ${Timestamp.full(guild.createdAt)}`        // "15 January 2025 14:30"
`Expires: ${Timestamp.time(expiryDate)}`             // "14:30"

// Permission checks
if (!Perms.botHas(interaction, ['BanMembers'])) {
  return interaction.reply(Perms.missingText(interaction, ['BanMembers']));
}

// Strings
Strings.truncate(longText, 256)         // "Hello world…"
Strings.codeblock(code, 'js')           // ```js\n...\n```
Strings.chunk(veryLongText, 2000)       // ['chunk1', 'chunk2', ...]
Strings.plural(1, 'apple')              // "apple"
Strings.plural(5, 'apple')              // "apples"

// Mentions
`Welcome ${Mention.user(userId)}!`
`Check ${Mention.channel(channelId)}`
`Use ${Mention.command('ban', commandId)}`

Discord API Managers

These cover Discord API endpoints not yet in discord.js:

import { DjsForge } from 'djs-forge';
const forge = new DjsForge(client);

// Soundboard
const sounds = await forge.soundboard.getGuildSounds(guildId);
await forge.soundboard.sendSound(channelId, soundId);

// Polls
const poll = await forge.polls.create(channelId, {
  question: 'Best language?',
  answers: [{ text: 'JavaScript' }, { text: 'TypeScript' }, { text: 'Rust' }],
  duration: 24,
});

// Monetization
const skus = await forge.monetization.getSkus();
const entitlements = await forge.monetization.getEntitlements({ userId: '123' });

// Onboarding
await forge.onboarding.edit(guildId, { enabled: true, mode: 0 });

// Voice Effects
await forge.voiceEffects.setEffect(channelId, { effectId: 'some_effect_id' });
await forge.voiceEffects.clearEffect(channelId);

// Super Reactions
const reactors = await forge.superReactions.getBurstReactors(channelId, messageId, '⚡');

CJS Support

// CommonJS
const { DjsForge, EmbedPresets } = require('djs-forge');
const { InteractionRouter }      = require('djs-forge/routing');
const { Paginator }              = require('djs-forge/pagination');
const { CooldownManager }        = require('djs-forge/cooldowns');
const { ConfirmationManager }    = require('djs-forge/confirmations');

Error Handling

All errors thrown by djs-forge are ForgeError instances with a .code property:

import { ForgeError, ForgeErrorCode } from 'djs-forge';

try {
  await forge.polls.create(channelId, { ...tooManyAnswers });
} catch (err) {
  if (err instanceof ForgeError) {
    console.error(err.code);       // 'POLL_TOO_MANY_ANSWERS'
    console.error(err.message);    // '[djs-forge/POLL_TOO_MANY_ANSWERS] Poll can have at most 10 answers.'
    console.error(err.httpStatus); // null (not an HTTP error)
  }
}

License

MIT — See LICENSE