<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Botnexio</title>
    <description>The latest articles on DEV Community by Botnexio (@botnexio).</description>
    <link>https://dev.to/botnexio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4190058%2F2b036fb9-eecc-4550-b70f-af8682b134e9.png</url>
      <title>DEV Community: Botnexio</title>
      <link>https://dev.to/botnexio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/botnexio"/>
    <language>en</language>
    <item>
      <title>Build a Discord bot with discord.js v14: app setup, your first slash command and keeping it online</title>
      <dc:creator>Botnexio</dc:creator>
      <pubDate>Sun, 18 Oct 2026 12:54:39 +0000</pubDate>
      <link>https://dev.to/botnexio/build-a-discord-bot-with-discordjs-v14-app-setup-your-first-slash-command-and-keeping-it-online-1bkj</link>
      <guid>https://dev.to/botnexio/build-a-discord-bot-with-discordjs-v14-app-setup-your-first-slash-command-and-keeping-it-online-1bkj</guid>
      <description>&lt;p&gt;This walkthrough takes you from an empty folder to a Discord bot that answers slash commands, and then covers what it takes to keep that bot running when your laptop is closed. Everything uses &lt;strong&gt;discord.js v14&lt;/strong&gt; (14.27.0 is the current release on npm at the time of writing) and CommonJS, so you can paste the snippets straight into a Node project.&lt;/p&gt;

&lt;p&gt;By the end you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Discord application with a bot user and a token kept out of your code&lt;/li&gt;
&lt;li&gt;a small script that registers slash commands through Discord's REST API&lt;/li&gt;
&lt;li&gt;a bot that handles &lt;code&gt;/ping&lt;/code&gt; and &lt;code&gt;/roll&lt;/code&gt;, including basic error handling&lt;/li&gt;
&lt;li&gt;a plan for running it 24/7&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt;: discord.js 14.27.0 declares &lt;code&gt;node &amp;gt;= 18&lt;/code&gt; in its &lt;code&gt;package.json&lt;/code&gt;. Use a currently maintained LTS release.&lt;/li&gt;
&lt;li&gt;Comfort with &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; and basic JavaScript.&lt;/li&gt;
&lt;li&gt;A Discord server where you have the &lt;em&gt;Manage Server&lt;/em&gt; permission. A private server just for development is ideal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Create the application in the Developer Portal
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt; and create a new application. The name you pick becomes your bot's name.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;General Information&lt;/strong&gt;, copy the &lt;strong&gt;Application ID&lt;/strong&gt;. You'll need it as &lt;code&gt;CLIENT_ID&lt;/code&gt; later.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Bot&lt;/strong&gt; page and click &lt;strong&gt;Reset Token&lt;/strong&gt;. Copy the token right away: Discord won't show it again unless you regenerate it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Treat the token like a password. Anyone who has it can log in as your bot. Never paste it into source files, screenshots or public repos. If it leaks, go back to the Bot page and hit &lt;strong&gt;Reset Token&lt;/strong&gt; immediately; the old one stops working.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Set up the project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-bot &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-bot
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;discord.js dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;DISCORD_TOKEN&lt;/span&gt;=&lt;span class="n"&gt;paste&lt;/span&gt;-&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;bot&lt;/span&gt;-&lt;span class="n"&gt;token&lt;/span&gt;-&lt;span class="n"&gt;here&lt;/span&gt;
&lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;=&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;application&lt;/span&gt;-&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;GUILD_ID&lt;/span&gt;=&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;dev&lt;/span&gt;-&lt;span class="n"&gt;server&lt;/span&gt;-&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get &lt;code&gt;GUILD_ID&lt;/code&gt;, turn on &lt;strong&gt;Developer Mode&lt;/strong&gt; in Discord (User Settings → Advanced), right-click your server's name and choose &lt;strong&gt;Copy Server ID&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then make sure the file never ends up in Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"node_modules/"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; On Node 20.6+ you can skip &lt;code&gt;dotenv&lt;/code&gt; and run &lt;code&gt;node --env-file=.env index.js&lt;/code&gt; instead. This tutorial sticks with &lt;code&gt;dotenv&lt;/code&gt; because it behaves the same everywhere, including under a process manager.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Define your commands
&lt;/h2&gt;

