NapNodes

← Discord bot guides

Discord bots · updated 6 November 2026

Discord Temporary Voice Channels: a “Join to Create” Bot in Python That Deletes Them When Empty

A server with ten fixed voice channels almost always has eight empty ones and two full of people who don't know each other. Temporary channels fix that: there's a single “Create room” channel, whoever joins gets a room with their name and is moved into it, they manage it themselves (name, user limit, who to kick) and, when the last person leaves, the room disappears. Here you build it yourself in discord.py, in one file, with no ads or someone else's branding, and without losing anything on restart.

What the bot does

You pick a voice channel as “Create room” with /rooms-setup. When someone joins it, the bot creates a voice channel in the same category, named after them, and moves them in. The room copies the permissions of “Create room”: if only members can see that channel, they're the only ones who see the rooms too.

Whoever creates it is its owner: the bot gives them “Manage Channels” and “Move Members” in their room only, so from Discord (right-click → “Edit Channel”) they rename it or set a user limit (with the limit equal to how many of you are already in, nobody else gets in), and they can disconnect anyone who's being a pain. No commands needed for that.

When the last person leaves, the bot deletes the room. Each person has one room at a time: if they join “Create room” again, they go to theirs. And if the bot was off when a room emptied, it deletes it when it comes back: the rooms it created are saved in rooms.json.

Rather not set it up yourself? Discord bot hosting from €1.50 a month, no contract and a full refund within 72 hours. Or create a free Discord bot, no card needed.

1. Create the bot on Discord

  1. Create the application and get the token

    At discord.com/developers/applications, “New Application”; in the “Bot” tab, “Reset Token” and copy the token. It's the bot's password: don't share it or push it to GitHub. If it's your first bot, the full walkthrough is in your first bot in Python.

  2. Invite it with these four permissions

    In “OAuth2” → “URL Generator”, tick “bot” and “applications.commands”, and in the permissions “View Channels”, “Manage Channels”, “Move Members” and “Connect”. The bot permissions calculator gives you the link ready made. Leave the “Privileged Gateway Intents” off: voice states aren't privileged.

2. Set up the “Create room” channel

Create a category (for example, “Rooms”) and in it a voice channel called “➕ Create room”. Rooms are created in that same category.

The bot's permissions have to apply in that category. If the category is closed to @everyone, add the bot's role under “Edit Category” → “Permissions” with the four above. And don't give “Create room” permissions the bot doesn't have: Discord won't let a bot hand out permissions it doesn't have on a new channel, and the room wouldn't be created.

“Manage Channels” is one of the permissions that require two-factor authentication if your server requires it for moderation: in that case, the account that owns the bot needs to have it on.

3. The code

Three files in a folder. The token goes in .env, without quotes. You need Python 3.10 or newer. Rooms are saved in rooms.json, which the bot creates next to app.py.

requirements.txt
discord.py
python-dotenv
.env
DISCORD_TOKEN=paste-your-token-here
app.py
import json
import os
from pathlib import Path

import discord
from discord import app_commands
from dotenv import load_dotenv

load_dotenv()  # reads the token from the .env file

# Which "Create room" channel each server has and which rooms the bot created (and whose they are).
DATA = Path("rooms.json")

# What the bot needs in the "Create room" category, with the name Discord shows.
NEEDED = {
    "view_channel": "View Channels",
    "manage_channels": "Manage Channels",
    "move_members": "Move Members",
    "connect": "Connect",
}


def load() -> dict:
    return json.loads(DATA.read_text("utf-8")) if DATA.exists() else {"hub": {}, "rooms": {}}


def save(data: dict) -> None:
    DATA.write_text(json.dumps(data, ensure_ascii=False, indent=2), "utf-8")


class Bot(discord.Client):
    def __init__(self):
        # Voice states come with the default intents: nothing to switch on in the portal.
        super().__init__(intents=discord.Intents.default())
        self.tree = app_commands.CommandTree(self)

    async def setup_hook(self):
        await self.tree.sync()  # publishes /rooms-setup and /rooms-off


bot = Bot()


def forget(room_id: int) -> None:
    data = load()
    data["rooms"].pop(str(room_id), None)
    save(data)


