Where to put your bot token

Your bot runs from one .py file on a read-only filesystem. That rules out a .env file — but not the code that reads one. Two ways work:

Environment variables

recommended

Set DISCORD_TOKEN in the dashboard, keep it out of your source.

TOKEN = os.getenv("DISCORD_TOKEN")
How it works →

In the script

Works, but the token then lives in your file — we recommend environment variables instead.

TOKEN = "MTk4NjIyNDgzNDcx…"
How it works →
Already using python-dotenv? Then change nothing at all in your code — just add the variables in the dashboard. Why that works: see below.

1. Set the variables

  1. In the upload dialog, field Environment variables — or
  2. later at any time with the key icon on the bot card.

One NAME=value per line. Same format as a .env file, so you can paste yours straight in:

DISCORD_TOKEN=MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs
PREFIX=!

2. Read them in your bot

Plain os.getenv

import os
import discord
from discord.ext import commands

TOKEN = os.getenv("DISCORD_TOKEN")
PREFIX = os.getenv("PREFIX", "!")          # second argument = default

intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix=PREFIX, intents=intents)

@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")

bot.run(TOKEN)

python-dotenv — unchanged

You do not have to rewrite a script built around a .env file. load_dotenv() finds no file here, does nothing, and raises nothing — and os.getenv() then reads the variable we set instead. The same file runs on your laptop with a .env and here without one.

import os
import discord
from dotenv import load_dotenv

load_dotenv()                       # no .env here — silently does nothing
TOKEN = os.getenv("DISCORD_TOKEN")  # comes from the dashboard instead

client = discord.Client(intents=discord.Intents.default())
client.run(TOKEN)
Worth adding: fail loudly when the variable is missing, so a typo in the name shows up as a clear sentence in your output instead of a token error.
TOKEN = os.getenv("DISCORD_TOKEN")
if not TOKEN:
    raise SystemExit("DISCORD_TOKEN is not set — add it with the key icon on the bot card.")

Rules and limits

  • Up to 5 variables per bot, 4096 characters per value.
  • Names: letters, digits, underscores; not starting with a digit.
  • Quotes are stripped, so TOKEN="abc" equals TOKEN=abc. # lines are ignored, an export  prefix is accepted.
  • A few names belong to the container and are refused (PATH, PYTHONPATH, LD_PRELOAD, …).
  • Saving replaces all variables of that bot — enter the complete set.
  • Variables can only be changed while the bot is stopped: a container receives them when it starts, so a change could not reach a running bot. The dialog still shows which names are set while it runs.

The token directly in your file

Works, but not recommended

This works, and plenty of hosted bots run this way. We still recommend against it. A token in your source file travels with every copy of that file — into a backup, a chat message, a version control system like Git. From a public repository a Discord token is harvested within minutes, and whoever has it fully controls your bot. Discord itself scans public repositories and invalidates tokens it finds — which protects you, but takes your bot offline without warning.

The safe way is the environment variables in the first tab: they never appear in your file, are never shown again in the dashboard, and do not show up in the server's process list.

import discord

TOKEN = "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs"

client = discord.Client(intents=discord.Intents.default())
client.run(TOKEN)

The one thing to watch

The token now lives in your source file. We never serve that file to the web, but you still have a copy of it — so do not paste it into a Discord channel, a pastebin, a screenshot or a public repository. If it does get out, regenerate it in the Discord Developer Portal and upload the corrected file.

Which one should you pick?

Environment variablesIn the script
Token visible in your fileNoYes
Sharing the script is safeYesNo
Change the tokenKey icon, no re-uploadEdit and re-upload the file
Setup effortPaste one line in the dialogNone

Patterns that cannot work here

All of these are perfectly normal on your own machine. They fail here for one of two reasons: only a single .py is uploaded, or the filesystem is read-only. Click a row for the fix. The error messages are the real ones, taken from a container running with the production settings.

The upload takes one .py file, so no .env ever reaches the server. load_dotenv() itself stays quiet — which is why this is so easy to miss — and the token simply ends up as None.

from dotenv import load_dotenv
load_dotenv()                      # no error, but no values either
TOKEN = os.getenv("DISCORD_TOKEN")  # -> None
client.run(TOKEN)

Fix: keep the code exactly as it is and set DISCORD_TOKEN in the dashboard.

from dotenv import dotenv_values
TOKEN = dotenv_values(".env")["DISCORD_TOKEN"]   # KeyError: 'DISCORD_TOKEN'

with open(".env") as f:                          # FileNotFoundError
    ...

The same goes for a config.json, config.yaml or token.txt next to your script: nothing but the one .py exists. Fix: os.getenv(), or put the value in the script.

from config import TOKEN     # config.py was never uploaded
from cogs.music import Music # same problem, one folder deeper

Fix: merge your project into a single file. There is no way around this one — it is a limit of the platform, not of your code. (If the name is a package instead, it simply is not installed.)

TOKEN = os.environ["DISCORD_TOKEN"]   # raises when unset

Not wrong as such — it only fails if the variable is missing. Either set it in the dashboard, or use os.getenv(...), which returns None instead of raising.

with open("token.txt", "w") as f:   # also /tmp, also a SQLite file
    f.write(token)

The container may not write anywhere on disk — deliberately, because that is part of what keeps bots isolated from each other. Keep state in memory, or in a database you reach over the network.

TOKEN = input("Token: ")

Nobody is sitting at a console: your bot is started by the platform, not by you in a shell.

An export DISCORD_TOKEN=… in your .bashrc, a systemd unit or your IDE's run configuration lives on your computer. Our container starts with the variables you set in the dashboard, and nothing else.

Error messages, translated

Your bot's own output is under View Output on its card.

What the output saysWhat it means
TypeError: expected token to be a str, received NoneType instead The variable is not set, or is spelled differently, so the token is None. Check the name with the key icon.
LoginFailure: Improper token has been passed. Discord rejected the token: empty, mistyped, or regenerated in the Developer Portal. We stop the bot instead of retrying — a rejected token never fixes itself.
KeyError: 'DISCORD_TOKEN' os.environ[…] or dotenv_values(…)[…] on something that is not set.
ModuleNotFoundError: No module named 'config' A second file of your project — merge everything into one .py. With a package name it means the package is not installed.
OSError: [Errno 30] Read-only file system Your script tries to write to disk. Not possible here.

Quick reference

What you doResult
os.getenv("DISCORD_TOKEN") + variable set hereWorks — recommended
load_dotenv() + os.getenv() + variable set hereWorks — no code changes
Token written directly in the .pyWorks
os.environ["DISCORD_TOKEN"] + variable set hereWorks
Uploading a .env, config.json or config.pyNot possible — one .py per bot
load_dotenv() without setting variables hereToken stays empty
Reading or writing files next to the scriptFails — read-only filesystem
input() for the tokenFails — no terminal

How we store your variables

In clear text, in a file only the web server can read, handed to your container when it starts. Clear text is unavoidable: your bot needs the real token to log in to Discord. Two consequences worth knowing:

  • They are never shown again, not even to you — the dialog only lists which names are set, so a token cannot end up in a browser cache or a screenshot.
  • They are deleted together with the bot, and are not part of the data export in your account settings. Details in our privacy policy.

Treat a bot token like a password: it grants full control of the bot account. If you think it leaked, regenerate it in the Discord Developer Portal and save the new one here.

Ready to try it?

Upload your .py, paste your variables, press Start.