{"id":23812890,"url":"https://github.com/mkvlrn/template-discordbot","last_synced_at":"2026-04-15T22:34:40.060Z","repository":{"id":270522512,"uuid":"880665389","full_name":"mkvlrn/template-discordbot","owner":"mkvlrn","description":"An esm nodejs template for discord bots using discord.js","archived":false,"fork":false,"pushed_at":"2026-04-14T07:38:50.000Z","size":889,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-14T09:30:02.420Z","etag":null,"topics":["bot","discord","esm","node","template"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mkvlrn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-10-30T05:58:51.000Z","updated_at":"2026-04-14T07:38:52.000Z","dependencies_parsed_at":"2026-03-27T17:02:40.884Z","dependency_job_id":null,"html_url":"https://github.com/mkvlrn/template-discordbot","commit_stats":{"total_commits":7,"total_committers":1,"mean_commits":7.0,"dds":0.0,"last_synced_commit":"131e20efc5df093c98bf8eb6ce981eb41d6d624f"},"previous_names":["mkvlrn/template-discordbot"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/mkvlrn/template-discordbot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkvlrn%2Ftemplate-discordbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkvlrn%2Ftemplate-discordbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkvlrn%2Ftemplate-discordbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkvlrn%2Ftemplate-discordbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mkvlrn","download_url":"https://codeload.github.com/mkvlrn/template-discordbot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mkvlrn%2Ftemplate-discordbot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31863493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","discord","esm","node","template"],"created_at":"2025-01-02T02:35:45.729Z","updated_at":"2026-04-15T22:34:40.054Z","avatar_url":"https://github.com/mkvlrn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# template-discordbot\n\n![build](https://img.shields.io/github/actions/workflow/status/mkvlrn/template-discordbot/checks.yml?branch=main\u0026style=flat\u0026logo=github)\n[![template](https://img.shields.io/badge/template-use_this_template-2ea44f?style=flat\u0026logo=github)](https://github.com/mkvlrn/template-discordbot/generate)\n[![mise](https://mise-versions.jdx.dev/badge.svg)](https://mise.jdx.dev)\n![license](https://img.shields.io/github/license/mkvlrn/template-discordbot?style=flat)\n\nA sane, opinionated template for discord bots written in typescript using the [discord.js](https://discord.js.org/#/) library. It doesn't rely on transpilation - typescript is ran directly by node.\n\n\u003e [!NOTE]\n\u003e This template provides a [mise](https://mise.jdx.dev) configuration to make it easy to keep node and pnpm versions in sync.\n\nUses, among other tools/packages:\n\n- [pnpm](https://github.com/pnpm/pnpm) as package manager for node\n- [biome](https://github.com/biomejs/biome) for code linting and formatting\n- [lefthook](https://github.com/evilmartians/lefthook) for git hooks\n- [commitlint](https://github.com/conventional-changelog/commitlint) for commit message linting\n- [vitest](https://github.com/vitest-dev/vitest) for testingypescript\n- [envalid](https://github.com/af/envalid) for env validation and parsing\n- [@mkvlrn/result](https://github.com/mkvlrn/tools/blob/main/packages/result/README.md) for error handling\n\n## requirements and dependencies\n\nIf you use [mise](https://mise.jdx.dev) and run `mise install` in the project root, you'll have the correct node and pnpm versions installed.\n\nThis is _by far_ the easiest way to keep your environment consistent across different machines and team members, no matter the frequency of version updates. I'm not affiliated with mise but I wholeheartedly recommend it, so check it here: https://mise.jdx.dev.\n\nIf not using mise, make sure you have:\n\n- node 24+ installed (v24.14.1 used)\n- pnpm 10+ installed (v10.33.0 used)\n\nThen, install dependencies with:\n\n```bash\npnpm install\n```\n\n\u003e [!NOTE]\n\u003e Git hooks are in place to make sure both the tooling managed by mise and the project dependencies are synced with each checkout and merge.\n\n## subpath imports\n\nSubpath imports (`#/`) are used instead of relative paths, mapped in both `package.json` and `tsconfig.json`.\n\n**Example**:\n\n```ts\nimport { add } from \"#/math/basic\"; // this points to ./src/math/basic.ts\n```\n\n## secrets\n\nAn untracked, local `.env` file can be used during development, and you can load it up with the `--env-file` flag for node.\n\nCreate a project there setting the following secrets:\n\n- `DISCORD_CLIENT_ID`\n- `DISCORD_CLIENT_TOKEN`\n- `DEV_SERVER`\n- `LOG_LEVEL`\n\nThe `dev`, `start`, `register`, and `unregister` npm scripts use the `.env` file by default, although you should probably use something else for secret management, such as [doppler](https://www.doppler.com/) or others.\n\nThis is just a low friction setup.\n\n## running\n\n### `pnpm dev`\n\nRuns the project in watch mode.\n\n### `pnpm start`\n\nRuns the built project.\n\n### `pnpm test`\n\nRuns tests with vitest.\n\n### `pnpm biome-fix`\n\nRuns biome in fix mode to lint and format the project.\n\n### `pnpm typecheck`\n\nRuns type checking using tsc.\n\n### `pnpm register [--dev]`\n\nRegisters slash commands globally, or to the dev server if `--dev` flag is provided\n\n### `pnpm unregister [--dev]`\n\nUnregisters slash commands globally, or from the dev server if `--dev` flag is provided\n\n## adding or removing commands\n\nCommands are **auto-loaded** from `./src/commands/`. Just create a file and call `createBotCommand`.\n\n**Note:** Discord requires command names to be lowercase. Use kebab-case for multi-word commands (e.g., `my-command`).\n\n1. Create a new file in `./src/commands/` (e.g., `my-command.ts`)\n2. Call `createBotCommand` with your command definition:\n\n```ts\nimport { SlashCommandBuilder } from \"discord.js\";\nimport { createBotCommand } from \"#/modules/commands\";\n\ncreateBotCommand({\n  data: new SlashCommandBuilder().setName(\"my-command\").setDescription(\"Does something\"),\n  async execute(interaction) {\n    await interaction.reply(\"Hello!\");\n  },\n});\n```\n\n3. Run `pnpm register` to register commands globally (or `pnpm register --dev` for your dev server)\n4. Restart your bot\n\n### handling follow-up interactions\n\nFor commands with buttons, select menus, or modals, add a `followUp` handler. Use a prefix in `customId` to route interactions back to your command:\n\n```ts\nimport { ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from \"discord.js\";\nimport { createBotCommand, type FollowUpInteraction } from \"#/modules/commands\";\n\ncreateBotCommand({\n  data: new SlashCommandBuilder().setName(\"counter\").setDescription(\"A simple counter\"),\n  async execute(interaction) {\n    const button = new ButtonBuilder()\n      .setCustomId(\"counter:increment\") // prefix must match command name\n      .setLabel(\"Click me\")\n      .setStyle(ButtonStyle.Primary);\n    const row = new ActionRowBuilder\u003cButtonBuilder\u003e().addComponents(button);\n    await interaction.reply({ content: \"Count: 0\", components: [row] });\n  },\n  async followUp(interaction: FollowUpInteraction) {\n    if (interaction.isButton()) {\n      await interaction.reply(\"Button clicked!\");\n    }\n  },\n});\n```\n\n## removing commands\n\n1. Run `pnpm unregister` (or `pnpm unregister --dev`) to clean the slate\n2. Delete the file from `./src/commands/`\n3. Run `pnpm register` (or `pnpm register --dev`) to register commands again\n4. Restart your bot\n\n## example commands\n\nThe template includes several examples demonstrating different patterns:\n\n| Command      | Description                                          |\n| ------------ | ---------------------------------------------------- |\n| `ping`       | Simple reply                                         |\n| `roll`       | Slash command with options (dropdown selection)      |\n| `roll-plus`  | String input parsing with image generation           |\n| `roll-panel` | Interactive buttons and select menus with `followUp` |\n\n## architecture\n\n```bash\nsrc/\n├── commands/ # Drop command files here — auto-loaded\n│ ├── ping.ts\n│ ├── roll.ts\n│ ├── roll-panel.ts\n│ └── roll-plus.ts\n├── modules/\n│ ├── bot.ts # Client setup, login, graceful shutdown\n│ ├── commands.ts # createBotCommand + auto-loader\n│ ├── interaction.ts # Dispatches interactions to commands\n│ └── logger.ts # Pino logger config\n├── utils/ # Shared utilities (dice rolling, image gen)\n└── main.ts # Entry point\n```\n\n## environment variables\n\nManaged by [envalid](https://github.com/af/envalid) with full type safety:\n\n| Variable               | Description                                                           |\n| ---------------------- | --------------------------------------------------------------------- |\n| `DISCORD_CLIENT_ID`    | Your Discord application's client ID                                  |\n| `DISCORD_CLIENT_TOKEN` | Your Discord bot token                                                |\n| `LOG_LEVEL`            | Logging level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`)    |\n| `DEV_SERVER`.          | Your Discord test server (target of register/unregister with `--dev`) |\n\nSee `./src/env.ts` for the schema definition.\n\n## ci\n\nThis repository uses GitHub Actions for CI. The workflow is defined in `.github/workflows/checks.yml`.\n\nIt automates:\n\n- **Linting \u0026 Formatting**: Running Biome.\n- **Type Checking**: Running TypeScript type checking.\n- **Testing**: Running Vitest with code coverage (generated by Istanbul).\n\n## vscode\n\nYou might want to install the recommended extensions in vscode. Search for **@recommended** in the extensions tab, they'll show up as _\"workspace recommendations\"_.\n\nIf you have been using eslint and prettier and their extensions, you might want to disable eslint entirely and keep prettier as the formatter only for certain types of files.\n\nThis is done by the `.vscode/settings.json` file.\n\nDebug configuration is also included for running the source directly with node.\n\n## license\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmkvlrn%2Ftemplate-discordbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmkvlrn%2Ftemplate-discordbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmkvlrn%2Ftemplate-discordbot/lists"}