&lt;p&gt;Slash commands are described with &lt;code&gt;SlashCommandBuilder&lt;/code&gt;. Keep the definitions in their own file so the deploy script and the bot can share them later.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;commands.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SlashCommandBuilder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SlashCommandBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Replies with Pong!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SlashCommandBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rolls a die&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIntegerOption&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;option&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sides&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Number of sides (default: 6)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMinValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMaxValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/roll&lt;/code&gt; has an optional integer option. Discord validates &lt;code&gt;setMinValue&lt;/code&gt;/&lt;code&gt;setMaxValue&lt;/code&gt; before the interaction reaches your bot, so users can't submit &lt;code&gt;sides: 1&lt;/code&gt; or &lt;code&gt;sides: 9000&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Register the commands with REST and Routes
&lt;/h2&gt;

&lt;p&gt;Commands don't appear in Discord until you register them. You only need to do this when a command's &lt;em&gt;definition&lt;/em&gt; changes (name, description, options), not every time the bot starts. Discord has a daily limit on command creations, so the discord.js guide recommends a standalone script using the lightweight &lt;code&gt;REST&lt;/code&gt; client instead of doing it inside the &lt;code&gt;ready&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deploy-commands.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./commands&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GUILD_ID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Registering &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; slash command(s)...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// PUT replaces the full set of guild commands with this list&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applicationGuildCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GUILD_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Registered &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; command(s) in guild &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GUILD_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rest.put&lt;/code&gt; &lt;strong&gt;overwrites&lt;/strong&gt; the whole command list for that route. If you remove a command from &lt;code&gt;commands.js&lt;/code&gt; and redeploy, it disappears from Discord.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Routes.applicationGuildCommands(clientId, guildId)&lt;/code&gt; registers commands in &lt;strong&gt;one server&lt;/strong&gt;. That's what you want during development.&lt;/li&gt;
&lt;li&gt;When you're ready to make commands available everywhere the bot is installed, switch to &lt;code&gt;Routes.applicationCommands(clientId)&lt;/code&gt;. Global commands also show up in DMs by default.&lt;/li&gt;
&lt;li&gt;If you register the same command both globally and in your dev guild, you'll see it twice there. Some people use a separate application (and token) for development to avoid this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run it after inviting the bot (step 6), because the &lt;code&gt;applications.commands&lt;/code&gt; scope must be authorized in the guild first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node deploy-commands.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Write the bot
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GatewayIntentBits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MessageFlags&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;GatewayIntentBits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Guilds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClientReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readyClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Ready! Logged in as &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;readyClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InteractionCreate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isChatInputCommand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commandName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pong!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commandName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sides&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sides&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🎲 You rolled a **&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;** (d&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sides&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong running that command.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ephemeral&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replied&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deferred&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;followUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DISCORD_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intents&lt;/strong&gt; tell Discord which gateway events to send you. &lt;code&gt;GatewayIntentBits.Guilds&lt;/code&gt; is the baseline: it keeps the guild, channel and role caches populated, and slash commands work with it alone. You don't need message intents to receive interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Events.ClientReady&lt;/code&gt;&lt;/strong&gt; fires once after login. &lt;code&gt;readyClient&lt;/code&gt; is typed as a logged-in client, which matters if you move to TypeScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Events.InteractionCreate&lt;/code&gt;&lt;/strong&gt; fires for every interaction: slash commands, buttons, select menus, modals, autocomplete. &lt;code&gt;isChatInputCommand()&lt;/code&gt; filters for slash commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral replies&lt;/strong&gt; are set with &lt;code&gt;flags: MessageFlags.Ephemeral&lt;/code&gt;. Older tutorials use &lt;code&gt;ephemeral: true&lt;/code&gt;; the discord.js guide now uses the flag.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; means a failing command logs an error and tells the user, instead of leaving them with "The application did not respond".&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Need more than three seconds?
&lt;/h3&gt;

&lt;p&gt;Discord expects an initial response within &lt;strong&gt;three seconds&lt;/strong&gt;, or the interaction fails. If a command calls an API or a database, acknowledge it first with &lt;code&gt;deferReply()&lt;/code&gt;, then fill in the answer with &lt;code&gt;editReply()&lt;/code&gt;. After the initial response you have 15 minutes to edit or send follow-ups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deferReply&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchSomethingSlow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Here you go: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Want to read messages too?
&lt;/h3&gt;

&lt;p&gt;Slash commands don't need it, but if you later want to react to regular messages (a word filter, for example), you need &lt;code&gt;GatewayIntentBits.GuildMessages&lt;/code&gt; plus the privileged &lt;code&gt;GatewayIntentBits.MessageContent&lt;/code&gt; intent. Privileged intents must also be switched on under &lt;strong&gt;Bot → Privileged Gateway Intents&lt;/strong&gt; in the portal. Request only what you actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Invite the bot and try it
&lt;/h2&gt;

&lt;p&gt;In the Developer Portal, open &lt;strong&gt;Installation&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Install Link&lt;/strong&gt;, select &lt;strong&gt;Discord Provided Link&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Default Install Settings → Guild Install&lt;/strong&gt;, add the scopes &lt;code&gt;applications.commands&lt;/code&gt; and &lt;code&gt;bot&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In the permissions list that appears, choose what your bot needs. For this tutorial, &lt;strong&gt;Send Messages&lt;/strong&gt; is enough.&lt;/li&gt;
&lt;li&gt;Open the install link in your browser, pick your dev server and confirm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now register and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node deploy-commands.js
node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;Ready! Logged in as YourBot#1234&lt;/code&gt;. Type &lt;code&gt;/ping&lt;/code&gt; or &lt;code&gt;/roll sides:20&lt;/code&gt; in your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Keeping it online
&lt;/h2&gt;

&lt;p&gt;As long as &lt;code&gt;node index.js&lt;/code&gt; runs on your machine, the bot is online. Close the terminal or shut down the computer, and it goes offline. For 24/7 operation you need two things: &lt;strong&gt;a machine that's always on&lt;/strong&gt; and &lt;strong&gt;something that restarts the process if it crashes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common setups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A VPS or home server with a process manager.&lt;/strong&gt; &lt;a href="https://pm2.keymetrics.io/" rel="noopener noreferrer"&gt;pm2&lt;/a&gt; is a popular choice for Node:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2
  pm2 start index.js &lt;span class="nt"&gt;--name&lt;/span&gt; my-bot
  pm2 logs my-bot        &lt;span class="c"&gt;# follow the output&lt;/span&gt;
  pm2 save               &lt;span class="c"&gt;# remember the process list&lt;/span&gt;
  pm2 startup            &lt;span class="c"&gt;# prints a command that starts pm2 on boot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;systemd works just as well if you prefer to stay with what the OS provides.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A container.&lt;/strong&gt; Package the bot in a Docker image and run it with a restart policy, either on your own server or on a container host:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;  FROM node:22-alpine
  WORKDIR /app
  COPY package*.json ./
  RUN npm ci --omit=dev
  COPY . .
  CMD ["node", "index.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-bot &lt;span class="nb"&gt;.&lt;/span&gt;
  docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-bot &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env my-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;node_modules&lt;/code&gt; to a &lt;code&gt;.dockerignore&lt;/code&gt; so the token never gets baked into the image. Pass it at runtime instead, as above.&lt;/p&gt;

&lt;p&gt;Whichever route you pick, a little graceful-shutdown handling avoids the bot dropping off mid-reply when the process manager or container runtime stops it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; received, logging out...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Command doesn't appear&lt;/td&gt;
&lt;td&gt;Bot was invited without &lt;code&gt;applications.commands&lt;/code&gt;, or &lt;code&gt;deploy-commands.js&lt;/code&gt; wasn't run (or failed; check its output).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[DisallowedIntents]&lt;/code&gt; on startup&lt;/td&gt;
&lt;td&gt;You request a privileged intent that isn't enabled in the Developer Portal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The application did not respond"&lt;/td&gt;
&lt;td&gt;No reply within three seconds. Use &lt;code&gt;deferReply()&lt;/code&gt; for slow work, and check your logs for errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TokenInvalid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wrong or reset token in &lt;code&gt;.env&lt;/code&gt;, or &lt;code&gt;.env&lt;/code&gt; isn't being loaded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command shows up twice&lt;/td&gt;
&lt;td&gt;It's registered both globally and in your guild.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;Once you have more than a handful of commands, one big &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; in &lt;code&gt;index.js&lt;/code&gt; gets messy. The usual next steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one file per command (exporting &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;execute&lt;/code&gt;) plus a loader that reads the &lt;code&gt;commands/&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;the same pattern for events&lt;/li&gt;
&lt;li&gt;structured logging so you can find problems after the fact&lt;/li&gt;
&lt;li&gt;a database once you need to persist anything (levels, settings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://discordjs.guide/" rel="noopener noreferrer"&gt;discord.js guide&lt;/a&gt; covers command and event handlers in detail, and the &lt;a href="https://discord.js.org/docs" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; are the reference for every class used above.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The extended version of this guide, with a section on structuring a larger bot project, lives at &lt;a href="https://botnexio.com/guides/build-discord-bot-discord-js" rel="noopener noreferrer"&gt;botnexio.com&lt;/a&gt;. If you want to prototype embed layouts for your bot's replies, there's also a free &lt;a href="https://botnexio.com/free/discord-embed-generator?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=content-kw43&amp;amp;utm_content=build-discord-bot-discord-js" rel="noopener noreferrer"&gt;Discord embed generator&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not affiliated with Discord.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a marketplace for Discord bots and templates: our Stripe Connect onboarding, step by step</title>
      <dc:creator>Botnexio</dc:creator>
      <pubDate>Sat, 17 Oct 2026 08:14:42 +0000</pubDate>
      <link>https://dev.to/botnexio/building-a-marketplace-for-discord-bots-and-templates-our-stripe-connect-onboarding-step-by-step-32b7</link>
      <guid>https://dev.to/botnexio/building-a-marketplace-for-discord-bots-and-templates-our-stripe-connect-onboarding-step-by-step-32b7</guid>
      <description>&lt;p&gt;We're building Botnexio, a marketplace where creators sell Discord bots, GPTs, vibe-coding templates and prompt packs. This is our thing, so treat it as a show-and-tell, not a neutral review.&lt;/p&gt;

&lt;p&gt;The part that took longest wasn't uploads, it was seller onboarding with Stripe Connect. What we learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Account created" ≠ "can sell". We only treat a creator as ready when Stripe reports both &lt;code&gt;charges_enabled&lt;/code&gt; and &lt;code&gt;payouts_enabled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;"Not started" and "incomplete" need different messages. Sending everyone to the same "continue onboarding" button confuses people who already filled in half the form.&lt;/li&gt;
&lt;li&gt;Don't preselect a product category. A silent default ends up being wrong more often than you'd think.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pricing for creators, plainly: you receive 85% of the net price (excl. VAT), less Stripe fees, i.e. 15% commission. Launch offer: Creators 1–10: 1 month 0% commission, creators 11–30: 3 months 5%, each from account activation; Stripe fees are borne by the creator. Terms: &lt;a href="https://botnexio.com/creator-bedingungen" rel="noopener noreferrer"&gt;https://botnexio.com/creator-bedingungen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built Connect onboarding yourself, I'd love to hear how you handle the "incomplete" state. And if you have a bot or template to sell: &lt;a href="https://botnexio.com/creator" rel="noopener noreferrer"&gt;https://botnexio.com/creator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Botnexio is operated by Aramac GmbH. Not affiliated with Discord.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>stripe</category>
      <category>discord</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
