diff --git a/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql b/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql
new file mode 100644
index 0000000..16e3c9f
--- /dev/null
+++ b/src/EllieBot/Migrations/PostgreSql/20250225212147_xp-excl-xp-rate.sql
@@ -0,0 +1,27 @@
+START TRANSACTION;
+ALTER TABLE userfishstats ADD bait integer;
+
+ALTER TABLE userfishstats ADD pole integer;
+
+CREATE TABLE channelxpconfig (
+    id integer GENERATED BY DEFAULT AS IDENTITY,
+    guildid numeric(20,0) NOT NULL,
+    channelid numeric(20,0) NOT NULL,
+    xpamount integer NOT NULL,
+    cooldown real NOT NULL,
+    CONSTRAINT pk_channelxpconfig PRIMARY KEY (id),
+    CONSTRAINT ak_channelxpconfig_guildid_channelid UNIQUE (guildid, channelid)
+);
+
+CREATE TABLE guildxpconfig (
+    guildid numeric(20,0) NOT NULL,
+    xpamount integer NOT NULL,
+    cooldown integer NOT NULL,
+    xptemplateurl text,
+    CONSTRAINT pk_guildxpconfig PRIMARY KEY (guildid)
+);
+
+INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
+VALUES ('20250225212147_xp-excl-xp-rate', '9.0.1');
+
+COMMIT;
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs
similarity index 98%
rename from src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs
rename to src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs
index b329303..66f7870 100644
--- a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.Designer.cs
+++ b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.Designer.cs
@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 namespace EllieBot.Migrations.PostgreSql
 {
     [DbContext(typeof(PostgreSqlContext))]
-    [Migration("20250202124905_init")]
+    [Migration("20250225212209_init")]
     partial class init
     {
         /// <inheritdoc />
@@ -3456,6 +3456,14 @@ namespace EllieBot.Migrations.PostgreSql
 
                 NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 
+                b.Property<int?>("Bait")
+                    .HasColumnType("integer")
+                    .HasColumnName("bait");
+
+                b.Property<int?>("Pole")
+                    .HasColumnType("integer")
+                    .HasColumnName("pole");
+
                 b.Property<int>("Skill")
                     .HasColumnType("integer")
                     .HasColumnName("skill");
@@ -3474,6 +3482,65 @@ namespace EllieBot.Migrations.PostgreSql
                 b.ToTable("userfishstats", (string)null);
             });
 
+            modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b =>
+            {
+                b.Property<int>("Id")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("integer")
+                    .HasColumnName("id");
+
+                NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+                b.Property<decimal>("ChannelId")
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("channelid");
+
+                b.Property<float>("Cooldown")
+                    .HasColumnType("real")
+                    .HasColumnName("cooldown");
+
+                b.Property<decimal>("GuildId")
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("guildid");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("integer")
+                    .HasColumnName("xpamount");
+
+                b.HasKey("Id")
+                    .HasName("pk_channelxpconfig");
+
+                b.HasAlternateKey("GuildId", "ChannelId")
+                    .HasName("ak_channelxpconfig_guildid_channelid");
+
+                b.ToTable("channelxpconfig", (string)null);
+            });
+
+            modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
+            {
+                b.Property<decimal>("GuildId")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("guildid");
+
+                b.Property<int>("Cooldown")
+                    .HasColumnType("integer")
+                    .HasColumnName("cooldown");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("integer")
+                    .HasColumnName("xpamount");
+
+                b.Property<string>("XpTemplateUrl")
+                    .HasColumnType("text")
+                    .HasColumnName("xptemplateurl");
+
+                b.HasKey("GuildId")
+                    .HasName("pk_guildxpconfig");
+
+                b.ToTable("guildxpconfig", (string)null);
+            });
+
             modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
             {
                 b.Property<int>("Id")
@@ -3978,4 +4045,4 @@ namespace EllieBot.Migrations.PostgreSql
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs
similarity index 98%
rename from src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs
rename to src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs
index 6e4032b..bf7ff89 100644
--- a/src/EllieBot/Migrations/PostgreSql/20250202124905_init.cs
+++ b/src/EllieBot/Migrations/PostgreSql/20250225212209_init.cs
@@ -187,6 +187,23 @@ namespace EllieBot.Migrations.PostgreSql
                     table.UniqueConstraint("ak_buttonrole_roleid_messageid", x => new { x.roleid, x.messageid });
                 });
 
