Production-grade Discord bot framework for building scalable, maintainable bots with clean, type-safe code.
Make EasyCord easy. Ship Discord bots with less ceremony, safer defaults, and plugin patterns that stay understandable as your server grows.
Slash commands, context menus, modal forms, components with dynamic routing, plugins with versioned config schemas and dependency management, per-guild storage, multi-language i18n, conversation memory, optional AI orchestration, middleware pipeline, lifecycle hooks, and task scheduling—all with zero boilerplate.
30 built-in plugins including levels, economy, moderation, starboard, polls, translation, AI moderation, and more. 1871 tests, atomic database operations, concurrent plugin safety, proper error isolation, and comprehensive type hints. Deploy with confidence.
- No boilerplate. Decorators do the work. Define commands in two lines, not thirty.
- Type-safe. Full Pyright support. Catch bugs at dev time, not runtime.
- Plugin-native. Modular, testable, reusable. Versioned config schemas with automatic migration. Build plugins in minutes, not hours.
- Optional AI. Includes conversation memory and multi-provider LLM orchestration (9 providers). Use it or ignore it.
- Tested. 1871 tests covering concurrency, crashes, race conditions, and edge cases.
- Async-first. Proper lock safety, atomic database operations, isolated error handling. Won't silently corrupt state.
pip install "https://github.com/rolling-codes/EasyCord/releases/download/v5.61.1/easycord-5.61.1-py3-none-any.whl"Or scaffold a full project:
easycord new my-bot --template community
cd my-bot && pip install -e ".[dev]"Requirements: Python 3.10+. The only runtime dependency is discord.py>=2.7.1,<3.
import os
from easycord import Bot, SQLiteDatabase
from easycord.plugins import LevelsPlugin, ModerationPlugin, TagsPlugin
bot = Bot(database=SQLiteDatabase("bot.db"))
@bot.slash(description="Server info")
async def info(ctx):
await ctx.respond(f"{ctx.guild.name} — {ctx.guild.member_count} members")
bot.add_plugins(LevelsPlugin(), ModerationPlugin(), TagsPlugin())
bot.run(os.environ["DISCORD_TOKEN"])That's /rank, /leaderboard, /kick, /ban, /timeout, /warn, /tag, and /info — by assembling library plugins and one small command function.
from easycord.testing import invoke
async def test_info():
ctx = await invoke(bot, "info")
assert "members" in ctx.last_responseNo bot token, no Discord connection. See Testing Commands.
| I want to... | Start with... |
|---|---|
| Add moderation, levels, tags, polls, or tickets | Import a built-in plugin object |
| Add one server-specific command | Use @bot.slash on a function |
| Group related commands with shared state | Create a Plugin class |
| Package a reusable feature for other bots | Use easycord plugin create |
For the current release notes and verified fix list, see EasyCord v5.61.1.
A full-featured community server bot assembled from ready-made plugin objects. Start with shipped features, then add your own command functions only where your server needs custom behavior.
import os
import discord
from easycord import Bot, SQLiteDatabase
from easycord.plugins import EconomyPlugin, ModerationPlugin, ReminderPlugin, StarboardPlugin
bot = Bot(
load_builtin_plugins=True, # WelcomePlugin, TagsPlugin, PollsPlugin, LevelsPlugin
database=SQLiteDatabase("bot.db"),
)
@bot.slash(description="Show server overview", guild_only=True, require_admin=True)
async def dashboard(ctx):
guild = ctx.guild
embed = discord.Embed(title=f"Admin — {guild.name}", color=discord.Color.gold())
embed.add_field(name="Members", value=str(guild.member_count), inline=True)
embed.add_field(name="Channels", value=str(len(guild.channels)), inline=True)
embed.add_field(name="Roles", value=str(len(guild.roles)), inline=True)
await ctx.respond(embed=embed, ephemeral=True)
bot.add_plugins(
ModerationPlugin(),
EconomyPlugin(),
ReminderPlugin(),
StarboardPlugin(),
)
bot.run(os.environ["DISCORD_TOKEN"])What you get: /rank, /leaderboard, /tag, /poll, /welcome, /kick, /ban, /timeout, /warn, /balance, /daily, /transfer, /remind, plus starboard pinning and a custom /dashboard — all production-ready with persistent SQLite storage.
Add a conversational AI command by creating a provider object and passing it to the shipped AIPlugin.
import os
from easycord import Bot
from easycord.plugins import AIPlugin, AnthropicProvider
bot = Bot()
bot.add_plugin(
AIPlugin(
provider=AnthropicProvider(api_key=os.environ["ANTHROPIC_API_KEY"]),
rate_limit=3,
rate_window=60.0,
)
)
bot.run(os.environ["DISCORD_TOKEN"])What you get: /ask backed by Claude without writing a plugin class. Swap in another provider object when you want OpenAI, Gemini, Groq, Ollama, or a custom provider. See AI Features for conversation memory, tool safety levels, and all 9 providers.
A production-ready bot can be mostly wiring: choose storage, attach middleware, and add shipped plugin objects. Move to custom plugin classes only when your feature needs its own lifecycle or persistent state.
import os
from easycord import Bot, SQLiteDatabase
from easycord.middleware import catch_errors, rate_limit, log_middleware
from easycord.plugins import LevelsPlugin, ModerationPlugin, TicketsPlugin
bot = Bot(database=SQLiteDatabase("bot.db"))
bot.use(catch_errors())
bot.use(rate_limit(limit=5, window=10.0))
bot.use(log_middleware())
bot.add_plugins(LevelsPlugin(), ModerationPlugin(), TicketsPlugin())
bot.run(os.environ["DISCORD_TOKEN"])What you get: SQLite-backed state, rate limiting, structured error replies, moderation commands, leveling, and support tickets without owning a framework subclass. When you do need custom state, add ConfigSchema and plugin config helpers from Plugin Config Schemas.
Load the starter set with one call, or cherry-pick:
bot = Bot(load_builtin_plugins=True) # loads Welcome, Tags, Polls, Levels
# or
from easycord.plugins import ModerationPlugin, EconomyPlugin, TicketsPlugin
bot.add_plugins(ModerationPlugin(), EconomyPlugin(), TicketsPlugin())| Plugin | Key Commands | Description |
|---|---|---|
LevelsPlugin |
/rank, /leaderboard, /set_xp_multiplier |
XP, leveling, role rewards, leaderboard caching |
ModerationPlugin |
/kick, /ban, /unban, /timeout, /warn |
Member moderation with reason logging |
EconomyPlugin |
/balance, /daily, /transfer |
Virtual currency with per-guild balances |
TagsPlugin |
/tag get/set/delete/list |
Per-guild text snippets |
PollsPlugin |
/poll |
Reaction-based emoji polls |
WelcomePlugin |
/welcome |
Configurable join messages |
StarboardPlugin |
(reaction-based) | Pins messages reaching a ⭐ threshold |
TicketsPlugin |
/ticket_open, /ticket_close |
Private support ticket channels |
SuggestionsPlugin |
/suggest |
Community suggestions with voting |
GiveawayPlugin |
/giveaway, /giveaway_end, /giveaway_reroll |
Timed giveaways with role requirements |
ReminderPlugin |
/remind me in X do Y |
User-scheduled reminders |
BirthdayPlugin |
/birthday_set, /birthday_unset, /birthday_list |
Per-guild birthday tracking and announcements |
ReputationPlugin |
/rep, /rep_check, /rep_top |
Member reputation system |
TranslatePlugin |
/translate |
Google Translate, no API key required |
VerificationPlugin |
/verify |
CAPTCHA-style member gate |
WordFilterPlugin |
/filter_add, /filter_remove, /filter_list |
Auto-delete messages matching patterns |
AutoResponderPlugin |
(event-based) | Keyword-triggered auto-replies |
AutoRolePlugin |
/autorole_add, /autorole_remove, /autorole_list |
Assign roles automatically on join |
ReactionRolesPlugin |
(reaction-based) | Role assignment via emoji reactions |
ScheduledAnnouncementsPlugin |
/announcement_add, /announcement_list, /announcement_remove |
Recurring channel announcements |
ServerStatsPlugin |
(dynamic channels) | Live member/channel count display channels |
MemberLoggingPlugin |
(event-based) | Logs joins, leaves, nickname and role changes |
InviteTrackerPlugin |
(event-based) | Tracks which invite brought each member |
RolePersistencePlugin |
(event-based) | Restores roles when members rejoin |
AIModeratorPlugin |
(event-based) | AI-powered content moderation |
SecurityLabPlugin |
/lab_report, /lab_flood_check, /lab_redos |
Audit guild permissions and security posture |
ServerSetupPlugin |
/setup-server |
Preview and apply channel/role presets from four server templates |
AIPlugin |
/ask |
Base conversational AI plugin — powers any configured provider |
OpenClaudePlugin |
/ask |
Backwards-compatible subclass of AIPlugin for Anthropic Claude |
OpenClawPlugin |
/claw |
Lightweight Claude assistant variant |
Full documentation: docs/builtin-plugins.md
+----------------+ +-------------------+ +----------------------+
| Discord.py | <--> | EasyCord (Bot) | <--> | InteractionRegistry |
+----------------+ +---------+---------+ +----------------------+
|
+-----------+-----------+-----------+-----------+
| | | | |
+-----+-----+ +---+-------+ +-+--------+ +-+-------+ +-----------+
| Plugins | | Middleware| | Database | | i18n | | AI Layer |
+-----------+ +-----------+ +----------+ +---------+ +-----------+
InteractionRegistry is the authoritative EasyCord inventory. discord.app_commands.CommandTree remains the Discord sync backend.
my_bot/
├── bot.py # startup, BotConfig, plugin registration
├── plugins/
│ ├── __init__.py
│ ├── fun.py # one Plugin subclass per file
│ └── moderation.py
├── locales/
│ ├── en-US.json
│ └── es-ES.json
├── tests/
│ └── test_commands.py
└── pyproject.toml
- Keep
bot.pyfor startup and wiring only. - Put each feature in its own
Plugin— reloadable, testable, isolated. - Use
db_backend="memory"in tests so runs stay offline and produce no local files.
| What you need | Raw discord.py |
EasyCord |
|---|---|---|
| Slash command with typed args | CommandTree + manual sync |
@slash(description="...") — sync handled |
| Component routing | Match string IDs manually per handler | @component("ticket:close:{id:int}") — typed |
| Reusable feature module | Cog with manual setup/teardown |
Plugin with on_load, on_unload, on_reload |
| Per-guild settings | Hand-rolled storage + migration code | ServerConfigStore + ConfigSchema migrations |
| Request control | Decorator chains on every command | bot.use(catch_errors(), rate_limit(), guild_only()) |
| Error waterfall | Re-raise or duplicate handlers | @command_error → Plugin.on_error → @bot.on_error |
| Offline testing | Mock the entire discord.py client | ctx = await invoke(bot, "kick") |
| AI tool calling | LLM SDK + prompt engineering + glue | @ai_tool + Orchestrator + safety levels |
| Day-one feature set | Zero plugins, all from scratch | 30 built-in plugins, load_builtin_plugins=True |
EasyCord is released under the MIT License.
See pyproject.toml for the canonical license metadata.
Copyright (c) 2026 Rolling Codes.
Docs: Getting Started · Built-in Plugins · AI Features · All guides → · Latest release notes