How to make your own Discord-bot using Node-red (with pictures and examples)

In this guide you will learn how to use node-red and Discord.JS to create you own bot! There’s currently multiple discord-nodes available on nodered.org, but they might not fit your needs, and you might want to try out Discord.JS as it offers more flexibility.

There’s only two prerequisites for this guide. The first one is that you must have a fully functional node-red(v.1.3.3 or higher) installed. The second one is a Discord-server where you have admin permissions. To understand what is happening and further develop your bot, you need a little experience with Javascript as well.

Table of contents:

Create your bot on Discord.com

The first thing you’ll have to do is to create a Discord-application. The application that you’ll be creating will be containing a Discord-bot.
Head over to https://discord.com/developers/applications, log in, and press ‘New Application’ in the upper right corner.

Press ‘New Application’

A window will appear and you’ll be asked to name your application. No worries if you’re not able to come up with a fitting name. The name can easily be changed later on :- ).

Choose a name and click ‘Create’

In the navigation-pane to the left, choose ‘Bot’, and click ‘Add Bot’. When prompted with “ADD A BOT TO THIS APP?”, press ‘Yes, do it!’

Choose ‘Bot’ and click ‘Add Bot’
Click ‘Yes, do it!’

You have now successfully added a bot user to your app! Edit your bots username and icon(avatar) to your preference and press ‘Save Changes’! The bots username and avatar will be visible in your servers memberlist. You must also press ‘Click to Reveal Token’ and take note of the token presented. This is your bots password and you should not share this with anyone.

Choose a username, an avatar, and copy your token.

The only step missing is setting your bot’s permissions and inviting it to your discord-server. Head over to ‘OAuth2’ in the navigaton pane. It is considered good practice to follow the principle of least privelege when setting up the permissions.

Select “Bot” under “Scopes”. Because this bot is only going to send messages, im only going to give it the permissions ‘Send messages’ and ‘Read Message History’. When developing your own bot, you’re more than likely going to give it more permissions than i have in this example. When you’re done selecting your permissions, click ‘Copy’ and paste the URL into the address bar of your web browser.


Choose which server you want to add the bot to and click “Continue”. Verify your permissions and click ‘Authorize’


If you head over to your Discord server, you’ll see that your bot is now listed in the servers memberlist. It’s currently disconnected, but follow along and i’ll show you how to connect nodered to discord using Discord.Js :- ).

Installing the Discord.JS-module in Node-RED

Now that you have set up your bot, it’s time to establish a connection between your Node-RED instance and Discord using Discord.JS. The reason why Node-RED version 1.3 or higher is a prerequisite, is because it simplifies the process of adding and importing node modules by quite a lot.

Head over to the Node-RED editor in your web browser and drag a function node onto your workspace. Open the nodes settings by double-clicking. Navigate to the setup tab and press ‘+ add’ in the bottom left corner to add/import(require) a new module. In the field to the right, type “discord.js”, and in the field to the left, remove the dot so it says “discordjs” as seen in the picture below. Press done and deploy the flow. Node-RED will now download the module, and you’ll be able to use it in your function-node. Head over to the next step and we’ll create a message-listener :- ).

Did you know?

If you ‘require’ a module, it automatically gets imported and installed from https://www.npmjs.com/. This mean that you suddenly have plenty of popular modules to choose from.

Creating your event-listener

Now it’s time to write some code and we’ll be creating a couple of event-listeners. In the same function-node as earlier, click on the ‘On Message’-tab. Each step underneath contains a screenshot, a code-block(for your convenience) and an explanation, so it should be pretty straight forward to follow along in your own environment :- ).

The first thing we’ll do, is to create a client. We’ll store the client in the ‘Client’-variable and in the global context for later use. In short, global context is a place to store stuff so it’s available for other nodes to access. Read more about context here.

From line 1-13, i’ve also implemented a safety net so that we only have one client running at most.

// Get the client from global context. If not available, store null instead.
const DCClient = global.get('DCClient') || null

// If the client was found in global context, try to close(destroy) the session.
if (DCClient) {
    try {
        DCClient.destroy()
    } catch (error) {
        node.warn(error)
    }

    global.set('DCClient', null)
}

// Initiate the client and store it in global context.
const client = new discordjs.Client()
global.set('DCClient', client)

I do like visual feedback, so when the client is logged in or disconnected, i want the node to tell me so. Using the client we created in the previous step, we’ll create event listeners that listens for when the client is ready and when it’s disconnected. node.warn() will write this information to the debug console, and node.status() will display the status underneath the node as seen in the screenshot below.

// Get the client from global context. If not available, store null instead.
const DCClient = global.get('DCClient') || null

// If the client was found in global context, try to close(destroy) the session.
if (DCClient) {
    try {
        DCClient.destroy()
    } catch (error) {
        node.warn(error)
    }

    global.set('DCClient', null)
}

// Initiate the client and store it in global context.
const client = new discordjs.Client()
global.set('DCClient', client)

