Skip to content

Getting Started

Simon Roy edited this page Oct 19, 2025 · 16 revisions

Introduction

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.

Key features

  • 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

Inspiration

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.

What is This Guide For

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

Creating a New 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>

Installation

Prerequisites

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.

Virtual Environment

It is recommend to use a virtual environment to manage the dependencies of your project.

What is a Virtual Environment?

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.

Create and Activate Your Virtual Environment

In this example, we use virtualenv to create and activate the virtual environment.

$ virtualenv venv
$ source venv/bin/activate

Install Grace Framework

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]"

Generate Your Bot

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

Directory Structure

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)

Building and Running Your Bot

Minimal Configuration

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 .env WITH ANYONE. In case of accidental reveal, reset it as soon as possible.

Run Your Bot

Now, let's run your bot with:

$ grace run --watch

--watch enables 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!

Creating a Database Model

Let's start by generating our first model using the built-in generator:

$ grace generate model Task name:str description:str done:bool

This 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 ...  done

Note

Defining columns in the generate command is currently limited to basic Python types (int, float, str, bool, etc.).

Database Migrations

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

Running Migrations

Once a migration has been created, you can apply it to your database using:

$ grace db up

This 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.

Creating a Cog

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 Task

This 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.

Adding Commands

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 Task

Listing tasks

Let’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.

Adding a Task

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

Deleting a Task

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}** 🗑️")

Marking a Task as Done

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 ✅")

Trying it out

/add "Write docs" Finish the migration section
/list
/done 1
!list
!delete 1

Writing Tests

This guide is still being written!

write the doc