Documentation

Custom commands

Build your own ,commands with variables, conditionals, actions and a fully sandboxed Script API.

Overview

What custom commands are

Custom commands let admins create their own ,commands. They are created and managed only on the dashboard under Server > Custom Commands, gated by the custom_commands feature flag, and run in Discord via ,<name>. A custom command can never shadow a built-in command.

Standard

response + actions

A response template rendered with variables, inline functions and conditionals, plus an optional sequence of actions built on the dashboard.

Script

sandboxed JS

Sandboxed JavaScript with the full exceed API. The script enqueues actions that run afterward and pass every guardrail.

Safety model

always enforced
  • A custom command runs with the bot's permissions, but every action passes guardrails before it executes.
  • Role changes respect role hierarchy via role_guard. The bot can never act above its own highest role.
  • allowedMentions is locked, so a command can never @everyone or mass-mention.
  • Moderation actions are triple-gated: the invoker must hold the matching Discord permission, the bot must hold it, and the bot must outrank the target.
  • Output and action caps apply, and there is no recursion, so commands cannot loop into themselves.
Variables

Variables

Used in Standard responses and the {embed} / $v{} engine. Each group exposes the same fields where it makes sense.

Invoker

{user.*}

Fields

idnametagmentionnickdisplayavatarbannercreatedjoinedjoindaysaccountageboostingcolortoprolerolesbadge

Target & random user

{target.*} / {random_user.*}

{target.*} is the first mention or id in the args and falls back to the invoker. {random_user.*} is a random human member. Both expose the same fields as the invoker.

Server

{server.*}

Fields

nameidiconmembercounthumansbotsboostsboost_tierownercreatedrolecountchannelcountemojicountvanity

Channel

{channel.*}

Fields

nameidmentiontopiccategorycreatednsfw

Message & misc

{message.*}

Tokens

{message.id}{message.link}{prefix}{command.name}

Arguments

Tokens

{args}{args.length}{args.0}{args.1}

Indices are zero-based.

Storage

{db.<key>}

A saved per-server value. Set it from the dashboard, or use {set:k|v} for scratch storage during a render.

Last.fm

{lastfm.*}

Auto-filled from the runner's linked Last.fm account. The member must link Last.fm with the bot's lastfm command first. {lastfm.nowplaying} is true or false, and {lastfm.status} is Now playing or Last played.

Fields

userurltrackartistalbumimagenowplayingstatusscrobblestopartisttoptrack
Inline functions

Inline functions

These work anywhere in a response and render in place.

Math & strings

{math:1+2*3}Safe arithmetic with + - * / % and parentheses.
{upper:t} {lower:t} {title:t}Change case.
{len:t} {reverse:t} {trim:t} {urlencode:t}Length, reverse, trim, URL-encode.
{replace:src|find|with}Replace a substring.
{substring:src|start|end}Slice a string.
{repeat:src|n}Repeat a string n times.
{lpad:src|len|char}Left-pad to a length.
{round:n|digits} {abs:n}Round to digits, absolute value.

Randomness

{choose:a|b|c}Pick a random option.
{random:lo-hi}Random integer in a range.
{dice:NdM}Roll N dice with M sides.

Scratch storage & time

{set:key|value}Store a scratch value; renders empty.
{get:key}Read a scratch value.
{time} {date} {now} {unix}Current time, date, datetime and unix seconds.
{timestamp:R}A Discord timestamp. Styles: t T d D f F R.
External data

External data

These tokens pull live data from outside services. Argument tokens take values after a colon and are cached for about a minute; the no-argument tokens refresh quickly. Any lookup that fails renders an empty string.

Crypto

{crypto:<coin>}
{crypto:<coin>}USD price of a coin, e.g. {crypto:bitcoin} or {crypto:eth}.
{crypto:<coin>:change}24h percent change.
{crypto:<coin>:marketcap}Market cap in USD.

Currency

{currency:<amount>:<from>:<to>}
{currency:<amount>:<from>:<to>}Convert between currencies, e.g. {currency:100:USD:EUR}.

Minecraft

{minecraft:<name>}
{minecraft:<name>}The player's UUID.
{minecraft:<name>:name}The player's current name.
{minecraft:<name>:avatar}Avatar image URL.
{minecraft:<name>:skin}Skin image URL.
{minecraft:<name>:body}Body render image URL.

Weather

{weather:<city>}
{weather:<city>}Short summary, e.g. 19.5°C in London.
{weather:<city>:temp}Temperature in Celsius.
{weather:<city>:humidity}Humidity percentage.
{weather:<city>:wind}Wind speed.
{weather:<city>:city}Resolved city name.