+            migrationBuilder.CreateTable(
+                name: "channelxpconfig",
+                columns: table => new
+                {
+                    id = table.Column<int>(type: "integer", nullable: false)
+                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+                    guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
+                    channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
+                    xpamount = table.Column<int>(type: "integer", nullable: false),
+                    cooldown = table.Column<float>(type: "real", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("pk_channelxpconfig", x => x.id);
+                    table.UniqueConstraint("ak_channelxpconfig_guildid_channelid", x => new { x.guildid, x.channelid });
+                });
+
             migrationBuilder.CreateTable(
                 name: "commandalias",
                 columns: table => new
@@ -487,6 +504,20 @@ namespace EllieBot.Migrations.PostgreSql
                     table.PrimaryKey("pk_guildfilterconfig", x => x.id);
                 });
 
+            migrationBuilder.CreateTable(
+                name: "guildxpconfig",
+                columns: table => new
+                {
+                    guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
+                    xpamount = table.Column<int>(type: "integer", nullable: false),
+                    cooldown = table.Column<int>(type: "integer", nullable: false),
+                    xptemplateurl = table.Column<string>(type: "text", nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("pk_guildxpconfig", x => x.guildid);
+                });
+
             migrationBuilder.CreateTable(
                 name: "honeypotchannels",
                 columns: table => new
@@ -1033,7 +1064,9 @@ namespace EllieBot.Migrations.PostgreSql
                     id = table.Column<int>(type: "integer", nullable: false)
                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                     userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
-                    skill = table.Column<int>(type: "integer", nullable: false)
+                    skill = table.Column<int>(type: "integer", nullable: false),
+                    pole = table.Column<int>(type: "integer", nullable: true),
+                    bait = table.Column<int>(type: "integer", nullable: true)
                 },
                 constraints: table =>
                 {
@@ -2310,6 +2343,9 @@ namespace EllieBot.Migrations.PostgreSql
             migrationBuilder.DropTable(
                 name: "buttonrole");
 
+            migrationBuilder.DropTable(
+                name: "channelxpconfig");
+
             migrationBuilder.DropTable(
                 name: "clubapplicants");
 
@@ -2376,6 +2412,9 @@ namespace EllieBot.Migrations.PostgreSql
             migrationBuilder.DropTable(
                 name: "guildcolors");
 
+            migrationBuilder.DropTable(
+                name: "guildxpconfig");
+
             migrationBuilder.DropTable(
                 name: "honeypotchannels");
 
@@ -2551,4 +2590,4 @@ namespace EllieBot.Migrations.PostgreSql
                 name: "discorduser");
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
index 5d110ab..d58940a 100644
--- a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
+++ b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs
@@ -3453,6 +3453,14 @@ namespace EllieBot.Migrations.PostgreSql
 
                 NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 
+                b.Property<int?>("Bait")
+                    .HasColumnType("integer")
+                    .HasColumnName("bait");
+
+                b.Property<int?>("Pole")
+                    .HasColumnType("integer")
+                    .HasColumnName("pole");
+
                 b.Property<int>("Skill")
                     .HasColumnType("integer")
                     .HasColumnName("skill");
@@ -3471,6 +3479,65 @@ namespace EllieBot.Migrations.PostgreSql
                 b.ToTable("userfishstats", (string)null);
             });
 
