Free Hosting

Reducing Memory Usage in discord.js Bots

Practical techniques to cut a discord.js bot's memory — cache limits, sweepers, intents, partials, avoiding leaks and capping the Node.js heap — with code for v14.

On this page
  1. Measure first
  2. 1. Request fewer intents
  3. 2. Limit caches with makeCache
  4. 3. Sweep old entries
  5. 4. Use partials instead of caching everything
  6. 5. Don’t fetch everything
  7. 6. Store IDs, not objects
  8. 7. Fix the classic leaks
  9. 8. Cap the heap
  10. Before and after
  11. When to stop optimising
  12. Summary

discord.js is fast and full-featured, and by default it caches a lot. That’s convenient in development and expensive in production, especially on a small plan. The good news: a few configuration changes can cut memory dramatically without changing how your bot behaves. This guide covers them for discord.js v14.

Measure first

Know your baseline before changing anything. Log memory periodically:

setInterval(() => {
  const m = process.memoryUsage();
  console.log(`rss=${(m.rss / 1048576).toFixed(0)}MB heap=${(m.heapUsed / 1048576).toFixed(0)}MB`);
}, 5 * 60 * 1000);

rss is what counts against your plan’s limit. heapUsed is JavaScript objects — mostly caches and your data. Let the bot run for an hour before judging; caches fill up gradually.

1. Request fewer intents

Every intent brings in more events, and many populate caches. Start from the minimum:

const client = new Client({
  intents: [GatewayIntentBits.Guilds],   // add others only when a feature needs them
});

The worst offender is GuildPresences: in large servers it streams every member’s status changes and fills the presence cache. If you don’t use presences, don’t request the intent. GuildMembers is the next heaviest. See gateway intents explained.

2. Limit caches with makeCache

makeCache controls the size of each manager’s cache. Set limits for anything your bot doesn’t need:

const { Client, GatewayIntentBits, Options } = require('discord.js');

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
  makeCache: Options.cacheWithLimits({
    ...Options.DefaultMakeCacheSettings,
    MessageManager: 25,            // keep 25 recent messages per channel
    PresenceManager: 0,            // no presence cache
    ReactionManager: 0,
    ReactionUserManager: 0,
    GuildStickerManager: 0,
    GuildScheduledEventManager: 0,
    ThreadMemberManager: 0,
    GuildMemberManager: {
      maxSize: 200,
      keepOverLimit: (member) => member.id === member.client.user.id,
    },
    UserManager: {
      maxSize: 500,
      keepOverLimit: (user) => user.id === user.client.user.id,
    },
  }),
});

Always keep your own bot’s member and user objects (keepOverLimit) — some internals expect them. Leave managers that your features rely on at their defaults: VoiceStateManager for music bots, MessageManager for bots that log edits and deletions, GuildMemberManager for bots that work heavily with members.

Some managers can’t be limited safely — notably the guild and channel caches, which discord.js needs to function. Don’t set those to zero.

3. Sweep old entries

Limits cap cache size; sweepers remove stale entries on an interval, keeping caches small even under the cap:

sweepers: {
  ...Options.DefaultSweeperSettings,
  messages: {
    interval: 3600,     // every hour
    lifetime: 1800,     // remove messages older than 30 minutes
  },
  users: {
    interval: 3600,
    filter: () => (user) => user.id !== user.client.user.id && !user.bot,
  },
},

Message sweeping is the single most effective setting for bots in active servers.

4. Use partials instead of caching everything

Partials let discord.js deliver events for objects that aren’t cached, instead of dropping them. That means you can keep caches small and still receive, say, reactions on old messages:

const { Partials } = require('discord.js');

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessageReactions],
  partials: [Partials.Message, Partials.Reaction, Partials.Channel],
});

client.on('messageReactionAdd', async (reaction) => {
  if (reaction.partial) await reaction.fetch().catch(() => null);
  // ...
});

Fetch only when you actually need the full object.

5. Don’t fetch everything

Calling guild.members.fetch() with no arguments for every guild at startup loads every member of every server into memory at once. Fetch on demand — a single member by ID when a command needs it — and let the cache limits clean up afterwards.

6. Store IDs, not objects

When you keep your own collections — cooldowns, active games, queues — store IDs and small plain values, not discord.js objects:

// Heavy: keeps the whole member object (and its references) alive
cooldowns.set(member.id, member);

// Light: just what you need
cooldowns.set(member.id, Date.now());

Holding a discord.js object in your own map also stops cache limits and sweepers from freeing it.

7. Fix the classic leaks

A bot whose memory climbs forever usually has one of these:

  • Listeners registered inside commands. client.on(...) in a command handler adds a new listener on every use. Node.js warns with MaxListenersExceededWarning — take it seriously.
  • Collectors without limits. Always give message and component collectors a time or max, and stop them when done.
  • Intervals that are never cleared. An interval created per game or per command must be cleared when that game ends.
  • Maps that only grow. Add expiry, or move the data into a database.

To find a leak, take two heap snapshots a while apart with require('node:v8').writeHeapSnapshot() and compare them in Chrome DevTools’ Memory tab. The objects that multiplied are your leak.

8. Cap the heap

Node.js decides how much heap it may use based on system memory, which inside a container may be more than your plan allows. Setting a heap cap below your plan’s limit makes the garbage collector work harder before memory hits the ceiling:

node --max-old-space-size=180 index.js

On a 256 MB plan, 160–190 MB leaves room for the rest of the process. On a 512 MB plan, try around 380. The cap doesn’t reduce what your bot needs — if it genuinely needs more, it will crash with a heap out-of-memory error, which at least tells you clearly. Set it in your start command or package.json start script.

Before and after

Applying intents, cache limits and message sweeping to a typical moderation bot commonly cuts steady-state memory substantially — often enough to move a bot that was straining on 256 MB into comfortable territory. Results depend on your servers and features, so measure again after each change.

When to stop optimising

If your bot legitimately needs large caches — heavy member management, full message logging in big servers — trimming them breaks features. At that point, more RAM is the right answer. Kerit Cloud’s Discord bot plans step from 256 MB (Free) to 512 MB (Basic), 1 GB (Pro) and beyond, and upgrades happen in place. How much RAM does a Discord bot need? has typical numbers by bot type.

Summary

Cut discord.js memory by requesting only the intents you use, limiting caches with makeCache, sweeping old messages and users, using partials instead of caching everything, fetching on demand, storing IDs rather than objects, fixing listener and collector leaks, and capping the heap with --max-old-space-size. Measure before and after each change, and upgrade when your features genuinely need the memory.