On This Page

Example Bots

Small, complete bots for moderation, reaction roles, welcomes and tickets, ready to copy and run

Each example on this page is one complete file shaped like the quick start's bot: It exports its options as bot with defineBot and passes them to runBot when Node.js runs the file. Copy it, set a few environment variables and run it. Every example logs failures in full and stops cleanly on Ctrl+C

Start with the small bot for a guided tour of commands, arguments, embeds, events, guards and cooldowns in one file. The examples below each solve one common task

Run an Example

Set up a project as in the quick start: A folder with "type": "module" in package.json and the SDK installed. Save the example as bot.js, or as bot.ts to keep the TypeScript version, which Node.js 24 runs directly

A generated project's test imports bot from that file, so it runs the example without a token or a network. The test checks !ping, which most examples do not have, so change it to the example's own commands or events, as Testing a bot shows. Examples with required variables read them in setup, so a test that calls ready() sets them first

Put the token and the example's other variables in a .env file, one KEY=value line each, and keep that file out of version control

.env
FLUXER_BOT_TOKEN=paste-the-token-here
WELCOME_CHANNEL_ID=123456789012345678

Start the bot with Node.js loading that file:

node --env-file=.env bot.jsnode --env-file=.env bot.ts

A missing variable stops the bot at startup with an explanation. Channels, roles and messages are configured by their decimal IDs. The deploying guide shows how to keep a bot running on a server

Give the Bot Its Permissions

The invite in Create a bot grants only what the quick start needs. Each example below lists the permissions it needs, and without them its requests fail with a missing-permissions error

For a bot that is already in the community, edit the role Fluxer created for it, which has the application's name, and turn on the listed permissions in the community's role settings. For a bot that is not in the community yet, pass the listed names to the invite script instead, for example for the moderation example:

import { links, permissionBits } from "@neontechspace/fluxerly"

const applicationId = process.argv[2] ?? ""
const permissions = permissionBits.from([
    "ViewChannel",
    "SendMessages",
    "ReadMessageHistory",
    "ModerateMembers",
    "KickMembers",
    "BanMembers",
    "ManageMessages",
])
console.log(links.installation(applicationId, { permissions }))
import { links, permissionBits } from "@neontechspace/fluxerly"

const applicationId = process.argv[2] ?? ""
const permissions = permissionBits.from([
    "ViewChannel",
    "SendMessages",
    "ReadMessageHistory",
    "ModerateMembers",
    "KickMembers",
    "BanMembers",
    "ManageMessages",
])
console.log(links.installation(applicationId, { permissions }))

Some examples also change roles or members. Fluxer allows that only when the bot's highest role ranks above them, so place the bot's role above those roles in the role settings

Moderation Commands

Five prefix commands for moderators: !timeout @member 10m reason stops a member from talking for 1 minute to 7 days, !kick @member reason removes a member from the community, !ban @member reason bans a member permanently, !tempban @member 7d reason bans a member for 1 minute to 2 years, and !clear 20 deletes up to 20 recent messages in the channel. Each reason is optional. Each command has a guard that checks the moderator's own permission. The !timeout, !kick, !ban and !tempban commands also check role hierarchy through members.fetchCanManage, with the moderator's ID as actorUserId. When a command fails, a middleware function tells the moderator without showing error details in the channel

  • Variables: FLUXER_BOT_TOKEN
  • Bot permissions: ViewChannel, SendMessages, ReadMessageHistory, ModerateMembers, KickMembers, BanMembers and ManageMessages. The bot's highest role must also rank above the members it acts on
  • Moderator permissions: ModerateMembers for !timeout, KickMembers for !kick, BanMembers for !ban and !tempban, and ManageMessages for !clear
  • Two-factor authentication: In a community that requires it for moderation, every command fails with HTTP 400 unless the account that owns the bot's application has it enabled. See communities that require two-factor authentication