+            modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b =>
+            {
+                b.Property<int>("Id")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("integer")
+                    .HasColumnName("id");
+
+                NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+                b.Property<decimal>("ChannelId")
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("channelid");
+
+                b.Property<float>("Cooldown")
+                    .HasColumnType("real")
+                    .HasColumnName("cooldown");
+
+                b.Property<decimal>("GuildId")
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("guildid");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("integer")
+                    .HasColumnName("xpamount");
+
+                b.HasKey("Id")
+                    .HasName("pk_channelxpconfig");
+
+                b.HasAlternateKey("GuildId", "ChannelId")
+                    .HasName("ak_channelxpconfig_guildid_channelid");
+
+                b.ToTable("channelxpconfig", (string)null);
+            });
+
+            modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
+            {
+                b.Property<decimal>("GuildId")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("numeric(20,0)")
+                    .HasColumnName("guildid");
+
+                b.Property<int>("Cooldown")
+                    .HasColumnType("integer")
+                    .HasColumnName("cooldown");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("integer")
+                    .HasColumnName("xpamount");
+
+                b.Property<string>("XpTemplateUrl")
+                    .HasColumnType("text")
+                    .HasColumnName("xptemplateurl");
+
+                b.HasKey("GuildId")
+                    .HasName("pk_guildxpconfig");
+
+                b.ToTable("guildxpconfig", (string)null);
+            });
+
             modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
             {
                 b.Property<int>("Id")
@@ -3975,4 +4042,4 @@ namespace EllieBot.Migrations.PostgreSql
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql b/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql
new file mode 100644
index 0000000..26ee2fa
--- /dev/null
+++ b/src/EllieBot/Migrations/Sqlite/20250225212144_xp-excl-xp-rate.sql
@@ -0,0 +1,25 @@
+BEGIN TRANSACTION;
+ALTER TABLE "UserFishStats" ADD "Bait" INTEGER NULL;
+
+ALTER TABLE "UserFishStats" ADD "Pole" INTEGER NULL;
+
+CREATE TABLE "ChannelXpConfig" (
+    "Id" INTEGER NOT NULL CONSTRAINT "PK_ChannelXpConfig" PRIMARY KEY AUTOINCREMENT,
+    "GuildId" INTEGER NOT NULL,
+    "ChannelId" INTEGER NOT NULL,
+    "XpAmount" INTEGER NOT NULL,
+    "Cooldown" REAL NOT NULL,
+    CONSTRAINT "AK_ChannelXpConfig_GuildId_ChannelId" UNIQUE ("GuildId", "ChannelId")
+);
+
+CREATE TABLE "GuildXpConfig" (
+    "GuildId" INTEGER NOT NULL CONSTRAINT "PK_GuildXpConfig" PRIMARY KEY AUTOINCREMENT,
+    "XpAmount" INTEGER NOT NULL,
+    "Cooldown" INTEGER NOT NULL,
+    "XpTemplateUrl" TEXT NULL
+);
+
+INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
+VALUES ('20250225212144_xp-excl-xp-rate', '9.0.1');
+
+COMMIT;
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs b/src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs
similarity index 98%
rename from src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs
rename to src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs
index ffb15cb..483367d 100644
--- a/src/EllieBot/Migrations/Sqlite/20250202124903_init.Designer.cs
+++ b/src/EllieBot/Migrations/Sqlite/20250225212206_init.Designer.cs
@@ -11,7 +11,7 @@ using EllieBot.Db;
 namespace EllieBot.Migrations.Sqlite
 {
     [DbContext(typeof(SqliteContext))]
-    [Migration("20250202124903_init")]
+    [Migration("20250225212206_init")]
     partial class init
     {
         /// <inheritdoc />
@@ -2571,6 +2571,12 @@ namespace EllieBot.Migrations.Sqlite
                     .ValueGeneratedOnAdd()
                     .HasColumnType("INTEGER");
 
+                b.Property<int?>("Bait")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int?>("Pole")
+                    .HasColumnType("INTEGER");
+
                 b.Property<int>("Skill")
                     .HasColumnType("INTEGER");
 
@@ -2585,6 +2591,51 @@ namespace EllieBot.Migrations.Sqlite
                 b.ToTable("UserFishStats");
             });
 
+            modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b =>
+            {
+                b.Property<int>("Id")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("INTEGER");
+
+                b.Property<ulong>("ChannelId")
+                    .HasColumnType("INTEGER");
+
+                b.Property<float>("Cooldown")
+                    .HasColumnType("REAL");
+
+                b.Property<ulong>("GuildId")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("INTEGER");
+
+                b.HasKey("Id");
+
+                b.HasAlternateKey("GuildId", "ChannelId");
+
+                b.ToTable("ChannelXpConfig");
+            });
+
+            modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
+            {
+                b.Property<ulong>("GuildId")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("Cooldown")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("INTEGER");
+
+                b.Property<string>("XpTemplateUrl")
+                    .HasColumnType("TEXT");
+
+                b.HasKey("GuildId");
+
+                b.ToTable("GuildXpConfig");
+            });
+
             modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
             {
                 b.Property<int>("Id")
@@ -3030,4 +3081,4 @@ namespace EllieBot.Migrations.Sqlite
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/Sqlite/20250202124903_init.cs b/src/EllieBot/Migrations/Sqlite/20250225212206_init.cs
similarity index 98%
rename from src/EllieBot/Migrations/Sqlite/20250202124903_init.cs
rename to src/EllieBot/Migrations/Sqlite/20250225212206_init.cs
index 1f7ecdc..c18b049 100644
--- a/src/EllieBot/Migrations/Sqlite/20250202124903_init.cs
+++ b/src/EllieBot/Migrations/Sqlite/20250225212206_init.cs
@@ -186,6 +186,23 @@ namespace EllieBot.Migrations.Sqlite
                     table.UniqueConstraint("AK_ButtonRole_RoleId_MessageId", x => new { x.RoleId, x.MessageId });
                 });
 