async def delete_if_empty(room: discord.VoiceChannel) -> None:
    if room.voice_states:  # someone is still inside
        return
    try:
        await room.delete(reason="Empty temporary room")
    except discord.NotFound:
        pass  # someone deleted it by hand
    except discord.Forbidden:
        print(f"I can't delete {room.name}: I'm missing \"Manage Channels\".")
        return
    forget(room.id)


async def create_room(member: discord.Member, hub: discord.VoiceChannel) -> None:
    guild = member.guild
    data = load()
    # One room per person: if they already have one, they go to theirs instead of getting a new one.
    for room_id, owner in data["rooms"].items():
        room = guild.get_channel(int(room_id))
        if owner == member.id and room is not None:
            try:
                await member.move_to(room, reason="Back to their room")
            except discord.HTTPException:
                pass  # no longer in "Create room"
            return
    # The room lets in the same people as "Create room", and its owner manages it from Discord.
    perms = dict(hub.overwrites)
    perms[member] = discord.PermissionOverwrite(connect=True, manage_channels=True, move_members=True)
    perms[guild.me] = discord.PermissionOverwrite(view_channel=True, connect=True, manage_channels=True, move_members=True)
    room = await guild.create_voice_channel(
        f"{member.display_name}'s room"[:100],  # Discord doesn't allow names over 100 characters
        category=hub.category,
        overwrites=perms,
        reason=f"Temporary room for {member}",
    )
    data["rooms"][str(room.id)] = member.id
    save(data)
    try:
        await member.move_to(room, reason="Their temporary room")
    except discord.HTTPException:
        # They left "Create room" before being moved (error 40032): the room is not needed.
        await delete_if_empty(room)


