---
title: "Temporary roles on Discord: how the bot removes them after X days | StreetHosting"
description: "Create temporary Discord roles with discord.js v14: store the expiry date in a database, sweep every minute and remove the role even after the bot restarts."
url: "https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot"
type: "page"
language: "en-US"
---

Discord Bots · 8 min · Intermediate

Published on Nov 13, 2026 · Updated on Nov 13, 2026

# Temporary roles on Discord: a 30-day VIP that expires on its own

A 30-day VIP or a 7-day timeout only works if the role comes off at the right time, even when the bot restarts along the way. Learn how to store the expiry date and remove the role safely.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Community and game modes](https://streethosting.com.br/en/guides/topics/community)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=Temporary%20roles%20on%20Discord%3A%20how%20the%20bot%20removes%20them%20after%20X%20days&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot "Share on LinkedIn") [](https://wa.me/?text=Temporary%20roles%20on%20Discord%3A%20how%20the%20bot%20removes%20them%20after%20X%20days%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-temporary-roles-bot "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot.md)

In this guide 7 sections

* [Why setTimeout alone fails](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#por-que-settimeout-falha)
* [Store the expiry date in a database](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#tabela-expiracao)
* [Give the role and record the deadline](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#dar-cargo)
* [The sweep that removes the role](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#varredura)
* [Permission and role hierarchy](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#permissoes-hierarquia)
* [Renewing, leaving and coming back](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#casos-de-borda)
* [Where to run this on StreetHosting](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#onde-rodar)

Quick answer

When you give the role, store the expiry date in the database (in milliseconds since 1970). A `setInterval` of 60 seconds queries the expired rows and calls `member.roles.remove()`. The bot needs the Manage Roles permission and a role above the one it will manage. Because the deadline is in the database, restarting the bot loses nothing.

## Why setTimeout alone fails[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#por-que-settimeout-falha)

The first idea almost everyone has is to give the role and schedule its removal with a timer:

`await member.roles.add(ID_CARGO_VIP); setTimeout(() => member.roles.remove(ID_CARGO_VIP), 30 * 24 * 60 * 60 * 1000);`

It works in a 10 second test and fails in production for two reasons.

* **The timer lives in memory.** A deploy, a power cut, an unhandled error or a plain process restart wipes every timer. The member keeps the VIP forever and nobody notices.
* **Node's limit.** The maximum delay of a `setTimeout` is 2147483647 milliseconds, about 24.8 days. Past that, Node prints a `TimeoutOverflowWarning` and fires almost immediately. A 30 day VIP would be removed the moment it was given.

The way out is to split the two jobs: the deadline is stored in a database, which survives any restart, and a short routine checks whether any row has expired. The same idea works for reminders, temporary bans and anything scheduled, as in the guide on [scheduling messages on Discord](https://streethosting.com.br/en/guides/discord-bots/discord-bot-schedule-messages).

## Store the expiry date in a database[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#tabela-expiracao)

For a small bot, SQLite with `better-sqlite3` does the job: it is a single file, with no service to maintain. Install it with `npm install better-sqlite3` and create the table once, at startup:

`` // db.js const Database = require('better-sqlite3'); const db = new Database('dados.db'); db.pragma('journal_mode = WAL'); db.exec(` CREATE TABLE IF NOT EXISTS cargos_temporarios ( guild_id TEXT NOT NULL, user_id TEXT NOT NULL, role_id TEXT NOT NULL, expira_em INTEGER NOT NULL, PRIMARY KEY (guild_id, user_id, role_id) ); CREATE INDEX IF NOT EXISTS idx_expira_em ON cargos_temporarios (expira_em); `); module.exports = db; ``

Two details matter. The primary key joins server, user and role, so the same role never creates two rows for the same person. And `expira_em` holds a number (milliseconds since 1970, which is what `Date.now()` returns). A number is simple to compare and does not depend on time zones.

If your bot already uses MariaDB, the table is the same, swapping the date type for `BIGINT`, the IDs for `VARCHAR(20)` (MariaDB does not accept a TEXT column in a primary key without a length) and `ON CONFLICT` for `ON DUPLICATE KEY UPDATE`. Do not use a plain `INT`: a 32 bit integer cannot hold today's milliseconds.

## Give the role and record the deadline[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#dar-cargo)

The function below grants the role and stores the expiry. If the person already had the same role, the deadline is replaced, which in practice renews the VIP:

`` // cargos.js const db = require('./db'); const gravar = db.prepare(` INSERT INTO cargos_temporarios (guild_id, user_id, role_id, expira_em) VALUES (?, ?, ?, ?) ON CONFLICT (guild_id, user_id, role_id) DO UPDATE SET expira_em = excluded.expira_em `); async function darCargoTemporario(member, roleId, duracaoMs, motivo) { await member.roles.add(roleId, motivo); const expiraEm = Date.now() + duracaoMs; gravar.run(member.guild.id, member.id, roleId, expiraEm); return expiraEm; } module.exports = { darCargoTemporario }; ``

The order matters: Discord accepts the role first, then you save. If the call to Discord fails, no orphan row is left in the database. Now a `/vip` command that uses the function (see how to register commands in [creating a Discord bot with slash commands](https://streethosting.com.br/en/guides/discord-bots/create-discord-bot-slash-commands)):

`` const { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, InteractionContextType } = require('discord.js'); const vip = new SlashCommandBuilder() .setName('vip') .setDescription('Dá o cargo VIP por alguns dias') .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) .setContexts(InteractionContextType.Guild) // servers only .addUserOption((o) => o.setName('usuario').setDescription('Quem recebe').setRequired(true)) .addIntegerOption((o) => o.setName('dias').setDescription('Duração em dias').setMinValue(1).setMaxValue(365).setRequired(true) ); // in the interactionCreate handler if (interaction.commandName === 'vip') { const alvo = interaction.options.getMember('usuario'); const dias = interaction.options.getInteger('dias', true); const cargo = interaction.guild.roles.cache.get(process.env.ID_CARGO_VIP); if (!alvo || !cargo?.editable) { return interaction.reply({ content: 'Não consigo gerenciar esse cargo. Confira a hierarquia.', flags: MessageFlags.Ephemeral, }); } const expiraEm = await darCargoTemporario(alvo, cargo.id, dias * 24 * 60 * 60 * 1000, `VIP de ${dias} dias`); await interaction.reply(`${alvo} é VIP até <t:${Math.floor(expiraEm / 1000)}:f> (<t:${Math.floor(expiraEm / 1000)}:R>).`); } ``

The `<t:seconds:R>`format is a Discord timestamp: each person sees the date in their own time zone, and the relative version ("in 30 days") updates by itself.

## The sweep that removes the role[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#varredura)

Now the half that matters. One function fetches everything that has expired, removes the role and deletes the row. Run it once at startup (to catch up on whatever expired while the bot was down) and then every minute:

`// varredura.js const db = require('./db'); const buscarVencidos = db.prepare('SELECT * FROM cargos_temporarios WHERE expira_em <= ? LIMIT 200'); const apagar = db.prepare( 'DELETE FROM cargos_temporarios WHERE guild_id = ? AND user_id = ? AND role_id = ?' ); let rodando = false; async function varrer(client) { if (rodando) return; // never let two sweeps overlap rodando = true; try { const vencidos = buscarVencidos.all(Date.now()); for (const item of vencidos) { try { const guild = await client.guilds.fetch(item.guild_id); const member = await guild.members.fetch(item.user_id); await member.roles.remove(item.role_id, 'Cargo temporário expirou'); apagar.run(item.guild_id, item.user_id, item.role_id); } catch (err) { // 10007 = member no longer in the server, 10011 = role deleted, 10004 = unknown server if ([10007, 10011, 10004].includes(err.code)) { apagar.run(item.guild_id, item.user_id, item.role_id); } else { console.error('Falha ao remover cargo, tento de novo na próxima rodada:', err); } } } } finally { rodando = false; } } function iniciarVarredura(client) { varrer(client); setInterval(() => varrer(client), 60 * 1000); } module.exports = { iniciarVarredura };`

And in the main file, once the client is ready:

`const { Events } = require('discord.js'); const { iniciarVarredura } = require('./varredura'); client.once(Events.ClientReady, () => { iniciarVarredura(client); });`

The safety point is in the `catch`: we only delete the row when the role was removed or when we know there is nothing left to remove. A temporary error (network, rate limit, a permission that was missing for a few minutes) leaves the row in the database and the next round tries again. The `LIMIT 200` keeps a large backlog from stalling the process; the rest goes out in the next round. Fetching a single member by ID uses the REST API, and does not depend on the cache or on the members intent.

Why 60 seconds? A role that comes off up to a minute late is unnoticeable for a VIP or a timeout. If your case needs more precision, shorten the interval, but remember that each removal is a call to the Discord API and there are limits. See [how to deal with the 429 rate limit](https://streethosting.com.br/en/guides/discord-bots/fix-discord-429-rate-limit).

## Permission and role hierarchy[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#permissoes-hierarquia)

Giving and taking roles requires the **Manage Roles**permission on the server. Having the permission is not enough, though, because Discord applies a hierarchy rule: the bot can only touch roles that sit below its highest role in the server's role list.

* In Server Settings, Roles, drag the bot's role above the VIP role (or the timeout role). If it sits below, every call returns `Missing Permissions`.
* The member's position does not matter: the bot can give a role below its own even to someone whose roles sit higher than the bot's. The member's hierarchy only counts for kicking, banning and changing nicknames.
* Roles managed by an integration, such as another bot's automatic role or the booster role, cannot be assigned manually.
* The temporary role should not carry administrator permissions. If the VIP needs only a few privileges, create a role just for that and grant only what is needed.

In code, the `role.editable` property tells you whether the bot can manage that role, and it is what we use in the `/vip` command to show a clear error message instead of a generic one.

Giving the bot administrator permission does not fix the error, because the hierarchy applies to administrators too. It is also risky: if the token leaks, the whole server is exposed. Grant only Manage Roles and place the bot's role just above the roles it manages.

## Renewing, leaving and coming back[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#casos-de-borda)

Three situations show up in the first week of use, and it is worth handling all of them from the start.

* **Renewal.** With the `ON CONFLICT` above, a new `/vip` replaces the deadline. If you would rather add to what was left, read the row first and use `Math.max(Date.now(), expiraAtual) + duracaoMs`.
* **Early removal.** If a moderator takes the role off by hand, the row stays in the database. It is not serious (removing a role that is already gone is harmless), but you can clean up by listening to `guildMemberUpdate` and deleting the row when the role disappears from the member.
* **Leaving and coming back.** Discord removes the roles on leave. If the deadline has not passed and you want to give the benefit back, listen to `guildMemberAdd`, query that user's rows and reapply the role. For a timeout this is essential, otherwise leaving and rejoining becomes a way to escape the punishment.

The `guildMemberUpdate` and `guildMemberAdd` events only arrive with the GuildMembers intent on the client and the Server Members Intent option turned on in the developer portal, because it is privileged. The sweep itself does not need it.

If you sell the VIP, combine this logic with the payment flow from the guide on a [sales bot on Discord](https://streethosting.com.br/en/guides/discord-bots/discord-sales-bot): a confirmed payment calls `darCargoTemporario`. Also back up the database file, because it now holds the deadline of everyone who paid, as explained in [Discord bot data backup](https://streethosting.com.br/en/guides/discord-bots/discord-bot-data-backup).

## Where to run this on StreetHosting[](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#onde-rodar)

The sweep needs an always on process, and the database needs a disk that survives restarts. On [StreetHosting bot hosting](https://streethosting.com.br/en/bots) you run Node.js in a container with a panel, and the Discord Essential plan, at R$ 9.99 per month, includes 1 GB of RAM, 2 GB of NVMe and a MariaDB database. There is plenty of room for this kind of bot: SQLite fits on the container disk and, if you prefer, the same table works on the included MariaDB.

If the bot is only one part of a bigger project, with a website, a payment panel and other services, a [StreetHosting VPS](https://streethosting.com.br/en/vps) gives you root access and you organize everything your own way.

In this guide

* [Why setTimeout alone fails](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#por-que-settimeout-falha)
* [Store the expiry date in a database](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#tabela-expiracao)
* [Give the role and record the deadline](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#dar-cargo)
* [The sweep that removes the role](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#varredura)
* [Permission and role hierarchy](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#permissoes-hierarquia)
* [Renewing, leaving and coming back](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#casos-de-borda)
* [Where to run this on StreetHosting](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot#onde-rodar)

## Frequently asked questions

Can I just use setTimeout to remove the role after 30 days?

No. The timer lives in the process memory and disappears when the bot restarts. On top of that, setTimeout accepts at most about 24.8 days (2147483647 milliseconds); above that, Node fires it almost immediately. Store the expiry date in a database and check it periodically.

How often should the sweep run?

Once a minute is enough for almost every server. If the role can leave a few minutes late, running every 5 minutes also works. The cost is one indexed database query, so running every minute is cheap.

The bot has permission but I still get Missing Permissions. Why?

It is almost always hierarchy. The bot's highest role has to sit above the role it will give or take away, and that holds even if the bot is an administrator. Integration roles (from other bots or from boosters) cannot be assigned by a bot either.

What happens if the member leaves the server before the role expires?

Discord already removes the roles of anyone who leaves. During the sweep, fetching the member fails with code 10007 and you just delete the row from the database. If you want to give back the remaining time when they return, use the guildMemberAdd event and reapply the role.

Do I need MariaDB or is SQLite enough?

For a bot in a few servers, SQLite with better-sqlite3 is enough and needs no extra service. If the bot runs in several processes or you already have a MariaDB, use the same table there. The sweep logic is identical.

Next step

See bot plans

24/7 Discord bots with fast activation and support in Portuguese.

[See bot plans](https://streethosting.com.br/en/bots)

## Related guides

[Discord bots Beginner How to set up auto roles and reaction roles on Discord An auto role gives a role to everyone who joins. Reaction roles let members pick roles by clicking reactions or buttons. Both keep your server organized and cut down on manual work. 3 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-auto-role-reaction-roles) [Discord bots Intermediate How to schedule automatic Discord messages with a bot Scheduled messages are great for event announcements, voting reminders and recurring notices. See how to implement them with node-schedule in a Node.js bot, plus scheduling best practices. 3 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-bot-schedule-messages) [Discord bots Intermediate Discord reminder command: a bot that DMs you after 10m, 2h or 3d A reminder is only worth anything if it arrives even after the bot restarts. Learn the duration parser, the table that stores the reminders, DM delivery with a channel fallback and the limit that prevents abuse. 7 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
