This is a practical and advanced template for creating Discord applications using TypeScript. It includes some useful features, a basic file structure, example interaction files, and event handling.
Note
This project is under the MIT license. You are not required to credit me, but I would really appreciate it if you do.
- Fully typed with the pain of TypeScript.
- Super simple event and interaction handling. I sacrificed my sanity for this.
- Comes with
readyandinteractionCreateevents along with example interaction files. - Custom interaction functions:
interaction.reply2()andinteraction.update2(). These functions are made for automatically using components v2 along with some more useful features. Click here to learn more. - An advanced but simple way to use custom IDs. Working with args inside components has never been easier.
- Easy error handling, included with funny error messages.
- A useful debugging utility.
/src
├── /classes
│ └── CustomIdArgs.ts
│
├── /events
│ ├── interactionCreate.ts
│ └── ready.ts
│
├── /interactions
│ │
│ ├── /commands
│ │ ├── Launch-Activity.ts
│ │ ├── Message-Test.ts
│ │ ├── slash-test.ts
│ │ └── User-Test.ts
│ │
│ ├── /components
│ │ ├── component-test.ts
│ │ └── modal-test-button.ts
│ │
│ └── /components
│ └── modal-test.ts
│
├── /types
│ ├── enums.ts
│ └── index.ts
│
├── /utils
│ ├── buildCustomId.ts
│ ├── commands.ts
│ ├── components.ts
│ ├── debug.ts
│ ├── events.ts
│ └── modals.ts
│
└── index.ts
Note
This template uses pnpm as the package manager. It is recommended to use pnpm for installing dependencies and running scripts.
Run npm install -g pnpm to install pnpm globally.
- Create a new repository using this template or clone it to your local machine.
git clone https://github.com/Tolga1452/ts-discord-app-template.git my-discord-app- Set up the development environment
cd my-discord-app
pnpm devTip
You can use the script pnpm dev:clean to remove any example interaction files.
- Rename the
.env.examplefile to.envand update the environment variables as needed.
DEBUG: Set to"true"to enable debug logging.DISCORD_REGISTER_COMMANDS: Set to"true"to automatically register commands when your application starts.DISCORD_TOKEN: Your bot token (required).
cp .env.example .env- Run your application
pnpm start- After deploying your code, set up the production environment and run it.
pnpm prod
pnpm startThis project uses a custom way to use emojis in the interaction responses. You can edit the types/enums.ts file to modify emojis.
These emojis can also be used in the interaction.reply2() and interaction.update2() custom interaction functions, by providing the emoji field in the options object.
For interaction responses, you can use the interaction.reply2() and interaction.update2() custom functions that are implemented directly into the interactions.
The both functions automatically use components v2.
Replaces the interaction.reply(), interaction.editReply(), and interaction.followUp() functions.
Behavior before the response:
- Adds the
IS_COMPONENTS_V2flag to the response. - If
optionsis a string, automatically overwrites the wholecomponentsfield with a single Text Display component with the content ofoptions. - If the
contentfield is provided inoptions, does the same as step 2 for thecontentfield. - If the
emojifield (a custom field) is provided inoptions, the emoji will automatically be added to the first Text Display component of the response.
Behavior for response:
- If
followUpistrue, it will useinteraction.followUp(). - If the interaction is already deferred or replied, it will use
interaction.editReply(). - If none of the above conditions are met, it will use
interaction.reply().
Example Usage:
interaction.reply2('Hello World!');
interaction.reply2({
emoji: Emoji.Happy,
content: 'Hello World!'
});Replaces the interaction.update() function.
Behavior before the response:
- Adds the
IS_COMPONENTS_V2flag to the response. - If
optionsis a string, automatically overwrites the wholecomponentsfield with a single Text Display component with the content ofoptions. - If the
contentfield is provided inoptions, does the same as step 2 for thecontentfield. - If the
emojifield (a custom field) is provided inoptions, the emoji will automatically be added to the first Text Display component of the response.
Behavior for response:
It will use interaction.update().
Example Usage:
interaction.update2('You clicked the button!');
interaction.update2({
emoji: Emoji.Wave,
content: 'You clicked the button!'
});For components and modals, you need to set a custom ID. And in some cases you may need to add some additional metadata to custom IDs.
The buildCustomId() utility is an useful way to set custom IDs with additional metadata. It basically converts your input into a string that the interaction handler can easily parse. The function automatically converts all arguments to strings.
For example buildCustomId('my-button', user.id, channel.id) will produce a string like my-button:1234567890,0987654321.
Example Usage:
interaction.reply({
components: [
new ActionRowBuilder<ButtonBuilder>()
.setComponents(
new ButtonBuilder()
.setCustomId(buildCustomId('my-button', user.id, channel.id))
.setLabel('Click Me!')
.setStyle(ButtonStyle.Primary)
)
]
});Component and modal files use a special way to handle custom IDs. You can give keys for the each argument you added to the custom ID by adding them to the args array. TypeScript will automatically infer the types of the arguments based on their positions in the array.
Then all you have to do is use the args class provided by the interaction handler, like args.get('userId').
Example Usage:
const args = ['userId', 'channelId'] as const;
export default {
customId: 'my-button',
args,
async execute(interaction, args) {
await interaction.deferUpdate();
const userId = args.get('userId');
const channelId = args.get('channelId');
if (!userId || !channelId) throw new Error('Missing required arguments');
await interaction.reply2(`Command used by <@${userId}>, in channel <#${channelId}>`);
}
} satisfies Component<ComponentType.Button, typeof args>;This template uses a simple way to handle errors in interactions. If something goes wrong, just throw an error. It will be caught and logged to the console, and the interaction will respond with a default error message.
Note
In order to handle errors from async functions, you always need to await them in the interaction file. Otherwise the interaction handler will not be able to catch the error.
Example Usage:
const channel = await interaction.client.channels.fetch(channelId);
if (!channel) throw new Error(`Could not find channel with ID ${channelId}`);You can use the debug(...args: any[]) utility function to log debug information.
This function works the same as console.debug(), the only difference is that it only logs when the DEBUG environment variable is set to "true".
If you want to update your dependencies to their latest versions (by ignoring the version ranges specified in package.json), you can use this command.
Since this script might update your dependencies to major versions, the updates may include breaking changes.
This script allows you to quickly manage your app's command files.
pnpm command create slash my-command
pnpm command remove my-command
pnpm command clearCreates a new command file.
Usage: pnpm command create [<type>] [<name>] [<options>]
Arguments
type(--type): The type of command to create (slash,user,message,activity). Default isslash.name(--name): The name of the command. Default isnew-command.--help,-h: Show help information.--autocomplete,-ac: For slash commands, implements autocomplete functionality.--guild <guild-id>,-g <guild-id>: The ID of the guild to restrict the command to.
Removes an existing command file.
Usage: pnpm command remove <name>
Arguments
name(--name): The name of the command to remove. Required.
Removes all existing command files.
Usage: pnpm command clear
This script allows you to quickly manage your app's component files.
pnpm component create my-component
pnpm component remove my-component
pnpm component clearCreates a new component file.
Usage: pnpm component create [<name>]
Arguments
name(--name): The custom ID of the component to create. Default isnew-component.
Removes an existing component file.
Usage: pnpm component remove <name>
Arguments
name(--name): The custom ID of the component to remove. Required.
Removes all existing component files.
Usage: pnpm component clear
This script allows you to quickly manage your app's modal files.
pnpm modal create my-modal
pnpm modal remove my-modal
pnpm modal clearCreates a new modal file.
Usage: pnpm modal create [<name>]
Arguments
name(--name): The custom ID of the modal to create. Default isnew-modal.
Removes an existing modal file.
Usage: pnpm modal remove <name>
Arguments
name(--name): The custom ID of the modal to remove. Required.
Removes all existing modal files.
Usage: pnpm modal clear
This script allows you to quickly manage your app's event files.
pnpm event create interactionCreate
pnpm event remove interactionCreate
pnpm event clearCreates a new event file.
Usage: pnpm event create [<name>]
Arguments
name(--name): The name of the event to create. Default isnewEvent.
Removes an existing event file.
Usage: pnpm event remove <name>
Arguments
name(--name): The name of the event to remove. Required.
Removes all existing event files.
Usage: pnpm event clear