+            migrationBuilder.CreateTable(
+                name: "ChannelXpConfig",
+                columns: table => new
+                {
+                    Id = table.Column<int>(type: "INTEGER", nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
+                    ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
+                    XpAmount = table.Column<int>(type: "INTEGER", nullable: false),
+                    Cooldown = table.Column<float>(type: "REAL", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_ChannelXpConfig", x => x.Id);
+                    table.UniqueConstraint("AK_ChannelXpConfig_GuildId_ChannelId", x => new { x.GuildId, x.ChannelId });
+                });
+
             migrationBuilder.CreateTable(
                 name: "CommandAlias",
                 columns: table => new
@@ -486,6 +503,21 @@ namespace EllieBot.Migrations.Sqlite
                     table.PrimaryKey("PK_GuildFilterConfig", x => x.Id);
                 });
 
+            migrationBuilder.CreateTable(
+                name: "GuildXpConfig",
+                columns: table => new
+                {
+                    GuildId = table.Column<ulong>(type: "INTEGER", nullable: false)
+                        .Annotation("Sqlite:Autoincrement", true),
+                    XpAmount = table.Column<int>(type: "INTEGER", nullable: false),
+                    Cooldown = table.Column<int>(type: "INTEGER", nullable: false),
+                    XpTemplateUrl = table.Column<string>(type: "TEXT", nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_GuildXpConfig", x => x.GuildId);
+                });
+
             migrationBuilder.CreateTable(
                 name: "HoneyPotChannels",
                 columns: table => new
@@ -1035,7 +1067,9 @@ namespace EllieBot.Migrations.Sqlite
                     Id = table.Column<int>(type: "INTEGER", nullable: false)
                         .Annotation("Sqlite:Autoincrement", true),
                     UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
-                    Skill = table.Column<int>(type: "INTEGER", nullable: false)
+                    Skill = table.Column<int>(type: "INTEGER", nullable: false),
+                    Pole = table.Column<int>(type: "INTEGER", nullable: true),
+                    Bait = table.Column<int>(type: "INTEGER", nullable: true)
                 },
                 constraints: table =>
                 {
@@ -2312,6 +2346,9 @@ namespace EllieBot.Migrations.Sqlite
             migrationBuilder.DropTable(
                 name: "ButtonRole");
 
+            migrationBuilder.DropTable(
+                name: "ChannelXpConfig");
+
             migrationBuilder.DropTable(
                 name: "ClubApplicants");
 
@@ -2378,6 +2415,9 @@ namespace EllieBot.Migrations.Sqlite
             migrationBuilder.DropTable(
                 name: "GuildColors");
 
+            migrationBuilder.DropTable(
+                name: "GuildXpConfig");
+
             migrationBuilder.DropTable(
                 name: "HoneyPotChannels");
 
@@ -2553,4 +2593,4 @@ namespace EllieBot.Migrations.Sqlite
                 name: "DiscordUser");
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
index 084a0ee..1b4a98c 100644
--- a/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
+++ b/src/EllieBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs
@@ -2568,6 +2568,12 @@ namespace EllieBot.Migrations.Sqlite
                     .ValueGeneratedOnAdd()
                     .HasColumnType("INTEGER");
 
+                b.Property<int?>("Bait")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int?>("Pole")
+                    .HasColumnType("INTEGER");
+
                 b.Property<int>("Skill")
                     .HasColumnType("INTEGER");
 
@@ -2582,6 +2588,51 @@ namespace EllieBot.Migrations.Sqlite
                 b.ToTable("UserFishStats");
             });
 
