-
Notifications
You must be signed in to change notification settings - Fork 1
Getting Started
Welcome to the official wiki for Grace Framework — an extensible, opinionated Discord bot framework built on top of discord.py. Grace is designed to help developers rapidly build scalable, feature-rich Discord bots with minimal boilerplate.
- Quick to start: Generate a full-featured bot in seconds
- Modular Architecture: Clean separation of features via extensions
- Database Integration: Easily connect your bot to a persistent backend
- Built-in Generators: Create extensions, models and more with a single command
Grace Framework was inspired by our community discord bot, Grace, that evolved into a modular and powerful bot — it began to resemble a standalone framework. Recognizing its potential, we decided to extracted its architecture into Grace Framework, making its ease-of-use and flexibility available to other developers.
This guide will provide you with every details necessary to getting started and understanding the basic concepts of Grace Framework.
This guide will show you:
- How to install Grace Framework, create a new Grace Bot, and connect your bot to a database.
- The general layout of a Bot.
- The basic principles of Extensions (Cog) and Models.
- How to configure your bot
Throughout this guide, we will create a bot called task-bot - a simple task manager bot.
This bot will able to do the follow:
- List all current tasks -
/list - Add new tasks -
/add <task name> <task description> - Remove existing tasks -
/delete <task id> - Mark tasks as complete -
/done <task id>
Before we begin, make sure you have the following installed:
- Python 3.10+
- pip
-
SQLite (default database, no setup needed)
- You can also use Postgresql, MySQL & MariaDB, Oracle, and MS-SQL, but it requires some configuration.
It is recommend to use a virtual environment to manage the dependencies of your project.
A virtual environment is an isolated Python environment. In other words, it allows you to isolate dependencies required by different projects from each other. This prevents version issues between projects and system tools.
In this example, we use virtualenv to create and activate the virtual environment.
$ virtualenv venv
$ source venv/bin/activate
To install Grace Framework simply use run:
$ pip install grace-framework
To install the development version:
Linux/Win:
$ git clone https://github.com/Code-Society-Lab/grace-framework.git
$ cd grace-framework
$ pip install -e .[dev]
MacOS:
$ git clone https://github.com/Code-Society-Lab/grace-framework.git
$ cd grace-framework
$ pip install -e ".[dev]"
Grace comes with a utility script to simplify interaction with your bot. By typing grace --help you can see the full list of commands you can use. Outside of a project, only one grace new is available. This command will generate a new project.
Run the command bellow to generate your task-bot:
$ grace new task-bot
Once generated, switch to its directory:
cd task-bot
Here a quick peek at at the files and directories that will be generated.
| File/Folder | Description |
|---|---|
| alembic.ini | Alembic configuration file for migrations |
| bot | Contains bot logic - You will mostly work from there |
| config | Configuration files and settings |
| db | Database migrations, seeds and scripts |
| lib | Shared libraries or utilities |
| logs | Log files generated by the application |
| README.md | Project overview and documentation |
| pyproject.toml | Your project packaging configuration file |
| .env | Environment variables file (hidden) |
In order for your bot to work, it requires a discord token - without it will be impossible to communicate with discord.
To get your discord bot token, you must first create a discord application. Once you’ve successfully created your app, copy your bot's token and past it in the .env after DISCORD_TOKEN=
DISCORD_TOKEN=Past your token here
Don’t forget to invite your bot into your discord server to test it.
🚨 DO NOT SHARE YOUR TOKEN OR
.envWITH ANYONE. In case of accidental reveal, reset it as soon as possible.
Now, let's run your bot with:
$ grace run --watch
--watchenables the hot reload
If your token is setup properly, it will show this:
[2025-04-18 01:22:25] development _show_application_info cli INFO
| Discord.py version: 2.5.2
| PID: 895923
| Environment: development
| Syncing command: True
| Watcher enabled: True
| Using database: task_bot_development.db with sqlite
[2025-04-18 01:22:25] development __init__ selector_events DEBUG Using selector: EpollSelector
2025-04-18 01:22:25 WARNING discord.ext.commands.bot Privileged message content intent is missing, commands may not work as expected.
[2025-04-18 01:22:25] development _async_setup_hook bot WARNING Privileged message content intent is missing, commands may not work as expected.
2025-04-18 01:22:25 INFO discord.client logging in using static token
[2025-04-18 01:22:25] development login client INFO logging in using static token
[2025-04-18 01:22:26] development setup_hook bot WARNING Syncing application commands. This may take some time.
2025-04-18 01:22:26 INFO discord.gateway Shard ID None has connected to Gateway (Session ID: 79ad3f472e8e83f31ee61a522a38150f).
[2025-04-18 01:22:26] development received_message gateway INFO Shard ID None has connected to Gateway (Session ID: 79ad3f472e8e83f31ee61a522a38150f).
[2025-04-18 01:22:28] development on_ready task_bot INFO TaskBot:#123456789123456789 is online and ready to use!Let's start by generating our first model using the built-in generator:
$ grace generate model Task name:str description:str done:boolThis command does several things automatically:
- Creates a new database migration inside
db/alembic/versions/. - Generates a new SQLModel class in
bot/models/task.py. - Registers the model so it can be used directly with the built-in ORM utilities.
Example output:
[2025-10-18 21:02:21] development generate model_generator INFO Generating model 'Task'
[2025-10-18 21:02:21] development __init__ migration INFO Context impl SQLiteImpl.
[2025-10-18 21:02:21] development __init__ migration INFO Will assume non-transactional DDL.
[2025-10-18 21:02:21] development _compare_tables compare INFO Detected added table 'task'
Generating /db/alembic/versions/99e6d0cf0aec_create_task.py ... doneNote
Defining columns in the generate command is currently limited to basic Python types (int, float, str, bool, etc.).
Grace uses Alembic under the hood to manage schema migrations.
Every time you generate a model or make changes to an existing one, Grace automatically detects the differences and creates a migration script for you in the db/alembic/versions directory.
Each migration file contains the necessary SQL operations to bring your database schema in sync with your models.
"""Create Task
Revision ID: 99e6d0cf0aec
Revises:
Create Date: 2025-10-18 21:02:21.978903
"""
import sqlmodel
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '99e6d0cf0aec'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table('task',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('done', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
def downgrade() -> None:
op.drop_table('task')You can inspect or modify these files manually if you need fine-grained control over schema changes, though for most use cases the auto-generated migrations will be sufficient.
Example structure:
db/
├── alembic/
│ ├── versions/
│ │ ├── 99e6d0cf0aec_create_task.py
│ ├── env.py
│ ├── script.py.mako
Once a migration has been created, you can apply it to your database using:
$ grace db upThis command applies all pending migrations in order, updating your database schema to match your latest models.
Example output:
[2025-10-18 21:05:42] development db_up INFO Applying migration 99e6d0cf0aec_create_task.py
[2025-10-18 21:05:42] development db_up INFO Upgrade successful.
Now that we have a model and a table for storing tasks, let’s give our bot a way to interact with them. In Grace, this is done using Cogs — modular classes that group related commands together. They contain the logic that responds to user actions.
Let’s generate our first one:
$ grace generate cog TaskThis command creates a new Cog module inside your project at:
bot/extensions/task_cog.py
The generated file looks like this:
from discord.ext.commands import Cog
from grace.bot import Bot
class TasksCog(Cog, name="Tasks"):
def __init__(self, bot: Bot):
self.bot: Bot = bot
async def setup(bot: Bot):
await bot.add_cog(TasksCog(bot))This is the starting point for your command logic.
The TasksCog class is registered automatically when your bot starts, and you can define commands inside it to interact with your models.
Let’s make our bot useful by allowing it to list, add, delete, and mark tasks as done through Discord commands.
Start by importing what you’ll need at the top of the file:
from discord.ext.commands import (
Cog,
Context,
hybrid_command,
)
from bot.models.task import TaskLet’s begin with a simple command that lists all existing tasks.
@hybrid_command(name="list")
async def list_tasks(self, ctx: Context):
tasks = Task.all()
if not tasks:
await ctx.send("No tasks found.")
return
lines = [f"- {'✅' if t.done else '❌'} [{t.id}] **{t.name}**: {t.description or ''}" for t in tasks]
await ctx.send("\n".join(lines))This command fetches all Task records and sends them as a formatted message.
Next, let’s allow users to create new tasks directly:
@hybrid_command(name="add")
async def add_task(self, ctx: Context, name: str, *, description: str = ""):
task = Task.create(name=name, description=description, done=False)
await ctx.send(f"Added task: **{task.name}**")Now users can type something like:
/add "Write docs" Finish the getting started guide
Let’s also let users delete tasks by name:
@hybrid_command(name="delete")
async def delete_task(self, ctx: Context, *, task_id: int):
task = Task.find(task_id)
if not task:
await ctx.send(f"No task found with id '{task_id}'.")
return
await task.delete()
await ctx.send(f"Deleted task: **{name}** 🗑️")Finally, we’ll add a command to mark a task as completed:
@hybrid_command(name="done")
async def done_task(self, ctx: Context, *, task_id: int):
task = Task.find(task_id)
if not task:
await ctx.send(f"No task found with name '{name}'.")
return
task.update(done=True)
await ctx.send(f"Marked **{task.name}** as done ✅")/add "Write docs" Finish the migration section
/list
/done 1
!list
!delete 1
This guide is still being written!