Set your bot's status and presence

Give your bot a "Playing", "Watching 5 servers", or "Listening to /help" status — set once when it's ready, or rotate through several on a timer. discord.js and discord.py, with the honest limits.

That little line under a bot's name — Playing with slash commands, Watching 5 servers, Listening to /help — is its presence, and setting it is a nice, quick win. This guide sets a presence the moment your bot is ready, walks the five activity types, and rotates through several on a timer. The main code is discord.js; the discord.py equivalent is at the end.

At a glance
You need a working bot (see the discord.js or discord.py guide)
Plan Free or premium — but see the honest note below about when the status shows
Time about fifteen minutes

⚠️ Heads up: A presence only shows while your bot is actually running and connected. It isn't stored on Discord's side — the moment the process stops, the status goes with it. On the free plan, when your session timer runs out and the server stops, the bot goes offline and the status disappears until you start it again. Premium's 24/7 is what keeps a status up around the clock.

The five activity types

The type decides the verb Discord shows before your text:

Type Shows as Notes
ActivityType.Playing Playing your text The default, most-used one
ActivityType.Watching Watching your text Great for "Watching 5 servers"
ActivityType.Listening Listening to your text "Listening to /help"
ActivityType.Streaming Streaming your text Needs a Twitch/YouTube url to turn the name purple
ActivityType.Competing Competing in your text Less common, but there

There's also ActivityType.Custom for a free-form status line — handy, but the four above are what most bots use.

Set it when the bot is ready

Presence is set on client.user, which only exists after login — so the ClientReady handler is exactly the right place. Import ActivityType alongside your other names and set an initial presence:

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

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

client.once(Events.ClientReady, (c) => {
  c.user.setPresence({
    activities: [{ name: '/help', type: ActivityType.Watching }],
    status: 'online',
  });
  console.log(`Listening as ${c.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

setPresence takes an activities array (Discord shows the first) and a status. The four statuses are online, idle (yellow moon), dnd (red, "Do Not Disturb"), and invisible (appears offline but still works). For just the activity, c.user.setActivity('/help', { type: ActivityType.Watching }) is the shorthand.

🎯 Good to know: Set presence in ClientReady, not at the top of the file. Before login, client.user is null — try to set a status there and you'll crash on startup.

Rotate through several statuses

A rotating status keeps things lively and is a natural place to show live numbers — like how many servers the bot is in. Add a setInterval inside ClientReady:

client.once(Events.ClientReady, (c) => {
  const statuses = [
    { name: 'with slash commands', type: ActivityType.Playing },
    () => ({ name: `${c.guilds.cache.size} servers`, type: ActivityType.Watching }),
    { name: '/help', type: ActivityType.Listening },
  ];

  let i = 0;
  const rotate = () => {
    const entry = statuses[i % statuses.length];
    const activity = typeof entry === 'function' ? entry() : entry;
    c.user.setActivity(activity.name, { type: activity.type });
    i++;
  };

  rotate();                       // set the first one immediately
  setInterval(rotate, 30_000);    // then change every 30 seconds
  console.log(`Listening as ${c.user.tag}`);
});

Each entry is either a fixed { name, type } or a function that's called fresh each rotation — that's how ${c.guilds.cache.size} servers stays current as the bot joins new servers.

💡 Tip: Don't rotate faster than every ~15 seconds. Presence updates are rate-limited, and a status flickering every second just burns your budget with Discord — see Discord rate limits. Thirty seconds looks lively and stays well clear of the limit.

The discord.py equivalent

Same idea: build an Activity and set it once the bot is ready. The activity classes map to the same types — discord.Game(...) is Playing, and discord.Activity(type=..., name=...) covers Watching, Listening, and the rest:

@client.event
async def on_ready():
    await client.change_presence(
        status=discord.Status.online,
        activity=discord.Activity(type=discord.ActivityType.watching, name="/help"),
    )
    print(f"Listening as {client.user}", flush=True)

To rotate, use a discord.ext.tasks loop (the Pythonic timer):

from discord.ext import tasks

@tasks.loop(seconds=30)
async def rotate_status():
    activity = discord.Activity(type=discord.ActivityType.watching,
                                name=f"{len(client.guilds)} servers")
    await client.change_presence(activity=activity)

Start it from on_ready with rotate_status.start(). discord.Status.idle, .dnd, and .invisible give you the same status options as discord.js.

Verify it works

Start the bot and look at it in your member list. Under its name you should see your status — Watching /help, then (if you added rotation) the text changing every 30 seconds. If it's blank, the bot either isn't fully connected yet (give it a moment after the Listening as … line) or the presence code is running in the wrong place.

Troubleshooting

  • Cannot read properties of null (reading 'setPresence') — you set presence before login. Move it inside the ClientReady handler, where client.user exists.
  • The status never appears — confirm the bot is actually online (green dot) and that setPresence/change_presence runs after ClientReady/on_ready. Nothing shows while the bot is offline — that's the platform limit above, not a bug.
  • Streaming type doesn't turn purple — the Streaming type needs a valid Twitch or YouTube url in the activity; without it, Discord falls back to plain text.
  • Rotation stops or stutters — you're likely updating too fast and hitting the presence rate limit. Slow the interval to 15–30 seconds.
  • Custom status text won't set — bots use the activity name for Playing/Watching/etc.; the free-form custom status behaves differently and isn't the usual path. Stick to the typed activities above.

Next steps

Was this guide helpful?