Extension Development Guide
This comprehensive guide covers everything you need to know to develop extensions for Skynet Bot.
Table of Contents
- Introduction
- Extension Types
- Available Modules
- API Reference
- Scopes & Permissions
- Best Practices
- Examples
Introduction
Extensions allow server administrators to add custom functionality to their bot without modifying the core codebase. Extensions run in a secure, isolated sandbox environment with controlled access to Discord and server data.
Key Features
- Isolated Execution: Extensions run in a secure V8 isolate (isolated-vm)
- Scope-Based Permissions: Fine-grained control over what extensions can access
- Persistent Storage: Each extension can store data (up to 25KB)
- Multiple Types: Commands, keywords, events, and timers
Extension Types
Command Extensions
Triggered when a user runs a specific command with the bot's prefix.
const command = require("command");
const message = require("message");
// command.prefix - The server's command prefix
// command.suffix - Everything after the command name
// command.key - The command trigger word
message.reply(`You said: ${command.suffix}`);
Slash Command Extensions
Triggered when a user runs a Discord slash command (/command).
const interaction = require("interaction");
// interaction.options - Map of option names to values
// interaction.user - The user who ran the command
// interaction.commandName - The slash command name
const name = interaction.options.name;
await interaction.reply(`Hello, ${name || "User"}!`);
Builder Configuration:
- Slash command name: The command name (lowercase, no spaces)
- Slash command description: Shown in Discord's command picker
- Slash options (JSON): Array of option objects defining parameters
Slash Options Format:
[
{"name": "query", "description": "Search query", "type": "string", "required": true},
{"name": "count", "description": "Number of results", "type": "integer", "required": false}
]
Supported option types: string, integer, number, boolean, user, channel, role, mentionable, attachment
Keyword Extensions
Triggered when specific keywords appear in messages.
const keyword = require("keyword");
const message = require("message");
// keyword.keywords - Array of trigger keywords
if (message.content.toLowerCase().includes("hello")) {
message.reply("Hello there! 👋");
}
Event Extensions
Triggered by Discord events (member join, leave, message delete, etc.).
const event = require("event");
const guild = require("guild");
// Available events: guildMemberAdd, guildMemberRemove, messageDelete, etc.
console.log(`Event triggered in ${guild.name}`);
Timer Extensions
Run at specified intervals (minimum 5 minutes).
const guild = require("guild");
const bot = require("bot");
// Runs every X minutes as configured
console.log(`Timer executed for ${guild.name}`);
Available Modules
Core Modules
| Module | Description | Scope Required |
|---|---|---|
message |
The triggering message | None |
channel |
Channel information | channels_read |
guild |
Server information | guild_read |
member |
Message author as member | members_read |
user / author |
Message author as user | None |
roles |
Guild role information | roles_read |
config |
Server Skynet config | config |
bot |
Bot user information | None |
command |
Command data (command type) | None |
keyword |
Keyword data (keyword type) | None |
event |
Event data (event type) | None |
interaction |
Slash command interaction | None |
extension |
Extension metadata & storage | storage |
utils |
Utility functions | None |
embed |
Embed builder helper | None |
points / economy |
Server economy/points data | members_read |
http |
HTTP requests (Tier 2) | http_request |
API Reference
Message Module
const message = require("message");
// Properties
message.id // Message snowflake ID
message.content // Raw message content
message.suffix // Content after command (for commands)
message.createdAt // ISO date string
// Author info (always available)
message.author.id // User ID
message.author.username // Username
message.author.tag // Username#discriminator
message.author.bot // Boolean
// Channel info (in message)
message.channel.id // Channel ID
message.channel.name // Channel name
message.channel.type // Channel type number
// Guild info (in message)
message.guild.id // Guild ID
message.guild.name // Guild name
Member Module
const member = require("member"); // Requires members_read scope
// Properties
member.id // Member/User ID
member.nickname // Server nickname or null
member.displayName // Nickname or username
member.joinedTimestamp // When they joined (ms)
member.roles // Array of role IDs
member.avatarURL // Avatar URL
member.kickable // Can bot kick them?
member.bannable // Can bot ban them?
member.manageable // Can bot manage them?
member.isOwner // Is server owner?
member.pending // Pending verification?
// Nested user object
member.user.id
member.user.username
member.user.tag
member.user.bot
member.user.createdTimestamp
User/Author Module
const user = require("user");
// OR
const author = require("author");
// Properties
user.id // User snowflake ID
user.username // Username
user.displayName // Display name or username
user.discriminator // Discriminator (usually "0")
user.tag // Full tag
user.bot // Is a bot?
user.system // Is system user?
user.avatar // Avatar hash
user.banner // Banner hash
user.accentColor // Accent color
user.createdTimestamp // Account creation time
user.avatarURL // Avatar URL
user.defaultAvatarURL // Default avatar URL
Guild Module
const guild = require("guild"); // Requires guild_read scope
// Properties
guild.id // Guild ID
guild.name // Guild name
guild.icon // Icon hash
guild.banner // Banner hash
guild.description // Guild description
guild.ownerId // Owner's user ID
guild.memberCount // Total members
guild.premiumTier // Boost level (0-3)
guild.premiumSubscriptionCount // Number of boosts
guild.verificationLevel // Verification level
guild.verified // Is verified?
guild.partnered // Is partnered?
guild.preferredLocale // Locale string
guild.vanityURLCode // Vanity URL code
guild.afkChannelId // AFK channel ID
guild.systemChannelId // System channel ID
guild.rulesChannelId // Rules channel ID
guild.createdTimestamp // Creation time
Channel Module
const channel = require("channel"); // Requires channels_read scope
// Properties
channel.id // Channel ID
channel.name // Channel name
channel.type // Channel type number
channel.topic // Channel topic
channel.nsfw // Is NSFW?
channel.position // Position in list
channel.parentId // Category ID
channel.rateLimitPerUser // Slowmode seconds
channel.lastMessageId // Last message ID
channel.guildId // Guild ID
channel.createdTimestamp // Creation time
Roles Module
const roles = require("roles"); // Requires roles_read scope
// Properties
roles.count // Number of roles
roles.list // Array of role objects
roles.byId // Object keyed by role ID
roles.highest // Highest role { id, name }
roles.everyone // @everyone role { id, name }
// Each role object contains:
// - id, name, color, hexColor, position
// - hoist, mentionable, managed, permissions, members
Utils Module
The utils module provides extensive helper functions organized by category.
Text Utilities
const utils = require("utils");
utils.text.upper("hello") // "HELLO"
utils.text.lower("HELLO") // "hello"
utils.text.capitalize("hello world") // "Hello World"
utils.text.capitalizeFirst("hello") // "Hello"
utils.text.reverse("hello") // "olleh"
utils.text.truncate("long text", 5) // "lo..."
utils.text.pad("hi", 5, "-") // "hi---"
utils.text.clean(" too many ") // "too many"
utils.text.slugify("Hello World!") // "hello-world"
utils.text.count("hello", "l") // 2
utils.text.includes("Hello", "ell") // true
utils.text.mock("hello") // "hElLo"
utils.text.replaceAll("aaa", "a", "b") // "bbb"
utils.text.words("hello world") // ["hello", "world"]
utils.text.escapeHtml("<script>") // "<script>"
utils.text.escapeMarkdown("**bold**") // "\\*\\*bold\\*\\*"
Random Utilities
const utils = require("utils");
utils.random.int(1, 10) // Random integer 1-10
utils.random.float(0, 1) // Random float 0-1
utils.random.pick(["a", "b", "c"]) // Random element
utils.random.pickMultiple(arr, 3) // 3 random unique elements
utils.random.shuffle([1, 2, 3]) // Shuffled array
utils.random.bool() // true or false (50%)
utils.random.bool(0.7) // true 70% of the time
utils.random.string(8) // "xK2mN9pQ"
utils.random.string(8, "hex") // "a3f2c1e9"
utils.random.dice("2d6+3") // { rolls: [3,5], total: 11 }
utils.random.weighted([
{ item: "common", weight: 70 },
{ item: "rare", weight: 25 },
{ item: "legendary", weight: 5 }
]) // Weighted selection
Math Utilities
const utils = require("utils");
utils.math.clamp(15, 0, 10) // 10
utils.math.lerp(0, 100, 0.5) // 50
utils.math.map(5, 0, 10, 0, 100) // 50
utils.math.round(3.14159, 2) // 3.14
utils.math.percentage(25, 100) // 25
utils.math.sum([1, 2, 3]) // 6
utils.math.average([1, 2, 3]) // 2
utils.math.min([5, 2, 8]) // 2
utils.math.max([5, 2, 8]) // 8
utils.math.between(5, 1, 10) // true
Array Utilities
const utils = require("utils");
utils.array.unique([1, 1, 2, 2, 3]) // [1, 2, 3]
utils.array.chunk([1,2,3,4,5], 2) // [[1,2], [3,4], [5]]
utils.array.flatten([[1,2], [3,4]]) // [1, 2, 3, 4]
utils.array.first([1, 2, 3], 2) // [1, 2]
utils.array.last([1, 2, 3], 2) // [2, 3]
utils.array.compact([0, 1, "", 2]) // [1, 2]
utils.array.countBy(["a","a","b"]) // { a: 2, b: 1 }
utils.array.groupBy(arr, "type") // Groups by property
utils.array.intersection([1,2], [2,3]) // [2]
utils.array.difference([1,2,3], [2]) // [1, 3]
Format Utilities
const utils = require("utils");
utils.format.number(1234567) // "1,234,567"
utils.format.currency(29.99) // "$29.99"
utils.format.currency(29.99, "EUR") // "€29.99"
utils.format.bytes(1536) // "1.5 KB"
utils.format.duration(3661000) // "1h 1m"
utils.format.duration(3661000, true) // "1 hour, 1 minute, 1 second"
utils.format.ordinal(1) // "1st"
utils.format.ordinal(22) // "22nd"
utils.format.list(["a", "b", "c"]) // "a, b, and c"
utils.format.pluralize(1, "cat") // "cat"
utils.format.pluralize(3, "cat") // "cats"
utils.format.progressBar(50, 100) // "█████░░░░░"
Time Utilities
const utils = require("utils");
utils.time.now() // Current timestamp (ms)
utils.time.unix() // Current timestamp (seconds)
utils.time.parse("2024-01-01") // Timestamp from string
utils.time.iso() // ISO string
utils.time.discord(Date.now()) // "<t:1234567890:f>"
utils.time.discord(Date.now(), "R") // "<t:1234567890:R>" (relative)
utils.time.relative(oldTimestamp) // "2 hours ago"
utils.time.add(Date.now(), 1, "h") // Add 1 hour
utils.time.startOf(Date.now(), "day") // Start of today
Discord Utilities
const utils = require("utils");
utils.discord.userMention("123") // "<@123>"
utils.discord.channelMention("123") // "<#123>"
utils.discord.roleMention("123") // "<@&123>"
utils.discord.emoji("name", "123") // "<:name:123>"
utils.discord.codeBlock("code", "js") // \`\`\`js\ncode\n\`\`\`
utils.discord.inlineCode("code") // \`code\`
utils.discord.bold("text") // "**text**"
utils.discord.italic("text") // "*text*"
utils.discord.underline("text") // "__text__"
utils.discord.strikethrough("text") // "~~text~~"
utils.discord.spoiler("text") // "||text||"
utils.discord.quote("text") // "> text"
utils.discord.blockQuote("text") // ">>> text"
utils.discord.hyperlink("text", "url") // "[text](url)"
utils.discord.snowflakeToTimestamp("123456789") // Creation time
Embed Module
const embed = require("embed");
// Create an embed
const myEmbed = embed.create({
title: "My Title",
description: "My description",
color: embed.colors.BLUE,
fields: [
{ name: "Field 1", value: "Value 1", inline: true },
{ name: "Field 2", value: "Value 2", inline: true }
],
footer: { text: "Footer text" },
thumbnail: { url: "https://example.com/image.png" },
image: { url: "https://example.com/image.png" },
author: { name: "Author", iconURL: "https://..." },
timestamp: new Date().toISOString()
});
// Available colors
embed.colors.DEFAULT // Black
embed.colors.BLUE // Blue
embed.colors.GREEN // Green
embed.colors.RED // Red
embed.colors.GOLD // Gold
embed.colors.PURPLE // Purple
embed.colors.ORANGE // Orange
embed.colors.BLURPLE // Discord Blurple
embed.colors.SUCCESS // Bright Green
embed.colors.ERROR // Bright Red
embed.colors.WARNING // Orange
embed.colors.INFO // Blue
// Parse custom color
embed.resolveColor("#FF5500")
embed.resolveColor("RANDOM")
embed.resolveColor([255, 85, 0])
Interaction Module (Slash Commands)
const interaction = require("interaction"); // Only for slash command extensions
// Properties
interaction.id // Interaction ID
interaction.commandName // The slash command name
interaction.guildId // Guild ID
interaction.channelId // Channel ID
// User who triggered the command
interaction.user.id // User ID
interaction.user.username // Username
interaction.user.tag // User tag
interaction.user.bot // Is bot?
// Options (parameters passed to the command)
interaction.options // Object of option name -> value
// For user/channel/role options, value is { id, type }
// For attachment options: { id, type, url, name }
Interaction Methods
// Reply to the interaction
await interaction.reply("Hello!");
await interaction.reply({ content: "Hello!", ephemeral: true });
await interaction.reply({ embeds: [myEmbed] });
// Defer reply (for long operations)
await interaction.deferReply();
await interaction.deferReply({ ephemeral: true });
// Edit a deferred reply
await interaction.editReply("Done processing!");
// Send follow-up messages
await interaction.followUp("Additional info!");
Points/Economy Module
Access the server's points and ranking system.
const points = require("points"); // Requires members_read scope
// OR
const economy = require("economy"); // Alias
// Check if points system is enabled
points.isEnabled // Boolean
points.canWrite // Boolean - true if economy_manage scope granted
// Your points data
points.self.userId // Your user ID
points.self.rankScore // Your rank score
points.self.messages // Total messages sent
points.self.voice // Voice activity time
points.self.rank // Current rank name
points.self.position // Leaderboard position
// Server leaderboard (top 25 by default)
points.leaderboard // Array of user entries
// Server ranks configuration
points.ranks // Array of rank objects
// --- READ METHODS ---
points.getSelf() // Get your points data
points.getUser(userId) // Get another user's points
points.getLeaderboard(limit) // Get leaderboard (max 100)
// --- WRITE METHODS (require economy_manage scope) ---
points.addPoints(userId, amount, reason) // Add points (max 10,000)
points.removePoints(userId, amount, reason) // Remove points
points.transfer(from, to, amount, reason) // Transfer between users
points.setPoints(userId, amount, reason) // Set to specific value
HTTP Module (Tier 2 Only)
Make external API requests from your extension.
const http = require("http"); // Requires http_request scope + appropriate network capability
const res = await http.request({
url: "https://api.jikan.moe/v4/anime?q=naruto&limit=1",
method: "GET", // GET or POST
responseType: "json", // "json" or "text"
timeoutMs: 8000, // Max 15000ms
maxBytes: 1048576, // Max response size
});
// Response object
res.success // Boolean
res.error // Error string if failed
res.status // HTTP status code
res.headers // Response headers
res.body // Raw response text
res.json // Parsed JSON (if responseType is "json")
Network Capability Levels:
| Level | Description | Approval |
|---|---|---|
none |
No network access | N/A |
allowlist_only |
Pre-approved APIs only (Jikan, Steam, Mojang) | Auto |
network |
Any HTTPS endpoint | Requires approval |
network_advanced |
HTTP, custom ports, webhooks | Requires approval |
Extension Module
const extension = require("extension");
// Extension metadata
extension.name // Extension name
extension.version // Version string
extension.type // "command", "slash", "keyword", "event", "timer"
// Persistent storage (requires storage scope)
extension.storage.get(key) // Get stored value
await extension.storage.write(key, value) // Store value (25KB limit)
await extension.storage.delete(key) // Delete key
await extension.storage.clear() // Clear all storage
// Server-admin configured settings (read-only)
extension.settings.get(key) // Get setting value
extension.settings.getAll() // Get all settings
Dashboard Settings (Server Admin UI)
Extension developers can add custom configuration panels to the server owner dashboard. Server admins see these fields when managing the extension.
Defining Dashboard Settings
Add a dashboard_settings object to your extension version:
{
"dashboard_settings": {
"enabled": true,
"sections": [
{
"id": "api_config",
"title": "API Configuration",
"fields": [
{
"id": "api_url",
"type": "text",
"label": "API Endpoint",
"placeholder": "https://api.example.com",
"required": true
},
{
"id": "api_key",
"type": "secret",
"label": "API Key"
},
{
"id": "max_results",
"type": "number",
"label": "Max Results",
"default": 10,
"min": 1,
"max": 100
}
]
}
]
}
}
Supported Field Types
| Type | Description | Extra Options |
|---|---|---|
text |
Single-line text input | placeholder |
textarea |
Multi-line text input | placeholder |
number |
Numeric input | min, max, default |
toggle |
Checkbox/boolean | default |
select |
Dropdown menu | options: [{value, label}] |
secret |
Password (encrypted) | Never shown after saving |
channel_select |
Server channel picker | - |
Accessing Settings in Code
const extension = require("extension");
const http = require("http");
// Read server-admin configured values
const apiUrl = extension.settings.get("api_url");
const maxResults = extension.settings.get("max_results") || 10;
// For HTTP requests with secrets (auto-injected into headers)
const res = await http.request({
url: apiUrl + "/search",
method: "GET",
injectSecrets: { "Authorization": "api_key" }
});
Security Notes
- Secrets are encrypted at rest and never exposed to extension code
- Secrets are injected into HTTP request headers by the sandbox runtime
- Server admins configure these values in Dashboard → Extensions
Bot Module
const bot = require("bot");
bot.user.id // Bot's user ID
bot.user.username // Bot's username
bot.user.tag // Bot's tag
bot.prefix // Server's command prefix
Config Module
const config = require("config"); // Requires config scope
// Access server Skynet configuration
config.command_prefix // Command prefix
config.name_display // Name display setting
// ... other config options
Scopes & Permissions
Extensions must declare which scopes they need. Users installing the extension will see what permissions it requires.
Message Scopes
| Scope | Description |
|---|---|
messages_read |
Read message history in current channel |
messages_global |
Read messages in all channels |
messages_write |
Send messages in all channels |
messages_manage |
Delete messages |
embed_links |
Send rich embeds with links/images |
reactions |
Add and manage reactions |
Member Scopes
| Scope | Description |
|---|---|
members_read |
Access member information |
members_manage |
Manage nicknames, etc. |
Role Scopes
| Scope | Description |
|---|---|
roles_read |
Access guild role information |
roles_manage |
Assign/remove roles |
Channel Scopes
| Scope | Description |
|---|---|
channels_read |
Access channel information |
channels_manage |
Modify channels, pin messages |
threads |
Create and manage threads |
Server Scopes
| Scope | Description |
|---|---|
guild_read |
Access guild settings and info |
guild_manage |
Modify guild settings |
Moderation Scopes
| Scope | Description |
|---|---|
ban |
Ban members from the guild |
kick |
Kick members from the guild |
timeout |
Timeout members (communication disabled) |
modlog |
Read and write to moderation log |
Economy Scopes
| Scope | Description |
|---|---|
economy_read |
Read points/leaderboard data |
economy_manage |
Add/remove/transfer points |
Data & Config Scopes
| Scope | Description |
|---|---|
config |
Read Skynet configuration |
storage |
Use persistent extension storage |
Network Scopes
| Scope | Description |
|---|---|
http_request |
Make external HTTP requests |
Advanced Scopes
| Scope | Description |
|---|---|
webhooks |
Create and use webhooks |
Best Practices
1. Error Handling
Always wrap potentially failing code in try-catch:
try {
const member = require("member");
// Use member...
} catch (err) {
console.log("Error:", err.message);
message.reply("Something went wrong!");
}
2. Check Before Acting
Verify conditions before performing actions:
const member = require("member");
if (!member.manageable) {
message.reply("I cannot manage this member!");
return;
}
// Safe to proceed...
3. Use Appropriate Scopes
Only request scopes your extension actually needs. Users are more likely to install extensions with minimal permissions.
4. Respect Rate Limits
Don't spam messages or actions. The sandbox has built-in limits but be considerate.
5. Handle Missing Data
Always check if data exists before using it:
const member = require("member");
const nickname = member.nickname || member.user.username;
const joinDate = member.joinedTimestamp
? new Date(member.joinedTimestamp).toLocaleDateString()
: "Unknown";
6. Use Storage Wisely
Extension storage is limited to 25KB. Store only essential data:
// Good: Store minimal data
extension.storage.write("scores", { user1: 100, user2: 50 });
// Bad: Store large objects
extension.storage.write("history", hugeArrayOfMessages); // May fail!
Examples
Simple Greeting Command
const message = require("message");
const utils = require("utils");
const greetings = [
"Hello there! 👋",
"Hey! How's it going?",
"Greetings, traveler!",
"Hi! Nice to see you!"
];
const greeting = utils.random.pick(greetings);
message.reply(greeting);
User Info Command
const message = require("message");
const member = require("member");
const embed = require("embed");
const utils = require("utils");
const targetUser = member.user;
const joinDate = utils.time.discord(member.joinedTimestamp, "D");
const accountAge = utils.time.relative(targetUser.createdTimestamp);
const infoEmbed = embed.create({
title: `${targetUser.displayName}'s Info`,
color: embed.colors.BLUE,
thumbnail: { url: member.avatarURL },
fields: [
{ name: "Username", value: targetUser.tag, inline: true },
{ name: "ID", value: targetUser.id, inline: true },
{ name: "Joined Server", value: joinDate, inline: false },
{ name: "Account Age", value: accountAge, inline: true },
{ name: "Roles", value: `${member.roles.length} roles`, inline: true },
{ name: "Is Bot", value: targetUser.bot ? "Yes" : "No", inline: true }
],
footer: { text: `Requested by ${message.author.tag}` }
});
message.reply({ embeds: [infoEmbed] });
Dice Rolling Command
const command = require("command");
const message = require("message");
const utils = require("utils");
const embed = require("embed");
const notation = command.suffix.trim() || "1d6";
const result = utils.random.dice(notation);
if (result.rolls.length === 0) {
message.reply("Invalid dice notation! Use format like \`2d6\` or \`1d20+5\`");
} else {
const rollEmbed = embed.create({
title: "🎲 Dice Roll",
color: embed.colors.GOLD,
fields: [
{ name: "Notation", value: `\`${notation}\``, inline: true },
{ name: "Rolls", value: result.rolls.join(", "), inline: true },
{ name: "Total", value: `**${result.total}**`, inline: true }
]
});
message.reply({ embeds: [rollEmbed] });
}
Welcome Message Event
// Event type: guildMemberAdd
const event = require("event");
const guild = require("guild");
const utils = require("utils");
const embed = require("embed");
const member = event.member;
const welcomeEmbed = embed.create({
title: "Welcome! 🎉",
description: `Welcome to **${guild.name}**, ${utils.discord.userMention(member.id)}!`,
color: embed.colors.GREEN,
thumbnail: { url: member.avatarURL },
fields: [
{ name: "Member #", value: `${guild.memberCount}`, inline: true },
{ name: "Account Created", value: utils.time.relative(member.user.createdTimestamp), inline: true }
],
footer: { text: "Enjoy your stay!" },
timestamp: new Date().toISOString()
});
// Note: You'll need to send this to a specific channel configured in your extension
Leaderboard with Storage
const command = require("command");
const message = require("message");
const extension = require("extension");
const utils = require("utils");
const embed = require("embed");
// Get or initialize scores
let scores = extension.storage.get("scores") || {};
const userId = message.author.id;
// Add point for participation
scores[userId] = (scores[userId] || 0) + 1;
await extension.storage.write("scores", scores);
// Build leaderboard
const sorted = Object.entries(scores)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
const leaderboard = sorted
.map((entry, i) => `${utils.format.ordinal(i + 1)} - ${utils.discord.userMention(entry[0])}: **${entry[1]}** points`)
.join("\n");
const lbEmbed = embed.create({
title: "🏆 Leaderboard",
description: leaderboard || "No scores yet!",
color: embed.colors.GOLD,
footer: { text: `Your score: ${scores[userId]} points` }
});
message.reply({ embeds: [lbEmbed] });
Slash Command Example
// Type: slash
// Name: greet
// Description: Greet someone with a custom message
// Options: [{"name":"user","description":"User to greet","type":"user","required":true},{"name":"message","description":"Custom message","type":"string","required":false}]
const interaction = require("interaction");
const embed = require("embed");
const targetUser = interaction.options.user;
const customMsg = interaction.options.message || "Hello!";
if (!targetUser) {
await interaction.reply({ content: "Please mention a user!", ephemeral: true });
} else {
const greetEmbed = embed.create({
title: "👋 Greeting!",
description: `<@${targetUser.id}>, ${customMsg}`,
color: embed.colors.GREEN,
footer: { text: `From ${interaction.user.username}` }
});
await interaction.reply({ embeds: [greetEmbed] });
}
HTTP API Request Example
// Requires: http_request scope + allowlist_only or higher network capability
const http = require("http");
const message = require("message");
const command = require("command");
const embed = require("embed");
const query = command.suffix.trim();
if (!query) {
message.reply("Usage: anime <search query>");
} else {
const res = await http.request({
url: "https://api.jikan.moe/v4/anime?q=" + encodeURIComponent(query) + "&limit=1",
method: "GET",
responseType: "json",
timeoutMs: 8000,
});
if (!res.success || !res.json?.data?.length) {
message.reply("No results found for: **" + query + "**");
} else {
const anime = res.json.data[0];
message.reply({
embeds: [embed.create({
title: anime.title,
url: anime.url,
description: (anime.synopsis || "").slice(0, 300) + "...",
thumbnail: { url: anime.images?.jpg?.image_url },
fields: [
{ name: "Score", value: String(anime.score || "N/A"), inline: true },
{ name: "Episodes", value: String(anime.episodes || "N/A"), inline: true },
],
color: embed.colors.BLUE,
footer: { text: "Powered by Jikan/MyAnimeList" },
})],
});
}
}
Limitations
- Execution Timeout: Extensions have a maximum execution time (100-10000ms, configurable)
- Memory Limit: Isolated VM has 128MB memory limit
- Storage Limit: 25KB per extension per server
- HTTP Requests: Available via
httpmodule with network capability (Tier 2 for full access) - Legacy Modules:
fetch,rss, andxmlparsermodules are not available - No File System Access: Extensions cannot read/write files
- Rate Limits: Discord's rate limits apply; HTTP requests limited to 30/minute per extension
Troubleshooting
"MISSING_SCOPES" Error
Your extension is trying to access a module that requires a scope you haven't declared. Add the required scope to your extension settings.
"UNKNOWN_MODULE" Error
You're trying to require a module that doesn't exist or isn't available for your extension type (e.g., command module in an event extension).
Extension Not Running
- Check that the extension is enabled for the server
- Verify the trigger (command key, keyword, event type) is correct
- Check the extension status in the dashboard for error messages
Storage Issues
- Ensure you're not exceeding the 25KB limit
- Use
JSON.stringify()for complex objects - Clear old data with
extension.storage.clear()if needed
Last Updated: December 2024
Extension API Version: 2.1
Builder Features
The Extension Builder at /extensions/builder provides:
- Code Editor: Monaco-based editor with syntax highlighting
- Upload/Download: Import/export
.js,.skyext, or.txtfiles - Format Code: Auto-format with Ctrl+Shift+F
- Shortcuts: Ctrl+S (Save), Ctrl+/ (Comment), Ctrl+D (Duplicate), Ctrl+F (Find)
- Tags: Categorize extensions (Moderation, Utility, Fun, Economy, etc.)
- Premium Settings: Set pricing for marketplace extensions
- Import/Export Packages: Share extensions via
.skypkgfiles