+            modelBuilder.Entity("EllieBot.Modules.Xp.ChannelXpConfig", b =>
+            {
+                b.Property<int>("Id")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("INTEGER");
+
+                b.Property<ulong>("ChannelId")
+                    .HasColumnType("INTEGER");
+
+                b.Property<float>("Cooldown")
+                    .HasColumnType("REAL");
+
+                b.Property<ulong>("GuildId")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("INTEGER");
+
+                b.HasKey("Id");
+
+                b.HasAlternateKey("GuildId", "ChannelId");
+
+                b.ToTable("ChannelXpConfig");
+            });
+
+            modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
+            {
+                b.Property<ulong>("GuildId")
+                    .ValueGeneratedOnAdd()
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("Cooldown")
+                    .HasColumnType("INTEGER");
+
+                b.Property<int>("XpAmount")
+                    .HasColumnType("INTEGER");
+
+                b.Property<string>("XpTemplateUrl")
+                    .HasColumnType("TEXT");
+
+                b.HasKey("GuildId");
+
+                b.ToTable("GuildXpConfig");
+            });
+
             modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
             {
                 b.Property<int>("Id")
@@ -3027,4 +3078,4 @@ namespace EllieBot.Migrations.Sqlite
 #pragma warning restore 612, 618
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Xp/Xp.cs b/src/EllieBot/Modules/Xp/Xp.cs
index 3e6801a..12b9ea5 100644
--- a/src/EllieBot/Modules/Xp/Xp.cs
+++ b/src/EllieBot/Modules/Xp/Xp.cs
@@ -53,91 +53,6 @@ public partial class Xp : EllieModule<XpService>
         }
     }
 
