Skip to content

Instantly share code, notes, and snippets.

@Trao-X
Created July 4, 2026 15:12
Show Gist options
  • Select an option

  • Save Trao-X/4f994b8fa43eacd17d6f15b7032416ec to your computer and use it in GitHub Desktop.

Select an option

Save Trao-X/4f994b8fa43eacd17d6f15b7032416ec to your computer and use it in GitHub Desktop.
Traosyst Tag System Documentation

Traosyst features a highly advanced, Assyst-inspired tag engine. Tags allow users to create custom commands with complex logic, math operations, and even sandboxed JavaScript execution.

Tags are strictly server-specific. A tag created in Server A will not leak into Server B.


Command Reference

The base command for all tag operations is .tag (or the alias .t).

  • .tag create <name> <content> β€” Creates a new tag. (30-second cooldown per user).
  • .tag edit <name> <content> β€” Modifies an existing tag.
  • .tag delete <name> β€” Deletes a tag.
  • .tag rename <old_name> <new_name> β€” Renames a tag.
  • .tag info <name> β€” Displays the author, usage count, and creation/edit dates.
  • .tag raw <name> β€” Sends the raw code of a tag so you can copy it.
  • .tag list [page] β€” Shows all tags in the current server.
  • .tag run <name> [args...] β€” Executes a tag.
  • .tag nuke β€” [Bot Owner Only] Wipes all tags across all servers.

Direct Invocation

You don't actually need to type .tag run mytag. You can just type .mytag. If a tag exists with that name (and it doesn't conflict with a native bot command), it will run automatically.


Tag Syntax & Subtags

Tags are built using "subtags" wrapped in {curly_braces}. Subtags can take arguments separated by a pipe |. They can also be infinitely nested inside each other up to 15 levels deep.