moderation/bot.jsmoderation/bot.ts
// In a server that requires two-factor authentication for moderation, Fluxer refuses these commands with
// HTTP 400 unless the account that owns the bot's application has two-factor authentication enabled
import { defineBot, describeError, format, guards, orThrow, runBot } from "@neontechspace/fluxerly"

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),
    commands: {
        prefix: "!",
        use: [
            // Tell the moderator when a command fails, without showing error details in the channel.
            // The router still reports the full error to onError
            async ({ reply }, next) => {
                try {
                    await next()
                } catch (error) {
                    const sent = await reply("That command failed. The error was reported")
// In a server that requires two-factor authentication for moderation, Fluxer refuses these commands with
// HTTP 400 unless the account that owns the bot's application has two-factor authentication enabled
import { defineBot, describeError, format, guards, orThrow, runBot } from "@neontechspace/fluxerly"

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),
    commands: {
        prefix: "!",
        use: [
            // Tell the moderator when a command fails, without showing error details in the channel.
            // The router still reports the full error to onError
            async ({ reply }, next) => {
                try {
                    await next()
                } catch (error) {
                    const sent = await reply("That command failed. The error was reported")

The !clear command previews the exact messages first and then deletes only those. The reply reports how many deletions were requested, because Fluxer confirms each batch rather than each message. See the source on GitHub

Reaction Roles

Members react to one existing message to receive an opt-in role, and remove their reaction to give it back. At startup, the bot checks that it ranks above the role and adds the emoji to the message itself, so members can see which one to use

  • Variables: FLUXER_BOT_TOKEN, ROLE_CHANNEL_ID and ROLE_MESSAGE_ID for the message, ROLE_ID for the role, and an optional ROLE_EMOJI, a Unicode emoji that defaults to ✅
  • Bot permissions: ViewChannel, ReadMessageHistory, AddReactions and ManageRoles. The bot's highest role must rank above the configured role
reaction-roles/bot.jsreaction-roles/bot.ts
import { defineBot, hierarchy, orThrow, runBot } from "@neontechspace/fluxerly"

function requiredEnv(name) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

const emoji = process.env.ROLE_EMOJI || "✅"
// Setup reads the required settings, so a test can import this file without them
let target = { channelId: "", id: "" }
let roleId = ""
let guildId = ""
let botUserId = ""

async function setRole(client, userId, granted) {
import { defineBot, hierarchy, orThrow, runBot, type Client } from "@neontechspace/fluxerly"

function requiredEnv(name: string) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

const emoji = process.env.ROLE_EMOJI || "✅"
// Setup reads the required settings, so a test can import this file without them
let target = { channelId: "", id: "" }
let roleId = ""
let guildId = ""
let botUserId = ""

async function setRole(client: Client, userId: string, granted: boolean) {

Configure only a role that members may give themselves. The bot sees only reactions made while it runs, so a member who reacted while it was offline removes the reaction and adds it again. See the source on GitHub and communities and permissions for more about role hierarchy

Welcome Messages

The bot greets each new member in a configured channel with a mention and an embed, and can also give every new member a starter role. It ignores joins in other communities and joining bots

  • Variables: FLUXER_BOT_TOKEN, WELCOME_CHANNEL_ID, and an optional WELCOME_ROLE_ID for the starter role
  • Bot permissions: ViewChannel, SendMessages and EmbedLinks in the welcome channel, plus ManageRoles when WELCOME_ROLE_ID is set. The bot's highest role must then rank above the starter role
welcome/bot.jswelcome/bot.ts
import { builders, defineBot, format, orThrow, runBot } from "@neontechspace/fluxerly"

function requiredEnv(name) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

// Optional role that every new member receives
const roleId = process.env.WELCOME_ROLE_ID || undefined
// Setup reads the channel, so a test can import this file without the setting
let channelId = ""
let guildId = ""

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),
import { builders, defineBot, format, orThrow, runBot } from "@neontechspace/fluxerly"

function requiredEnv(name: string) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

// Optional role that every new member receives
const roleId = process.env.WELCOME_ROLE_ID || undefined
// Setup reads the channel, so a test can import this file without the setting
let channelId = ""
let guildId = ""

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),

A welcome message that was sent but whose role grant failed stays posted, and the failure is logged. See the source on GitHub and events and collectors for more about member and community events

Support Tickets

A member sends !ticket to open a private channel visible to them, the staff role, the bot, the community owner and members with Administrator. Anyone in that channel sends !close to delete it. Each member has at most one open ticket, and a cooldown allows one !ticket per member per minute

  • Variables: FLUXER_BOT_TOKEN, TICKET_CATEGORY_ID for the category that holds tickets, and TICKET_STAFF_ROLE_ID for the role that answers them
  • Bot permissions: ManageChannels and ManageRoles, plus ViewChannel, SendMessages and ReadMessageHistory in the ticket category. Fluxer lets the bot grant only permissions it has itself
tickets/bot.jstickets/bot.ts
import { ChannelType, Permissions, defineBot, format, guards, orThrow, runBot } from "@neontechspace/fluxerly"

function requiredEnv(name) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

const access = Permissions.ViewChannel | Permissions.SendMessages | Permissions.ReadMessageHistory
// Setup reads the required settings, so a test can import this file without them
let categoryId = ""
let staffRoleId = ""

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),
    // Setup runs before the bot connects, so a missing setting stops the bot at startup
import { ChannelType, Permissions, defineBot, format, guards, orThrow, runBot } from "@neontechspace/fluxerly"

function requiredEnv(name: string) {
    const value = process.env[name]
    if (!value) throw new Error(`Set the ${name} environment variable`)
    return value
}

const access = Permissions.ViewChannel | Permissions.SendMessages | Permissions.ReadMessageHistory
// Setup reads the required settings, so a test can import this file without them
let categoryId = ""
let staffRoleId = ""

export const bot = defineBot({
    onError: (report) => console.error(report.describe()),
    // Setup runs before the bot connects, so a missing setting stops the bot at startup

The bot reads its own user ID with client.users.getSelf(), which returns the account Fluxer reported when the bot connected, without a request. The !close command deletes only channels in the ticket category whose names start with ticket-. See the source on GitHub and commands for more about guards and cooldowns