ItsMyStudio

Create Your First Addon

Build one minimal addon that ItsMyBot can actually load: addon class, config, one command, and the correct build loop.

Support

This page builds the smallest useful addon on purpose.

You are not trying to learn every addon folder here. You are trying to prove that you understand the addon flow from source files to a running feature.

What you are building

At the end of this page, you should have:

  • one addon class in src/addons/MyAddon/index.ts
  • one config validator in resources/config.ts
  • one default config file in resources/config.yml
  • one slash command in interactions/pingCommand.ts

Understand the goal first

For a first addon, the important thing is not the command itself.

The real goal is to understand this chain:

  1. define an addon class
  2. load config in load()
  3. add one interaction file
  4. build the project
  5. confirm that ItsMyBot loads the compiled addon from build/addons

If that chain makes sense, the rest of addon development becomes much easier.

Minimal structure

You can scaffold an addon with:

npm run make:addon

For learning, it helps to understand the structure it creates:

index.ts
pingCommand.ts
config.ts
config.yml

You do not need events/, leaderboards/, or any scripting extension folder yet.

Create the addon class

Every addon starts with one default export that extends Addon.

import { Addon, ConfigFile } from '@itsmybot';
import AddonConfig from './resources/config.js';

interface Configs {
  config: ConfigFile;
}

export default class MyAddon extends Addon {
  version = '0.0.1';
  authors = ['YourName'];
  description = 'My first ItsMyBot addon';

  configs = {} as Configs;

  async load() {
    this.configs.config = await this.createConfig('config.yml', AddonConfig);
  }

  async initialize() {}
}

What matters here:

  • index.ts is the addon entry point
  • load() is where you usually load config first
  • initialize() can stay empty in a first addon
  • configs is your typed place to keep loaded config files

Add one config file

Now give the addon one small config file so you understand how resources/ works.

Create resources/config.ts:

import { IsDefined, IsString } from 'class-validator';

export default class AddonConfig {
  @IsDefined()
  @IsString()
  name: string;
}

Create resources/config.yml:

name: "My addon"

And load it in load():

this.configs.config = await this.createConfig('config.yml', AddonConfig);

That line does three important jobs:

  • it looks for configs/MyAddon/config.yml
  • it creates the file from your default resources/config.yml if needed
  • it validates the loaded data with your AddonConfig class

That is why config loading belongs near the start of the addon lifecycle.

Add one slash command

Most first addons should begin with a slash command because it is the easiest thing to trigger on demand.

Create interactions/pingCommand.ts:

import { Command, CommandBuilder, User } from '@itsmybot';
import { ChatInputCommandInteraction } from 'discord.js';
import MyAddon from '..';

export default class PingCommand extends Command<MyAddon> {
  build() {
    return new CommandBuilder()
      .setName('ping')
      .setDescription('Check if the addon is alive');
  }

  async execute(interaction: ChatInputCommandInteraction<'cached'>, user: User) {
    await interaction.reply({ content: 'Pong!' });
  }
}

Why this file works:

  • it lives in interactions/, so it is part of the addon interaction set
  • it exports one default class
  • it extends Command<MyAddon>
  • build() defines the slash command
  • execute() runs when the command is used

For a first addon, that is enough. Do not add buttons, modals, and extra commands yet.

Add translations only if you already need them

Addon translations live in resources/lang/.

en-US.yml

You can read addon translation keys with:

this.lang.getString('path.to.key')

For the first addon, hard-coded text is acceptable. Add translations once the addon flow itself already works.

Add addon-specific dependencies

If your addon needs a third-party package, create a package.json yourself directly in the addon folder.

src/addons/MyAddon/package.json
{
  "dependencies": {
    "dayjs": "^1.11.13"
  }
}

ItsMyBot can then install those dependencies automatically for that addon.

Build before testing

Most first addon mistakes come from editing src/addons/... and then testing an old build/addons/... output.

ItsMyBot loads compiled addons from build/addons/<AddonName>, not from src/addons/<AddonName>.

The normal loop is:

npm run build
npm run start

The build step matters because it:

  • compiles TypeScript into build/
  • copies YAML resources into build/
  • gives the runtime the files it actually reads

If the addon does not seem to update, check the build output first.

Know what to do next

Once this minimal addon works, you are ready for the next layer:

  • open Add Configuration to understand config folders, nested config, and getters properly
  • open Additional Features when you need interactions, events, leaderboards, or scripting-related extensions
  • use _Example in the source repo only after the basic addon flow already makes sense

_Example is a reference, not the best first explanation.

On this page