// Event-listener that triggers when the client is ready
client.on('ready', () => {
    node.warn(`Logged in as: ${client.user.tag}`)
    node.status({ fill: "green", shape: "dot", text: "Connected" })
})

// Event-listener that triggers when the client disconnects
client.on('disconnect', () => {
    node.status({ fill: "red", shape: "dot", text: "disconnected" });
    node.warn(`${client.user.tag} disconnected.`);

    global.set('DCClient', null)

});

The third and most important event we’ll be listening to in this guide is ‘message’. This triggers everytime a message get sent to a channel that the bot has sufficent access to. As we do not want the bot to react to its own messages, we’ll check if the author of the message is a bot, and if so, it’ll exit the code.

// Get the client from global context. If not available, store null instead.
const DCClient = global.get('DCClient') || null

// If the client was found in global context, try to close(destroy) the session.
if (DCClient) {
    try {
        DCClient.destroy()
    } catch (error) {
        node.warn(error)
    }

    global.set('DCClient', null)
}

// Initiate the client and store it in global context.
const client = new discordjs.Client()
global.set('DCClient', client)

// Event-listener that triggers when the client is ready
client.on('ready', () => {
    node.warn(`Logged in as: ${client.user.tag}`)
    node.status({ fill: "green", shape: "dot", text: "Connected" })
})

// Event-listener that triggers when the client disconnects
client.on('disconnect', () => {
    node.status({ fill: "red", shape: "dot", text: "disconnected" });
    node.warn(`${client.user.tag} disconnected.`);

    global.set('DCClient', null)

});

// Event-listener that triggers everytime a message is received
client.on('message', message => {
    if (message.author.bot) return;


})

If the message is not authored by a bot, we want function-node to send() a msg-object which contains the message to the next node in the flow. We also have to tell the client to login() using the API-key obtained from discord.com in one of the first steps in this guide. Press done and deploy your flow.

It is worth nothing that the node.send-method contains two arguments. The first one is our newly created object, and the second one is a boolean telling node-RED not to clone our object before sending. By default, all objects sent by node.send() are cloned. Cloning the message-object from the event-listener causes node-RED to crash, so we have to specify that our object is not cloned. If you were to attach two different nodes to the same output(also called branching), the second node that recieves the message would get a cloned object even if we told it not to clone. Read more about cloning here.

// Get the client from global context. If not available, store null instead.
const DCClient = global.get('DCClient') || null

// If the client was found in global context, try to close(destroy) the session.
if (DCClient) {
    try {
        DCClient.destroy()
    } catch (error) {
        node.warn(error)
    }

    global.set('DCClient', null)
}

// Initiate the client and store it in global context.
const client = new discordjs.Client()
global.set('DCClient', client)

// Event-listener that triggers when the client is ready
client.on('ready', () => {
    node.warn(`Logged in as: ${client.user.tag}`)
    node.status({ fill: "green", shape: "dot", text: "Connected" })
})

// Event-listener that triggers when the client disconnects
client.on('disconnect', () => {
    node.status({ fill: "red", shape: "dot", text: "disconnected" });
    node.warn(`${client.user.tag} disconnected.`);

    global.set('DCClient', null)

});

// Event-listener that triggers everytime a message is received
client.on('message', message => {
    if (message.author.bot) return;

    return { "payload": message }
})

client.login('API KEY HERE')

In the Node-RED editor, insert and connect an inject node and a debug node to each side of the function node as shown in the picture below and deploy the flow. Remember not to branch your flow.

The inject-node is actually your bot’s ON-button. Pressing the ON-button causes your code to run which again creates a client. Go ahead and try. Your function-node should say ‘connected’, and if you head over to your discord server, you’ll see that the bot is now online.

If you write something in the Discord-channel, you’ll see in the same message in the debug-panel in the Node-RED editor. This means that your bot is ready to process messages.

Did you know?

If you’d like to listen for other events than new messages, you’ll find a list of events that the discord.js-client supports listed here: https://discord.js.org/#/docs/main/stable/class/Client

Reacting to the messages

Now that your bot is online(if it’s not, you have to retrace your steps), we can now start processing messages. Delete the debug node, and attach a new function node instead. Remember not to branch your flow.

To react to a specific message, we have to first check its content. If we decide we want to react to the message by replying, we can use the reply()-method which comes with the message-object in the payload.

if (msg.payload.content == 'ping'){
     msg.payload.reply('pong')
 }

Alternatively, instead of handling the logic inside a function-node, you could use a switch-node and route the message based on conditions. In the example underneath, i’m checking wether the content is ‘ping’ or ‘Have a nice day, bot!’, and then routing the message based on that.

We can now generate two different responses based on what someone writes in the channel. I think wishing the bot a nice day is a nice gesture, so let’s make it reply with thanks and an emoji.

msg.payload.reply('Thanks. You too!')
msg.payload.react('❤️')


