Sunar

Slash

Slash commands are one of the primary ways users interact with bots. They provide a structured way for users to issue commands directly within the chat interface.

Usage

import { Slash, execute } from 'sunar';

const slash = new Slash({
	name: 'example',
	description: 'example description',
});

execute(slash, (interaction) => {
	// handle execution
});

export { slash };

Implementation

The following example demonstrates how to implement a Slash command using Sunar:

import { Slash, execute } from 'sunar';
import { ApplicationCommandOptionType } from 'discord.js';

const slash = new Slash({
	name: 'avatar',
	description: 'Show user avatar',
	options: [
		{
			name: 'target',
			description: 'Target user',
			type: ApplicationCommandOptionType.User,
		},
	],
});

execute(slash, (interaction) => {
	const user = interaction.options.getUser('target') ?? interaction.user;

	const avatarURL = user.displayAvatarURL({
		size: 1024,
		forceStatic: false,
	});

	interaction.reply({
		content: `Avatar of user **${interaction.user.username}**`,
		files: [avatarURL],
	});
});

export { slash };

Subcommands

Sunar supports both simple subcommands and subcommand groups. This helps organize complex commands into a more structured hierarchy.

Simple Subcommands

First, create a parent command:

src/commands/music.js
import { SlashParent } from 'sunar';

const music = new SlashParent({
	name: 'music',
	description: 'Music player commands'
});

export { music };

A SlashParent command acts as a container and doesn't handle execution itself - it only defines the structure and available groups.

Then, create subcommands:

src/commands/music/play.js
import { SlashSubcommand, execute } from 'sunar';

const play = new SlashSubcommand('music', {
	name: 'play',
	description: 'Play a song',
	options: [
		{
			name: 'query',
			description: 'Song name or URL',
			type: ApplicationCommandOptionType.String,
			required: true
		}
	]
});

execute(play, async (interaction) => {
	const query = interaction.options.getString('query', true);
	await interaction.reply(`Playing: ${query}`);
});

export { play };

Each SlashSubcommand must specify its parent command (first argument) and can optionally belong to a group. Subcommands contain the actual execution logic and can have their own options.

Subcommand Groups

For more complex commands, you can use subcommand groups:

src/commands/music.js
import { SlashParent } from 'sunar';

const music = new SlashParent({
	name: 'music',
	description: 'Music player commands',
	groups: [
		{
			name: 'playlist',
			description: 'Playlist management commands'
		}
	]
});

export { music };

Groups allow you to organize related subcommands into categories, creating a three-level command structure.

Create grouped subcommands:

src/commands/music/playlist-add.js
import { SlashSubcommand, execute } from 'sunar';

const add = new SlashSubcommand('music', 'playlist', {
	name: 'add',
	description: 'Add a song to a playlist',
	options: [
		{
			name: 'name',
			description: 'Playlist name',
			type: ApplicationCommandOptionType.String,
			required: true
		},
		{
			name: 'song',
			description: 'Song to add',
			type: ApplicationCommandOptionType.String,
			required: true
		}
	]
});

execute(add, async (interaction) => {
	const playlist = interaction.options.getString('name', true);
	const song = interaction.options.getString('song', true);
	await interaction.reply(`Added "${song}" to playlist: ${playlist}`);
});

export { add };

Remember to export both the parent command and all subcommands, and ensure they match your glob pattern.

The commands will be available as:

  • /music play <query>
  • /music playlist add <name> <song>

This structure helps keep your commands organized and provides a better user experience for complex bots.

Using Protectors

Protectors act as middleware for your commands, allowing you to add validation, permissions, or any other checks. When applied to a SlashParent, all its subcommands inherit the protection.

Basic Protection

src/protectors/admin-only.js
import { Protector, execute } from 'sunar';

const adminOnly = new Protector({
	commands: ['slash'] // This protector works with slash commands
});

execute(adminOnly, (interaction, next) => {
	if (!interaction.memberPermissions?.has('Administrator')) {
		return interaction.reply({
			content: 'This command is for administrators only!',
			ephemeral: true
		});
	}
	return next(); // Continue execution if user is admin
});

export { adminOnly };

Protecting Parent Commands

When you protect a parent command, all its subcommands automatically inherit that protection:

src/commands/settings.js
import { SlashParent, protect } from 'sunar';
import { adminOnly } from '../protectors/admin-only.js';

const settings = new SlashParent({
	name: 'settings',
	description: 'Server settings commands'
});

// All subcommands of 'settings' will require admin permissions
protect(settings, [adminOnly]);

export { settings };
src/commands/settings/prefix.js
import { SlashSubcommand, execute } from 'sunar';

const prefix = new SlashSubcommand('settings', {
	name: 'prefix',
	description: 'Change server prefix',
	options: [
		{
			name: 'new-prefix',
			description: 'The new prefix to use',
			type: ApplicationCommandOptionType.String,
			required: true
		}
	]
});

// This command automatically inherits the adminOnly protection
execute(prefix, async (interaction) => {
	const newPrefix = interaction.options.getString('new-prefix', true);
	await interaction.reply(`Prefix updated to: ${newPrefix}`);
});

export { prefix };

You can also apply additional protectors directly to subcommands. They will be executed after the parent's protectors.

Reference

SlashConfig

Prop

Type

SlashParentConfig

Prop

Type

SlashParentCommandDataInput

Prop

Type

SlashSubcommandConfig

Prop

Type

How is this guide?

Last updated on