If you ever need to type a literal { without triggering the parser, escape it with a backslash: \{.

1. Variables & Arguments

  • {args} β€” The raw string of all arguments passed to the tag.
  • {argslen} β€” The total number of arguments provided.
  • {arg:index} β€” Gets a specific argument by index (0-based). Throws an error if missing.
  • {tryarg:index} β€” Gets a specific argument by index, but returns blank if missing.
  • {set:key|value} β€” Saves a value to a temporary variable.
  • {get:key} β€” Retrieves a saved variable.
  • {delete:key} β€” Deletes a saved variable.

2. Control Flow

  • {if:a|operator|b|then|else} β€” Evaluates a condition. If true, runs the then branch; otherwise, runs the else branch.
    • Supported operators: = (exact match), ~ (case-insensitive match), >, >=, <, <=.
    • Note: Unused branches are entirely ignored to save processing power.
  • {choose:option1|option2|...} β€” Picks a random item from the list.
  • {tag:name|args...} β€” Executes another existing tag and embeds its output.

3. Strings

  • {lower:text} β€” Converts text to lowercase.
  • {upper:text} β€” Converts text to uppercase.
  • {length:text} β€” Returns the character count of the text.
  • {replace:find|replaceWith|text} β€” Replaces all occurrences of a string.
  • {repeat:count|text} β€” Repeats the text count times.
  • {reverse:text} β€” Reverses the text.

4. Math

  • {range:min|max} β€” Returns a random whole number between min and max.
  • {max:a|b|c...} β€” Returns the highest number.
  • {min:a|b|c...} β€” Returns the lowest number.
  • {abs:n}, {sqrt:n}, {cos:n}, {sin:n}, {tan:n} β€” Standard math functions.
  • {e}, {pi} β€” Math constants.

5. Discord Data

  • {userid} β€” The ID of the user running the tag.
  • {channelid} β€” The ID of the channel the tag is running in.
  • {usertag:[id]} β€” Gets a user's Name#1234. If ID is omitted, gets the tag author's name.
  • {avatar:[id]} β€” Gets a user's avatar URL.
  • {mention:id} β€” Turns a raw ID into an active <@mention>.
  • {idof:@mention} β€” Extracts the raw ID from a mention.
  • {lastattachment} β€” Scans the last 20 messages in the channel and returns the URL of the most recent image/file attachment.

6. Network

  • {download:url} β€” Makes a GET request to the URL and returns the raw text response.
    • Limits: Max 50 KB payload. Max 5 requests per tag. Private/local IPs are blocked.

Secure JavaScript Engine ({js:code})

For complex logic that native subtags can't handle, you can write raw JavaScript using {js: ...code...}.

The code runs in an ultra-secure, heavily restricted Node.js Virtual Machine.

What you need to know:

  • It is Sandboxed: You have no access to process, require, fs, console, or anything related to the host computer. It is impossible to steal .env tokens or break the bot.
  • Top-Level Await: The code is wrapped in an async function automatically, so you can use await natively.
  • Returning Values: The tag will output whatever you return at the end of your script.
  • Image Generation: If your {js} block returns a raw Buffer or Uint8Array, the bot will automatically upload it as a PNG attachment rather than text!
  • Timeouts: The VM will forcefully kill your script if it takes longer than 2.5 seconds to complete.

Available Variables inside {js:...}

When your JavaScript runs, the bot injects a few safe, read-only variables into the environment for you to use:

  • args (Array) β€” An array of strings containing the arguments passed to the tag. Example: If run with .tagname hello world, args[0] is "hello" and args[1] is "world".
  • authorId (String) β€” The Discord Snowflake ID of the user who executed the tag.
  • channelId (String) β€” The Discord Snowflake ID of the channel where the tag was run.
  • guildId (String) β€” The Discord Snowflake ID of the server.
  • modalFields (Object|null) β€” A dictionary of values if the tag was triggered by a Modal Submission. Otherwise null.

Available Functions inside {js:...}

  • fetch(url, options) β€” A safe wrapper around the standard Fetch API. You can use it to make external API requests.
    • Security note: As a measure to prevent SSRF attacks, all requests to localhost, 127.0.0.1, 0.0.0.0, and private 192.168.x.x / 10.x.x.x networks are hard-blocked.
  • console.log(...) (and .error, .warn, .info) β€” Prints data directly to the bot's Discord output stream. Useful for debugging without breaking your return statement.

JS Example:

{js:
  // Fetch an API and return the title
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  const data = await res.json();
  return "The task is: " + data.title;
}

5. Rich Embeds & Interactive Buttons

The Tag Engine has been upgraded! Your JavaScript subtags ({js:...}) can now return Discord Embeds and Interactive Buttons, transforming your tags into mini-applications!

To do this, simply return a plain JavaScript object containing an embeds or components array. The engine detects it automatically β€” no special type field required.

Structure

return {
  content: "Optional text above the embed",
  embeds: [
    {
      title: "My Embed",
      description: "This is a rich embed!",
      color: 0x5865F2
    }
  ],
  components: [
    {
      type: 1, // ActionRow
      components: [
        {
          type: 2, // Button
          style: 1, // 1=Blurple, 2=Grey, 3=Green, 4=Red
          label: "Click Me!",
          custom_id: "tagbtn_mytagname_myaction" 
        }
      ]
    }
  ]
};

How Tag Buttons Work

When a user clicks a button, the bot needs to know what to do.

  1. Your button's custom_id MUST start with tagbtn_.
  2. The second part must be your tag's exact name. (e.g. tagbtn_testtag_xyz).
  3. When clicked, the bot will re-run your tag from the beginning!
  4. Any text after the tag name is passed into args[0], args[1], etc.

Interactive Example

Here is a complete example of a tag named clicker that counts how many times a button is clicked.

Command: .tag create clicker {js:...}

{js:
  // We use args[0] to track our state!
  // If the user just typed .clicker, args[0] is empty, so clicks = 0.
  // If they clicked the button, the custom_id was "tagbtn_clicker_1", so args[0] is "1".
  
  let clicks = parseInt(args[0]) || 0;
  
  // If they clicked the button, increase the count!
  if (args[0] !== undefined) {
    clicks += 1;
  }
  
  // Create the new button custom_id for the NEXT click
  const nextAction = "tagbtn_clicker_" + clicks;
  
  // Just return an object with embeds/components β€” no special type field needed!
  return {
    embeds: [{
      title: "Button Clicker",
      description: "You have clicked the button " + clicks + " times!",
      color: 0x2ECC71
    }],
    components: [{
      type: 1,
      components: [{
        type: 2,
        style: 3,
        label: "Click +1",
        custom_id: nextAction
      }]
    }]
  };
}

6. Advanced JS Patterns

Here are some powerful ways you can combine the JS engine with Discord's features.

Pattern: State Machines

Because the only way to persist data between button clicks is through the custom_id, you can serialize simple JSON data into the button itself, or use args as a state machine.

{js:
  // State is passed via args!
  // e.g. tagbtn_mytag_menu_page2
  const view = args[0] || 'main';
  const data = args[1] || 'default';

  if (view === 'main') {
    return { 
      content: "Main Menu", 
      components: [{ type: 1, components: [{ type: 2, style: 1, label: "Settings", custom_id: "tagbtn_mytag_settings_none" }] }] 
    };
  } else if (view === 'settings') {
    return { 
      content: "Settings Menu", 
      components: [{ type: 1, components: [{ type: 2, style: 2, label: "Back", custom_id: "tagbtn_mytag_main_none" }] }] 
    };
  }
}

Pattern: Validating the Author

By default, anyone can click a button on a tag message. If you want to restrict a button so only the original author can click it, pass the authorId into the custom_id and verify it!

{js:
  // When creating the first message:
  if (!args[0]) {
    return {
      content: "Only you can click this button!",
      components: [{ type: 1, components: [{ type: 2, style: 1, label: "Click Me", custom_id: `tagbtn_mytag_click_${authorId}` }] }]
    };
  }

  // When the button is clicked:
  const originalAuthor = args[1];
  if (authorId !== originalAuthor) {
    // If someone else clicks it, return an ephemeral error (handled by bot automatically) or a polite message:
    return "❌ You are not allowed to use this button.";
  }

  return "βœ… Verification passed!";
}

Limits & Boundaries

To prevent the bot from crashing or being abused, the parser enforces the following hard limits per execution:

  • 15 maximum nesting levels deep.
  • 500 total parser iterations.
  • 100 variables maximum via {set}.
  • 5 total HTTP requests (combined {download} and fetch()).
  • 10,000 characters maximum total output length.

Component Limits

  • Legacy mode (no IS_COMPONENTS_V2 flag): 1 embed, 1 action row, 5 buttons max.
  • Components V2 mode (flags: 1 << 15): Up to 20 top-level components (Discord's real limit is 40, but the bot enforces 20 to prevent abuse). Max 4,000 chars across all text.

8. Debugging & Common Pitfalls

Building advanced tags can sometimes lead to unexpected errors. Here's a troubleshooting guide:

"This interaction failed"

This means the bot took longer than 3 seconds to respond to Discord, or sent a payload Discord completely rejected.

  • Cause 1: Your {js} script is doing something too slow (like multiple heavy fetch calls). The script must finish in under 2.5 seconds.
  • Cause 2: You returned { content: "" } (an empty string). Discord rejects this in V2 mode. Just omit content entirely if you don't need text.
  • Cause 3: You didn't prefix your button's custom_id with tagbtn_tagname_.

"Tag Interaction Error"

This means your JavaScript code threw an exception. The bot will reply to you ephemerally with the exact error.

  • Double check your variable names.
  • Ensure APIs you are calling actually exist and return valid JSON (use try/catch block around res.json()).

My file attachment didn't work!

If you uploaded a .js or .txt file containing your tag code to bypass Discord's character limit, but the bot says "You must provide tag content":

  • Ensure the file is actually named .js or .txt.
  • Make sure you still provided the tag name! The correct syntax is .tag create mytagname + [Attached File].

Select Menu values are missing

Remember that select menus append their values to the END of the args array. If your custom ID is tagbtn_mytag_menu1, then args[0] will be "menu1" and args[1] will be the value the user selected from the dropdown!


6. Components V2

Components V2 is a powerful Discord feature that lets you build fully structured, layout-driven messages using a flat component tree instead of embeds.

To activate it, add flags: 1 << 15 (= 32768) to your return object. When this flag is set:

  • content and embeds fields are ignored by Discord
  • All text must go in Text Display components (type 10)
  • You can use layout components like Containers, Sections, Separators, and Media Galleries
  • Up to 20 top-level components are allowed by the bot (Discord allows 40)
  • Max 4,000 characters across all text combined

Available Components (Bot Messages)

Type Name Description
1 Action Row Container for buttons or a select menu
2 Button Clickable button (styles: 1=Blue, 2=Grey, 3=Green, 4=Red, 5=Link)
3 String Select Dropdown with custom text options
5 User Select Dropdown that lists server members
6 Role Select Dropdown that lists server roles
7 Mentionable Select Dropdown of users and roles combined
8 Channel Select Dropdown that lists channels
9 Section Side-by-side text + a thumbnail or button accessory
10 Text Display Markdown text block (replaces content)
11 Thumbnail Small image used as a Section accessory
12 Media Gallery Grid of 1–10 images with optional captions
13 File Displays an uploaded attachment
14 Separator Visual divider with padding
17 Container Visually grouped card with optional color bar

How Select Menus Work With Tags

When a user picks an option from a select menu, the selected values are passed as additional args after any existing args from the custom_id.

For example, if your custom_id is tagbtn_mymenu_page2 and the user selects option_a, your tag receives:

  • args[0] = "page2"
  • args[1] = "option_a" (the selected value)

Modals (Popups)

If a tag is triggered by a component interaction (like a button click), it can pop up a Modal instead of updating the message. To do this, return a modal object instead of components or embeds.

return {
  modal: {
    title: "Feedback Form",
    custom_id: "tagbtn_mytag_submitfeedback",
    components: [
      {
        type: 1, // Action Row
        components: [{
          type: 4, // Text Input
          custom_id: "feedback_text",
          label: "Your Feedback",
          style: 2, // Paragraph
          required: true
        }]
      }
    ]
  }
};

When the user submits the modal, the bot re-runs your tag using the custom_id you provided. The values they typed will be available in the modalFields object!

// Inside your tag code when 'submitfeedback' triggers:
const feedback = modalFields["feedback_text"];
// Now you can return a normal components v2 payload thanking them!

Example: Full Components V2 Showcase

This tag demonstrates every major V2 component type. Create it with .tag create showcase {js:...}:

{js:
let page = args[0] || 'home';

if (page === 'home') {
  return {
    flags: 1 << 15,
    components: [
      {
        type: 17, // Container
        accent_color: 0x5865F2,
        components: [
          { type: 10, content: "# 🌟 Components V2 Showcase\nThis message is built entirely with Components V2." },
          { type: 14, divider: true, spacing: 1 }, // Separator
          {
            type: 9, // Section
            components: [
              { type: 10, content: "## πŸ“– What is this?\nA full showcase of every **Components V2** layout and interactive element, built inside a single tag." },
              { type: 10, content: "-# Click the buttons below to explore!" }
            ],
            accessory: {
              type: 11, // Thumbnail
              media: { url: "https://cdn.discordapp.com/embed/avatars/0.png" },
              description: "Bot icon"
            }
          },
          { type: 14, divider: true, spacing: 1 }, // Separator
          {
            type: 12, // Media Gallery
            items: [
              { media: { url: "https://picsum.photos/seed/v2a/400/200" }, description: "Gallery image 1" },
              { media: { url: "https://picsum.photos/seed/v2b/400/200" }, description: "Gallery image 2" }
            ]
          },
          { type: 14, divider: false, spacing: 2 }, // Spacer
          { type: 10, content: "### πŸ”€ Navigate" },
          {
            type: 1, // Action Row - Buttons
            components: [
              { type: 2, style: 1, label: "Info", emoji: { name: "ℹ️" }, custom_id: "tagbtn_showcase_info" },
              { type: 2, style: 3, label: "Selects Demo", emoji: { name: "πŸ”½" }, custom_id: "tagbtn_showcase_selects" },
              { type: 2, style: 2, label: "Modals Demo", emoji: { name: "πŸ“" }, custom_id: "tagbtn_showcase_modaldemo" },
              { type: 2, style: 4, label: "Close", emoji: { name: "❌" }, custom_id: "tagbtn_showcase_close" }
            ]
          }
        ]
      }
    ]
  };
} else if (page === 'info') {
  return {
    flags: 1 << 15,
    components: [{
      type: 17,
      accent_color: 0x2ECC71,
      components: [
        { type: 10, content: "## ℹ️ Info Page\nHere is some information about this bot. This page was loaded by clicking a button β€” no new message was posted!" },
        { type: 14, divider: true, spacing: 1 },
        { type: 10, content: "- Built with **Node.js** and **Discord.js v14**\n- Runs on **AWS EC2** inside **Docker**\n- Tag engine supports `{js}`, `{if}`, `{set}`, `{get}`, and more!" },
        {
          type: 1,
          components: [
            { type: 2, style: 2, label: "← Back", custom_id: "tagbtn_showcase_home" }
          ]
        }
      ]
    }]
  };
} else if (page === 'selects') {
  return {
    flags: 1 << 15,
    components: [{
      type: 17,
      accent_color: 0xF39C12,
      components: [
        { type: 10, content: "## πŸ”½ Select Menus Demo\nPick an option below! The bot will re-run the tag with your selection passed as an argument." },
        { type: 14, divider: true, spacing: 1 },
        {
          type: 1, // Action Row with String Select
          components: [{
            type: 3, // String Select
            custom_id: "tagbtn_showcase_picked",
            placeholder: "Choose a colour...",
            options: [
              { label: "Red",   value: "red",   emoji: { name: "πŸ”΄" } },
              { label: "Green", value: "green", emoji: { name: "🟒" } },
              { label: "Blue",  value: "blue",  emoji: { name: "πŸ”΅" } }
            ]
          }]
        },
        {
          type: 1,
          components: [{ type: 2, style: 2, label: "← Back", custom_id: "tagbtn_showcase_home" }]
        }
      ]
    }]
  };
} else if (page === 'picked') {
  const picked = args[1] || 'nothing';
  const colours = { red: 0xE74C3C, green: 0x2ECC71, blue: 0x3498DB };
  return {
    flags: 1 << 15,
    components: [{
      type: 17,
      accent_color: colours[picked] || 0x95A5A6,
      components: [
        { type: 10, content: `## You picked: **${picked}** βœ…\nThe container's accent bar changed colour to match your choice!` },
        {
          type: 1,
          components: [
            { type: 2, style: 2, label: "← Try again", custom_id: "tagbtn_showcase_selects" },
            { type: 2, style: 2, label: "← Home", custom_id: "tagbtn_showcase_home" }
          ]
        }
      ]
    }]
  };
} else if (page === 'modaldemo') {
  // Return a modal popup! No 'flags' needed for modals.
  return {
    modal: {
      title: "Tell us about yourself",
      custom_id: "tagbtn_showcase_modalsubmit",
      components: [
        {
          type: 1,
          components: [{
            type: 4, // Text Input
            custom_id: "input_name",
            label: "What's your name?",
            style: 1, // Short
            required: true
          }]
        },
        {
          type: 1,
          components: [{
            type: 4,
            custom_id: "input_color",
            label: "Favorite color?",
            style: 1,
            required: false
          }]
        }
      ]
    }
  };
} else if (page === 'modalsubmit') {
  // Read from the modalFields object!
  const name = modalFields["input_name"] || "Mystery Person";
  const color = modalFields["input_color"] || "Invisible";
  
  return {
    flags: 1 << 15,
    components: [{
      type: 17,
      accent_color: 0x9B59B6,
      components: [
        { type: 10, content: `## πŸ“ Modal Submitted!\nHello, **${name}**! Your favorite color is **${color}**.` },
        {
          type: 1,
          components: [
            { type: 2, style: 2, label: "← Home", custom_id: "tagbtn_showcase_home" }
          ]
        }
      ]
    }]
  };
} else if (page === 'close') {
  return {
    flags: 1 << 15,
    components: [{
      type: 17,
      accent_color: 0x95A5A6,
      components: [
        { type: 10, content: "πŸ‘‹ **Showcase closed.**\nType `.showcase` to open it again." }
      ]
    }]
  };
}
}

Comments are disabled for this gist.