And thats it for this guide! If you’ve followed it step by step, you should now be able to write ‘Have a nice day, bot!’ and the bot should reply with a message and react with heart :- ).


Did you know?

The message object created by the event-listener contains many properties and methods which can interacted with. You can get a list of all properties and methods here: https://discord.js.org/#/docs/main/master/class/Message

The following json-code contains the whole flow used in this guide and is importable in Node-RED.

[
     {
         "id": "5ad1f66f.7f6e48",
         "type": "function",
         "z": "1d669f6c.999871",
         "name": "EventListener",
         "func": "// Get the client from global context. If not available, store null instead.\nconst DCClient = global.get('DCClient') || null\n\n// If the client was found in global context, try to close(destroy) the session.\nif (DCClient) {\n    try {\n        DCClient.destroy()\n    } catch (error) {\n        node.warn(error)\n    }\n\n    global.set('DCClient', null)\n}\n\n// Initiate the client and store it in global context.\nconst client = new discordjs.Client()\nglobal.set('DCClient', client)\n\n// Event-listener that triggers when the client is ready\nclient.on('ready', () => {\n    node.warn(Logged in as: ${client.user.tag})\n    node.status({ fill: \"green\", shape: \"dot\", text: \"Connected\" })\n})\n\n// Event-listener that triggers when the client disconnects\nclient.on('disconnect', () => {\n    node.status({ fill: \"red\", shape: \"dot\", text: \"disconnected\" });\n    node.warn(${client.user.tag} disconnected.);\n\n    global.set('DCClient', null)\n\n});\n\n// Event-listener that triggers everytime a message is received\nclient.on('message', message => {\n    if (message.author.bot) return;\n\n    // Construct the msg object\n    let msg = {\n        \"payload\": message\n    }\n\n    // Send the object to the next node in the flow\n    node.send(msg,false)\n})\n\nclient.login('API-key here')\n\n\n\n",
         "outputs": 1,
         "noerr": 0,
         "initialize": "",
         "finalize": "",
         "libs": [
             {
                 "var": "discordjs",
                 "module": "discord.js"
             }
         ],
         "x": 1100,
         "y": 400,
         "wires": [
             [
                 "597e1f73.a80fb"
             ]
         ]
     },
     {
         "id": "1324133e.88aaad",
         "type": "inject",
         "z": "1d669f6c.999871",
         "name": "",
         "props": [
             {
                 "p": "payload"
             },
             {
                 "p": "topic",
                 "vt": "str"
             }
         ],
         "repeat": "",
         "crontab": "",
         "once": false,
         "onceDelay": 0.1,
         "topic": "",
         "payload": "",
         "payloadType": "date",
         "x": 940,
         "y": 400,
         "wires": [
             [
                 "5ad1f66f.7f6e48"
             ]
         ]
     },
     {
         "id": "df10414a.9f476",
         "type": "function",
         "z": "1d669f6c.999871",
         "name": "Have a nice day!",
         "func": "msg.payload.reply('Thanks. You too!')\nmsg.payload.react('❤️')",
         "outputs": 0,
         "noerr": 0,
         "initialize": "",
         "finalize": "",
         "libs": [],
         "x": 1450,
         "y": 380,
         "wires": []
     },
     {
         "id": "597e1f73.a80fb",
         "type": "switch",
         "z": "1d669f6c.999871",
         "name": "",
         "property": "payload.content",
         "propertyType": "msg",
         "rules": [
             {
                 "t": "eq",
                 "v": "Have a nice day, bot!",
                 "vt": "str"
             },
             {
                 "t": "eq",
                 "v": "Ping",
                 "vt": "str"
             }
         ],
         "checkall": "false",
         "repair": false,
         "outputs": 2,
         "x": 1270,
         "y": 400,
         "wires": [
             [
                 "df10414a.9f476"
             ],
             [
                 "c79cb37e.ee4ca"
             ]
         ]
     },
     {
         "id": "c79cb37e.ee4ca",
         "type": "function",
         "z": "1d669f6c.999871",
         "name": "reply to message",
         "func": "msg.payload.reply('pong')\n",
         "outputs": 0,
         "noerr": 0,
         "initialize": "",
         "finalize": "",
         "libs": [],
         "x": 1450,
         "y": 420,
         "wires": []
     }
 ]

5 Comments

  1. Thanks for this guide @Get-Joe. I’m having some trouble getting started, though. When adding the required discord.js field to the function, it throws “discord.js – unexpected_error”

    Any thoughts?

  2. Hi. Nice tutorial.
    I was trying to follow it and add a discord bot to node-red but it seems that after discords latest updates your code does not work anymore.
    It throws a ‘Discord Api Valid intents must be provided for the Client’ .

    You need to update line 16 of your code to something like this:
    const client = new discordjs.Client({ intents: [“GUILDS”, “GUILD_MESSAGES”] })
    so that it can connect.

    Thanks for your tutorial!

  3. What a really great and insightful blog post. Thank you for the tips. I’m looking forward to implementing this myself!

Leave a Reply