-    [Cmd]
-    [RequireContext(ContextType.Guild)]
-    [UserPerm(GuildPerm.Administrator)]
-    public async Task XpExclude(Server _)
-    {
-        var ex = await _service.ToggleExcludeServerAsync(ctx.Guild.Id);
-
-        if (ex)
-            await Response().Confirm(strs.excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync();
-        else
-            await Response().Confirm(strs.not_excluded(Format.Bold(ctx.Guild.ToString()))).SendAsync();
-    }
-
-    [Cmd]
-    [UserPerm(GuildPerm.ManageRoles)]
-    [RequireContext(ContextType.Guild)]
-    public async Task XpExclude(Role _, [Leftover] IRole role)
-    {
-        var ex = await _service.ToggleExcludeRoleAsync(ctx.Guild.Id, role.Id);
-
-        if (ex)
-            await Response().Confirm(strs.excluded(Format.Bold(role.ToString()))).SendAsync();
-        else
-            await Response().Confirm(strs.not_excluded(Format.Bold(role.ToString()))).SendAsync();
-    }
-
-    [Cmd]
-    [UserPerm(GuildPerm.ManageChannels)]
-    [RequireContext(ContextType.Guild)]
-    public async Task XpExclude(Channel _, [Leftover] IChannel? channel = null)
-    {
-        if (channel is null)
-            channel = ctx.Channel;
-
-        var ex = await _service.ToggleExcludeChannelAsync(ctx.Guild.Id, channel.Id);
-
-        if (ex)
-            await Response().Confirm(strs.excluded(Format.Bold(channel.ToString()))).SendAsync();
-        else
-            await Response().Confirm(strs.not_excluded(Format.Bold(channel.ToString()))).SendAsync();
-    }
-
-    [Cmd]
-    [RequireContext(ContextType.Guild)]
-    public async Task XpExclusionList()
-    {
-        var serverExcluded = _service.IsServerExcluded(ctx.Guild.Id);
-        var roles = _service.GetExcludedRoles(ctx.Guild.Id)
-                            .Select(x => ctx.Guild.GetRole(x))
-                            .Where(x => x is not null)
-                            .Select(x => $"`role`   {x.Mention}")
-                            .ToList();
-
-        var chans = (await _service.GetExcludedChannels(ctx.Guild.Id)
-                                   .Select(x => ctx.Guild.GetChannelAsync(x))
-                                   .WhenAll()).Where(x => x is not null)
-                                              .Select(x => $"`channel` <#{x.Id}>")
-                                              .ToList();
-
-        var rolesStr = roles.Any() ? string.Join("\n", roles) + "\n" : string.Empty;
-        var chansStr = chans.Count > 0 ? string.Join("\n", chans) + "\n" : string.Empty;
-        var desc = Format.Code(serverExcluded
-            ? GetText(strs.server_is_excluded)
-            : GetText(strs.server_is_not_excluded));
-
-        desc += "\n\n" + rolesStr + chansStr;
-
-        var lines = desc.Split('\n');
-        await Response()
-              .Paginated()
-              .Items(lines)
-              .PageSize(15)
-              .CurrentPage(0)
-              .Page((items, _) =>
-              {
-                  var embed = CreateEmbed()
-                              .WithTitle(GetText(strs.exclusion_list))
-                              .WithDescription(string.Join('\n', items))
-                              .WithOkColor();
-
-                  return embed;
-              })
-              .SendAsync();
-    }
-
     [Cmd]
     [EllieOptions<LbOpts>]
     [Priority(0)]
diff --git a/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs b/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs
new file mode 100644
index 0000000..c60e15e
--- /dev/null
+++ b/src/EllieBot/Modules/Xp/XpRate/XpRateCommands.cs
@@ -0,0 +1,254 @@
+using LinqToDB;
+using LinqToDB.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using EllieBot.Common.ModuleBehaviors;
+using System.ComponentModel.DataAnnotations;
+
+namespace EllieBot.Modules.Xp;
+
+public partial class Xp
+{
+    [RequireUserPermission(GuildPermission.ManageGuild)]
+    public class XpRateCommands : EllieModule<GuildConfigXpService>
+    {
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRate()
+        {
+            var rates = await _service.GetGuildXpRatesAsync(ctx.Guild.Id);
+            if (rates.GuildConfig is null && !rates.ChannelRates.Any())
+            {
+                await Response().Pending(strs.xp_rate_none).SendAsync();
+                return;
+            }
+
+            var eb = CreateEmbed()
+                     .WithOkColor();
+            if (rates.GuildConfig is not null)
+            {
+                eb.AddField(GetText(strs.xp_rate_server),
+                    strs.xp_rate_amount_cooldown(
+                        rates.GuildConfig.XpAmount,
+                        rates.GuildConfig.Cooldown));
+            }
+
+            if (rates.ChannelRates.Any())
+            {
+                var channelRates = rates.ChannelRates
+                                        .Select(c => $"<#{c.ChannelId}>: {GetRateString(c.XpAmount, c.Cooldown)}")
+                                        .Join('\n');
+
+                eb.AddField(GetText(strs.xp_rate_channels), channelRates);
+            }
+
+            await Response().Embed(eb).SendAsync();
+        }
+
+        private string GetRateString(int argXpAmount, float cd)
+        {
+            if (argXpAmount == 0 || cd == 0)
+                return GetText(strs.xp_rate_no_gain);
+
+            return GetText(strs.xp_rate_amount_cooldown(argXpAmount, Math.Round(cd, 1).ToString(Culture)));
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRate(int amount, float minutes)
+        {
+            if (amount is < 0 or > 1000)
+            {
+                await Response().Error(strs.xp_rate_amount_invalid).SendAsync();
+                return;
+            }
+
+            if (minutes is < 0 or > 1440)
+            {
+                await Response().Error(strs.xp_rate_cooldown_invalid).SendAsync();
+                return;
+            }
+
+            await _service.SetGuildXpRateAsync(ctx.Guild.Id, amount, (int)Math.Ceiling(minutes));
+            await Response().Confirm(strs.xp_rate_server_set(amount, minutes)).SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRate(IMessageChannel channel, int amount, float minutes)
+        {
+            if (amount is < 0 or > 1000)
+            {
+                await Response().Error(strs.xp_rate_amount_invalid).SendAsync();
+                return;
+            }
+
+            if (minutes is < 0 or > 1440)
+            {
+                await Response().Error(strs.xp_rate_cooldown_invalid).SendAsync();
+                return;
+            }
+
+            await _service.SetChannelXpRateAsync(ctx.Guild.Id, channel.Id, amount, (int)Math.Ceiling(minutes));
+            await Response()
+                  .Confirm(strs.xp_rate_channel_set(Format.Bold(channel.ToString()), amount, minutes))
+                  .SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRateReset()
+        {
+            await _service.ResetGuildXpRateAsync(ctx.Guild.Id);
+            await Response().Confirm(strs.xp_rate_server_reset).SendAsync();
+        }
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRateReset(IMessageChannel channel)
+            => await XpRateReset(channel.Id);
+
+        [Cmd]
+        [RequireContext(ContextType.Guild)]
+        public async Task XpRateReset(ulong channelId)
+        {
+            await _service.ResetChannelXpRateAsync(ctx.Guild.Id, channelId);
+            await Response().Confirm(strs.xp_rate_channel_reset($"<#{channelId}>")).SendAsync();
+        }
+    }
+}
+
+public class GuildConfigXpService : IReadyExecutor, IEService
+{
+    private readonly DbService _db;
+
+    public GuildConfigXpService(DbService db)
+    {
+        _db = db;
+    }
+
+    public async Task<(GuildXpConfig? GuildConfig, List<ChannelXpConfig> ChannelRates)> GetGuildXpRatesAsync(
+        ulong guildId)
+    {
+        await using var uow = _db.GetDbContext();
+        var guildConfig =
+            await AsyncExtensions.FirstOrDefaultAsync(uow.GetTable<GuildXpConfig>(), x => x.GuildId == guildId);
+
+        var channelRates = await AsyncExtensions.ToListAsync(uow.GetTable<ChannelXpConfig>()
+                                                                .Where(x => x.GuildId == guildId));
+
+        return (guildConfig, channelRates);
+    }
+
+    public async Task SetGuildXpRateAsync(ulong guildId, int amount, int cooldown)
+    {
+        await using var uow = _db.GetDbContext();
+        await uow.GetTable<GuildXpConfig>()
+                 .InsertOrUpdateAsync(() => new()
+                 {
+                     GuildId = guildId,
+                     XpAmount = amount,
+                     Cooldown = cooldown
+                 },
+                     (_) => new()
+                     {
+                         Cooldown = cooldown,
+                         XpAmount = amount,
+                         GuildId = guildId
+                     },
+                     () => new()
+                     {
+                         GuildId = guildId
+                     });
+    }
+
+    public async Task SetChannelXpRateAsync(
+        ulong guildId,
+        ulong channelId,
+        int amount,
+        int cooldown)
+    {
+        await using var uow = _db.GetDbContext();
+        await uow.GetTable<ChannelXpConfig>()
+                 .InsertOrUpdateAsync(() => new()
+                 {
+                     GuildId = guildId,
+                     ChannelId = channelId,
+                     XpAmount = amount,
+                     Cooldown = cooldown
+                 },
+                     (_) => new()
+                     {
+                         Cooldown = cooldown,
+                         XpAmount = amount,
+                         GuildId = guildId,
+                         ChannelId = channelId
+                     },
+                     () => new()
+                     {
+                         GuildId = guildId,
+                         ChannelId = channelId
+                     });
+    }
+
+    public async Task<bool> ResetGuildXpRateAsync(ulong guildId)
+    {
+        await using var uow = _db.GetDbContext();
+        var deleted = await uow.GetTable<GuildXpConfig>()
+                               .Where(x => x.GuildId == guildId)
+                               .DeleteAsync();
+        return deleted > 0;
+    }
+
+    public async Task<bool> ResetChannelXpRateAsync(ulong guildId, ulong channelId)
+    {
+        await using var uow = _db.GetDbContext();
+        var deleted = await uow.GetTable<ChannelXpConfig>()
+                               .Where(x => x.GuildId == guildId && x.ChannelId == channelId)
+                               .DeleteAsync();
+        return deleted > 0;
+    }
+
+    public Task OnReadyAsync()
+        => Task.CompletedTask;
+}
+
+public class GuildXpConfig
+{
+    [Key]
+    public ulong GuildId { get; set; }
+
+    public int XpAmount { get; set; }
+    public int Cooldown { get; set; }
+    public string? XpTemplateUrl { get; set; }
+}
+
+public sealed class GuildXpConfigEntity : IEntityTypeConfiguration<GuildXpConfig>
+{
+    public void Configure(EntityTypeBuilder<GuildXpConfig> builder)
+    {
+    }
+}
+
+public class ChannelXpConfig
+{
+    [Key]
+    public int Id { get; set; }
+
+    public ulong GuildId { get; set; }
+    public ulong ChannelId { get; set; }
+    public int XpAmount { get; set; }
+    public float Cooldown { get; set; }
+}
+
+public sealed class ChannelXpConfigEntity : IEntityTypeConfiguration<ChannelXpConfig>
+{
+    public void Configure(EntityTypeBuilder<ChannelXpConfig> builder)
+    {
+        builder.HasAlternateKey(x => new
+        {
+            x.GuildId,
+            x.ChannelId
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/EllieBot/strings/aliases.yml b/src/EllieBot/strings/aliases.yml
index 77e41e6..3e76803 100644
--- a/src/EllieBot/strings/aliases.yml
+++ b/src/EllieBot/strings/aliases.yml
@@ -1091,12 +1091,6 @@ experience:
 xptemplatereload:
   - xptempreload
   - xptr
-xpexclusionlist:
-  - xpexclusionlist
-  - xpexl
-xpexclude:
-  - xpexclude
-  - xpex
 xpleveluprewards:
   - xplvluprewards
   - xprews
@@ -1581,4 +1575,8 @@ fishlist:
 fishspot:
   - fishspot 
   - fisp
-  - fish?
\ No newline at end of file
+  - fish?
+xprate:
+  - xprate
+xpratereset:
+  - xpratereset
\ No newline at end of file
diff --git a/src/EllieBot/strings/commands/commands.en-US.yml b/src/EllieBot/strings/commands/commands.en-US.yml
index a390f53..cb691ca 100644
--- a/src/EllieBot/strings/commands/commands.en-US.yml
+++ b/src/EllieBot/strings/commands/commands.en-US.yml
@@ -3517,28 +3517,6 @@ xptemplatereload:
     - ''
   params:
     - { }
-xpexclusionlist:
-  desc: Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded.
-  ex:
-    - ''
-  params:
-    - { }
-xpexclude:
-  desc: Exclude a channel, role or current server from the xp system.
-  ex:
-    - Role Excluded-Role
-    - Server
-  params:
-    - _:
-        desc: "The ID of the server to exclude from the XP system."
-    - _:
-        desc: "The role that should not receive XP rewards."
-      role:
-        desc: "The role that should not receive XP rewards."
-    - _:
-        desc: "The ID of the channel to exclude from XP tracking."
-      channel:
-        desc: "The ID of the channel to exclude from XP tracking."
 xpleveluprewards:
   desc: Shows currently set level up rewards.
   ex:
@@ -4953,4 +4931,35 @@ fishspot:
   ex:
     - ''
   params:
-    - { }
\ No newline at end of file
+    - { }
+xprate:
+  desc: |-
+    Sets the xp rate for the server or the specified channel.
+    First specify the amount, and then the cooldown in minutes.
+    Provide no parameters to see the current rates.
+  ex:
+    - ''
+    - '3 5'
+    - '#channel 50 1'
+  params:
+    - { }
+    - amount:
+        desc: "The amount of xp to give per message."
+      minutes:
+        desc: "The cooldown in minutes. Allows decimal values."
+    - channel:
+        desc: "The channel to set the rate for."
+      amount:
+        desc: "The amount of xp to give per message."
+      minutes:
+        desc: "The cooldown in minutes. Allows decimal values."
+xpratereset:
+  desc: |-
+    Resets the xp rate for the server or the specified channel.
+  ex:
+    - ''
+    - '#channel'
+  params:
+    - { }
+    - channel:
+        desc: "The channel to reset the rate for."
\ No newline at end of file
diff --git a/src/EllieBot/strings/responses/responses.en-US.json b/src/EllieBot/strings/responses/responses.en-US.json
index 13c177e..e5dadaf 100644
--- a/src/EllieBot/strings/responses/responses.en-US.json
+++ b/src/EllieBot/strings/responses/responses.en-US.json
@@ -836,11 +836,6 @@
   "xpn_notif_dm": "In a direct message channel.",
   "xpn_notif_disabled": "Nowhere.",
   "xprewsreset_confirm": "Are you sure you want to delete ALL xp level up rewards from this server? This action is irreversible.",
-  "excluded": "{0} has been excluded from the XP system on this server.",
-  "not_excluded": "{0} is no longer excluded from the XP system on this server.",
-  "exclusion_list": "Exclusion List",
-  "server_is_excluded": "This server is excluded.",
-  "server_is_not_excluded": "This server is not excluded.",
   "level_up_channel": "Congratulations {0}, You've reached level {1}!",
   "level_up_dm": "Congratulations {0}, You've reached level {1} on {2} server!",
   "level_up_global": "Congratulations {0}, You've reached global level {1}!",
@@ -1172,5 +1167,16 @@
   "fish_weather_forecast": "Forecast",
   "fish_tod": "Time of Day",
   "fish_skill_up": "Fishing skill increased to **{0} / {1}**",
-  "fish_list_title": "Fishing"
+  "fish_list_title": "Fishing",
+  "xp_rate_none": "No xp rate overrides on this server.",
+  "xp_rate_amount_invalid": "Amount must be between 0 and 1000.",
+  "xp_rate_cooldown_invalid": "Cooldown must be between 0 and 1440 minutes.",
+  "xp_rate_server": "Server xp Rate",
+  "xp_rate_amount_cooldown": "{0} xp per every {1} minutes",
+  "xp_rate_channels": "Channel XP Rates",
+  "xp_rate_server_set": "Server xp rate set to {0} xp per every {1} minutes.",
+  "xp_rate_channel_set": "Channel {0} xp rate set to {1} xp per every {2} minutes.",
+  "xp_rate_server_reset": "Server xp rate has been reset to global defaults.",
+  "xp_rate_channel_reset": "Channel {0} xp rate has been reset.",
+  "xp_rate_no_gain": "No xp gain"
 }