Mineral is a Dart framework for building Discord bots. It handles the Discord gateway, REST API, and interaction routing so you can focus on your bot's logic.
Everything is organized around providers — isolated modules that register commands, events, and components. Each feature lives in its own class, making bots easy to grow and maintain across a team.
| Package | Description | Version |
|---|---|---|
| mineral | Core framework — gateway, REST API, commands, events, components | |
| mineral_cache | Cache providers — in-memory and Redis |
Register providers and start the bot. Each provider groups related commands, events, and components.
void main() async {
final client = ClientBuilder()
.setIntent(Intent.allNonPrivileged)
.registerProvider(WelcomeProvider.new)
.registerProvider(FeedbackProvider.new)
.build();
await client.init();
}final class WelcomeProvider extends Provider {
final Client _client;
WelcomeProvider(this._client) {
_client
..register<OnMemberJoin>(OnMemberJoin.new)
..register<FeedbackButton>(FeedbackButton.new);
}
}React to a member joining the server and send a message in the system channel.
final class OnMemberJoin extends ServerMemberAddEvent {
@override
Future<void> handle(Member member, Server server) async {
final channel = await server.channels.resolveSystemChannel();
await channel?.send(MessageBuilder.text('Welcome, ${member.username}!'));
}
}Declare a /say command that repeats a message back to the user.
final class SayCommand implements CommandDeclaration {
Future<void> handle(ServerCommandContext ctx, CommandOptions options) async {
final message = options.require<String>('message');
await ctx.interaction.reply(builder: MessageBuilder.text(message));
}
@override
CommandDeclarationBuilder build() {
return CommandDeclarationBuilder()
..setName('say')
..setDescription('Repeat a message')
..addOption(Option.string(name: 'message', description: 'Text to repeat', required: true))
..setHandle(handle);
}
}Bind a button to a handler by its customId. Mineral routes the click automatically — no switch statement, no manual dispatch.
final class FeedbackButton extends ServerButtonClickEvent {
@override
String? get customId => 'open_feedback';
@override
Future<void> handle(ServerButtonContext ctx) async {
await ctx.interaction.reply(
builder: MessageBuilder.text('Thanks for your feedback!'),
);
}
}This repo uses Dart workspaces. All packages share a single dependency resolution from the root.
# Install all dependencies
dart pub get
# Run tests for a specific package
dart test packages/core
dart test packages/cache
# Analyze a specific package
dart analyze packages/core
dart analyze packages/cacheEach package is published independently, triggered by a git tag.
git tag core-v4.3.0 && git push origin core-v4.3.0 # publishes mineral
git tag cache-v1.3.0 && git push origin cache-v1.3.0 # publishes mineral_cache