GitHub

{github:<user>} / {github:<owner>/<repo>}
{github:<user>}Follower count, e.g. {github:torvalds}.
{github:<user>:repos}Public repo count.
{github:<user>:name}Display name.
{github:<user>:bio}Profile bio.
{github:<user>:avatar}Avatar image URL.
{github:<user>:url}Profile URL.
{github:<owner>/<repo>}Star count, e.g. {github:facebook/react}.
{github:<owner>/<repo>:forks}Fork count.
{github:<owner>/<repo>:language}Primary language.
{github:<owner>/<repo>:issues}Open issue count.
{github:<owner>/<repo>:description}Repo description.
{github:<owner>/<repo>:url}Repo URL.

Pokemon

{pokemon:<name>}
{pokemon:<name>}Name and types, e.g. {pokemon:pikachu}.
{pokemon:<name>:id}Pokedex number.
{pokemon:<name>:types}Type list.
{pokemon:<name>:height}Height.
{pokemon:<name>:weight}Weight.
{pokemon:<name>:sprite}Sprite image URL.

Reference

{dictionary} / {urban} / {npm}
{dictionary:<word>}First definition. Alias: {define:<word>}.
{urban:<term>}Top Urban Dictionary definition.
{npm:<package>}Latest version, e.g. {npm:react}.
{npm:<package>:description}Package description.
{npm:<package>:license}Package license.
{npm:<package>:homepage}Package homepage URL.

Random

no arguments

These take no arguments. Drop the bare token in and it resolves on each run.

{advice}A random piece of advice.
{joke}A random safe joke.
{cat}A random cat image URL.
{dog}A random dog image URL.
Conditionals

Conditionals

Branch your response with if / elseif / else blocks.

Syntax

{if <condition>}...{elseif <condition>}...{else}...{/if}

Operators

==!=><>=<=containsstartswithendswith&&||

Example

{if {args.length} > 0}Hello {args.0}{else}Hello there{/if}
Actions

Actions

Standard advanced mode runs a sequence of actions built on the dashboard. Each one passes the same guardrails before it fires.

Standard actions

replyReply to the invoking message.
send_embedSend a rendered embed.
send_to_channelSend a message to another channel.
dm_userDirect-message a user.
reactReact to the invoking message.
pin_messagePin the invoking message.
delete_invoking_messageDelete the message that ran the command.
add_role / remove_role / toggle_roleManage a role on the user.
require_roleGate the command behind a role.
counterIncrement, decrement or set a counter.
set_var / db_setSet a render variable or persist a stored value.
waitPause the sequence for 0 to 10 seconds.
ifBranch with nested then / else action lists.
stopHalt the sequence early.
buttonsUp to 5: role-toggle, link, or run another command.

Moderation actions

triple-gated

The invoker must hold the matching Discord permission, the bot must hold it, and the bot must outrank the target.

timeout_userTime a member out.
kick_userKick a member.
ban_userBan a member.
set_nicknameChange a member's nickname.
purgeBulk-delete recent messages.
Script API

Script API

Script-type commands run sandboxed JavaScript. Everything is on the global exceed object; standard Math / JSON / String / Array / Date are available. The script enqueues actions that run after it and pass every guardrail.

Sandbox limits

Enforced

1 second8 MBno networkno filesystemno process

Context

read-only
exceed.user / exceed.target / exceed.randomUser / exceed.botEach carries { id, name, username, tag, mention, nick, display, avatar, createdAt, joinedAt, joinDays, accountAge, isBooster, color, topRole, roles[], roleNames[], isBot }.
exceed.server{ id, name, memberCount, humanCount, botCount, ownerId, owner, createdAt, boostCount, boostTier, roleCount, channelCount, emojiCount, vanity, icon, description }.
exceed.channel{ id, name, mention, topic, nsfw, type, categoryId, createdAt }.
exceed.message{ id, content, link }.
exceed.guildRoles[]{ id, name, mention, color, position, mentionable, hoist, memberCount } for every role.
exceed.guildChannels[]{ id, name, mention, type, categoryId } for every channel.
exceed.guildEmojis[]{ id, name, animated, mention } for every custom emoji.
exceed.args[] / exceed.argString / exceed.mentions[]Parsed arguments, the raw argument string, and resolved mentions.
exceed.prefix / exceed.commandName / exceed.nowThe active prefix, command name and current unix time.

Storage

exceed.store.get(key)Read a guild-scoped value.
exceed.store.set(key, value)Write a guild-scoped value that persists across runs.
exceed.store.setUser(key, value)Write a value scoped to the invoking user.