@bot.event
async def on_voice_state_update(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
    if before.channel == after.channel or member.bot:
        return  # they muted or turned on the camera: same channel
    data = load()
    hub_id = data["hub"].get(str(member.guild.id))
    if after.channel is not None and after.channel.id == hub_id:
        try:
            await create_room(member, after.channel)
        except discord.HTTPException as error:
            print(f"Couldn't create the room for {member}: {error!r}")
    if before.channel is not None and str(before.channel.id) in data["rooms"]:
        await delete_if_empty(before.channel)


@bot.event
async def on_ready():
    # Rooms that emptied while the bot was off are deleted when it comes back.
    for room_id in list(load()["rooms"]):
        room = bot.get_channel(int(room_id))
        if room is None:
            forget(int(room_id))  # it no longer exists
        else:
            await delete_if_empty(room)
    print(f"Logged in as {bot.user}")


@bot.tree.command(name="rooms-setup", description="Pick the voice channel that creates a room for each person")
@app_commands.guild_only()
@app_commands.default_permissions(manage_guild=True)
@app_commands.describe(channel="The \"Create room\" voice channel")
async def rooms_setup(interaction: discord.Interaction, channel: discord.VoiceChannel):
    perms = channel.permissions_for(interaction.guild.me)
    missing = [name for key, name in NEEDED.items() if not getattr(perms, key)]
    if missing:
        await interaction.response.send_message(
            f"In {channel.mention} I'm missing: {', '.join(missing)}. Give it to me in the category and try again.", ephemeral=True)
        return
    data = load()
    data["hub"][str(interaction.guild_id)] = channel.id
    save(data)
    await interaction.response.send_message(f"Done: whoever joins {channel.mention} gets their own room.", ephemeral=True)


@bot.tree.command(name="rooms-off", description="Stop creating temporary rooms on this server")
@app_commands.guild_only()
@app_commands.default_permissions(manage_guild=True)
async def rooms_off(interaction: discord.Interaction):
    data = load()
    data["hub"].pop(str(interaction.guild_id), None)
    save(data)
    await interaction.response.send_message("Done: no more new rooms. The existing ones are deleted when they empty.", ephemeral=True)


bot.run(os.environ["DISCORD_TOKEN"])

How it works under the hood

Everything happens in on_voice_state_update, the event Discord sends whenever someone joins, leaves or switches voice channels (also when they mute or turn on the camera: that's why the first thing is checking whether they really changed channel). If the new channel is “Create room”, it creates the room; if the one they left is one of the bot's rooms and nobody is left, it deletes it.

To move someone, Discord requires the bot to have “Move Members” and to be able to join the target channel. That's why the bot gives itself “Connect” in every room, even if the category is closed. If the person leaves “Create room” before being moved, Discord answers with error 40032 (“Target user is not connected to voice”) and the bot deletes the room it just created.

Rooms are counted with voice_states, which doesn't depend on the member list: you don't need the privileged members intent.

4. Try it

Who can use /rooms-setup and /rooms-off is changed without touching the code in “Server Settings” → “Integrations” → your bot.

  1. Start the bot

    In a terminal, in the folder: pip install -r requirements.txt and python app.py. When it says “Logged in as…”, you're set. Commands can take a few minutes to show up the first time.

  2. Pick the channel

    Type /rooms-setup and choose “➕ Create room”. If the bot is missing a permission in that category, it tells you which one.

  3. Join and leave

    Join “➕ Create room”: within a second you're in a room named after you. Right-click it → “Edit Channel” and set a user limit. Leave: the room disappears.

If something doesn't work

You join “Create room” and nothing happens: the bot is off or this server doesn't have “Create room” set (use /rooms-setup again). Check the console: if it says “Couldn't create the room…” with “Missing Permissions”, the bot is missing a permission in the category or “Create room” has a permission the bot doesn't have. The codes are in Missing Access and Missing Permissions.

The room is created but you aren't moved: “Move Members” or “Connect” is missing in the category. The room isn't deleted when it empties: “Manage Channels” is missing (the console says so).

Commands don't show up: what to check when they're missing. If the bot is online and does nothing, my bot isn't responding.

5. Keep it online all the time (for free)

This bot only works if it's on when someone joins “Create room”: if it lives on your computer and you turn it off, the channel becomes a normal voice channel and rooms in use aren't deleted until it's back. It has to be hosted on a server.

On NapNodes: create the free bot (it starts as Node.js; for this one, in “Settings” → “Language”, switch it to Python), upload the three files in “Files” and click “Start”. The libraries in requirements.txt install themselves at startup and app.py is the startup file that comes set. rooms.json shows up in “Files” next to app.py.

The free bot has 256 MB, plenty for this bot even if your server has lots of rooms at once, and you renew it with one click every week: if you forget, it switches off until you start it. So you don't have to remember, the Mini plan (512 MB) stays online with nothing to renew (Discord bot pricing). The whole setup is in the Discord bot hosting guide.

Ideas to extend it

A better name: instead of the owner's name, the game they're playing (member.activity, which needs the privileged presences intent) or a name the owner picks with a command.

A /room-claim so that, if the owner leaves and the room is still full, someone else takes over the owner permissions: change the owner in rooms.json and move the permissions with room.set_permissions. Or merge it with the moderation bot and the welcome bot into one: all three fit in the free bot.

Frequently asked questions

How do I make temporary voice channels on Discord?

With a bot: a “Create room” channel that, when someone joins, creates a voice channel for that person, moves them in and deletes it when it empties. The full Python code is in this guide.

Does Discord have temporary voice channels without a bot?

No: Discord voice channels stay until someone deletes them. For them to be created and deleted on their own you need a bot.

What permissions does the bot need?

View Channels, Manage Channels, Move Members and Connect, in the category where “Create room” is. It doesn't need any privileged intent.

Can the room owner change its name or limit?

Yes: the bot gives them “Manage Channels” and “Move Members” in their room only, so they change it from Discord with right-click → “Edit Channel”, no commands.

Where do I keep the bot online all the time?

On a server. NapNodes has a free 256 MB bot you renew with one click every week, or the Mini plan, always on.

Shall we set it up for you?

Discord bot hosting from €1.50 a month, never oversold: your RAM is yours alone. And if you'd rather start without paying, there's a free Discord bot, no card needed.

Free tool: Discord bot permissions calculator. Tick your bot's permissions and get the number and the invite link; or paste a number and see which permissions it grants.

Keep reading

All Discord bot guides, sorted →