Lookups

exceed.getRole(id)Resolve a role by id.
exceed.findRole(name)Resolve a role by name.
exceed.getChannel(id)Resolve a channel by id.
exceed.findChannel(name)Resolve a channel by name.
exceed.findEmoji(name)Resolve a custom emoji by name.
exceed.hasRole(id)True if the invoker holds the role.
exceed.targetHasRole(id)True if the target holds the role.

Messaging actions

exceed.reply(text)Reply to the invoking message.
exceed.send(text)Send a plain message to the channel.
exceed.embed(text)Send the text rendered as an embed.
exceed.sendTo(channelId, text)Send a message to another channel.
exceed.dm(text)Direct-message the invoker.
exceed.react(emoji)React to the invoking message.
exceed.pin()Pin the invoking message.
exceed.deleteInvoke()Delete the invoking message.
exceed.button({ label, style, roleId | url | run })Attach a button: role-toggle, link, or run another command (max 5).

Role actions

exceed.addRole(id)Add a role to the invoker.
exceed.removeRole(id)Remove a role from the invoker.
exceed.toggleRole(id)Toggle a role on the invoker.
exceed.requireRole(id)Gate the command; stop unless the invoker holds the role.

Moderation actions

mod-gated
exceed.timeout(seconds, reason)Time the target out.
exceed.kick(reason)Kick the target.
exceed.ban(reason, deleteDays)Ban the target.
exceed.setNick(name)Set the target's nickname.
exceed.purge(count)Bulk-delete recent messages.

State actions

exceed.setVar(name, value)Set a render variable for later tokens.
exceed.counter(name, op, by)Apply a counter op (inc / dec / set).
exceed.incr(name, by)Increment a counter.
exceed.decr(name, by)Decrement a counter.
exceed.setCounter(name, value)Set a counter to an exact value.

Flow actions

exceed.wait(seconds)Pause the action queue (0 to 10 seconds).
exceed.stop()Halt the command and run no further actions.

String helpers

exceed.str.*

Methods

upperlowertitlecapitalizetrimreverserepeat(t, n)padStart(t, n, c)padEnd(t, n, c)truncate(t, n)replace(t, a, b)slice(t, a, b)contains(t, a)startsWith(t, a)endsWith(t, a)count(t, a)words(t)length(t)slug(t)escape(t)

Number helpers

exceed.num.*

Methods

round(v, d)floorceilabsclamp(v, lo, hi)min(...)max(...)sum(arr)avg(arr)pow(a, b)sqrtsignmod(a, b)percent(a, b)format(v)ordinal(v)parse(t)

Format helpers

exceed.fmt.*

Methods

bolditalicunderlinestrikespoilercodecodeblock(t, lang)quoteblockquotelink(text, url)user(id)role(id)channel(id)emoji(name, id)timestamp(unix, style)header(t, level)

Time helpers

exceed.time.*

Methods

unix()ms()relative(unix)format(unix, style)addSeconds(unix, n)addDays(unix, n)diff(a, b)humanize(seconds)

Random helpers

exceed.rng.*

Methods

int(lo, hi)float(lo, hi)bool()chance(percent)choose(arr)shuffle(arr)sample(arr, n)dice(n, sides)weighted([{ value, weight }])

Array helpers

exceed.arr.*

Methods

firstlastuniquecountincludes(a, v)join(a, sep)sortreversesumavgchunk(a, n)range(n)

Top-level shortcuts

Also available

exceed.random(lo, hi)exceed.choose(arr)exceed.shuffle(arr)exceed.dice(n, sides)exceed.range(n)
Examples

Examples

Realistic commands you can adapt to your own server.

Standard variable command

,welcome
Welcome {user.mention} to {server.name}! You are member #{server.membercount}.

Conditional

,roll
{user.name} rolled {random:1-100}{if {random:1-100} > 95} (critical!){/if}

Now playing

,np
{lastfm.status}: {lastfm.track} by {lastfm.artist}
Scrobbles: {lastfm.scrobbles}

Crypto price

,btc
Bitcoin is at ${crypto:bitcoin} ({crypto:bitcoin:change}% today).

Script: react and assign a role

exceed.reply("Hi " + exceed.user.name);
exceed.toggleRole("ROLE_ID");

Script: helpers

var msg = exceed.fmt.bold(exceed.str.title(exceed.argString));
exceed.reply(msg + " rolled " + exceed.rng.dice(2, 6));

Script: persistent storage

var c = Number(exceed.store.get("count") || 0) + 1;
exceed.store.set("count", c);
exceed.reply("Used " + exceed.num.ordinal(c) + " time.");