diff --git a/src/EllieBot/Modules/Expressions/EllieExpressionExtensions.cs b/src/EllieBot/Modules/Expressions/EllieExpressionExtensions.cs
new file mode 100644
index 0000000..72606a7
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/EllieExpressionExtensions.cs
@@ -0,0 +1,91 @@
+#nullable disable
+using EllieBot.Db.Models;
+using System.Runtime.CompilerServices;
+
+namespace EllieBot.Modules.EllieExpressions;
+
+public static class EllieExpressionExtensions
+{
+    private static string ResolveTriggerString(this string str, DiscordSocketClient client)
+        => str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal);
+
+    public static async Task<IUserMessage> Send(
+        this EllieExpression cr,
+        IUserMessage ctx,
+        IReplacementService repSvc,
+        DiscordSocketClient client,
+        IMessageSenderService sender)
+    {
+        var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel;
+
+        var trigger = cr.Trigger.ResolveTriggerString(client);
+        var substringIndex = trigger.Length;
+        if (cr.ContainsAnywhere)
+        {
+            var pos = ctx.Content.AsSpan().GetWordPosition(trigger);
+            if (pos == WordPosition.Start)
+                substringIndex += 1;
+            else if (pos == WordPosition.End)
+                substringIndex = ctx.Content.Length;
+            else if (pos == WordPosition.Middle)
+                substringIndex += ctx.Content.IndexOf(trigger, StringComparison.InvariantCulture);
+        }
+
+        var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
+
+        var repCtx = new ReplacementContext(client: client,
+                guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild,
+                channel: ctx.Channel,
+                users: ctx.Author
+            )
+            .WithOverride("%target%",
+                () => canMentionEveryone
+                    ? ctx.Content[substringIndex..].Trim()
+                    : ctx.Content[substringIndex..].Trim().SanitizeMentions(true));
+
+        var text = SmartText.CreateFrom(cr.Response);
+        text = await repSvc.ReplaceAsync(text, repCtx);
+
+        return await sender.Response(channel).Text(text).Sanitize(false).SendAsync();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static WordPosition GetWordPosition(this ReadOnlySpan<char> str, in ReadOnlySpan<char> word)
+    {
+        var wordIndex = str.IndexOf(word, StringComparison.OrdinalIgnoreCase);
+        if (wordIndex == -1)
+            return WordPosition.None;
+
+        if (wordIndex == 0)
+        {
+            if (word.Length < str.Length && str.IsValidWordDivider(word.Length))
+                return WordPosition.Start;
+        }
+        else if (wordIndex + word.Length == str.Length)
+        {
+            if (str.IsValidWordDivider(wordIndex - 1))
+                return WordPosition.End;
+        }
+        else if (str.IsValidWordDivider(wordIndex - 1) && str.IsValidWordDivider(wordIndex + word.Length))
+            return WordPosition.Middle;
+
+        return WordPosition.None;
+    }
+
+    private static bool IsValidWordDivider(this in ReadOnlySpan<char> str, int index)
+    {
+        var ch = str[index];
+        if (ch is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '1' and <= '9')
+            return false;
+
+        return true;
+    }
+}
+
+public enum WordPosition
+{
+    None,
+    Start,
+    Middle,
+    End
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Expressions/EllieExpressions.cs b/src/EllieBot/Modules/Expressions/EllieExpressions.cs
new file mode 100644
index 0000000..5def4f3
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/EllieExpressions.cs
@@ -0,0 +1,447 @@
+#nullable disable
+using EllieBot.Db.Models;
+
+namespace EllieBot.Modules.EllieExpressions;
+
+[Name("Expressions")]
+public partial class EllieExpressions : EllieModule<EllieExpressionsService>
+{
+    public enum All
+    {
+        All
+    }
+
+    private readonly IBotCredentials _creds;
+    private readonly IHttpClientFactory _clientFactory;
+
+    public EllieExpressions(IBotCredentials creds, IHttpClientFactory clientFactory)
+    {
+        _creds = creds;
+        _clientFactory = clientFactory;
+    }
+
+    private bool AdminInGuildOrOwnerInDm()
+        => (ctx.Guild is null && _creds.IsOwner(ctx.User))
+           || (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
+
+    private async Task ExprAddInternalAsync(string key, string message)
+    {
+        if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
+        {
+            return;
+        }
+
+        var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
+
+        await Response()
+              .Embed(_sender.CreateEmbed()
+                            .WithOkColor()
+                            .WithTitle(GetText(strs.expr_new))
+                            .WithDescription($"#{new kwum(ex.Id)}")
+                            .AddField(GetText(strs.trigger), key)
+                            .AddField(GetText(strs.response),
+                                message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
+              .SendAsync();
+    }
+
+    [Cmd]
+    [UserPerm(GuildPerm.Administrator)]
+    public async Task ExprToggleGlobal()
+    {
+        var result = await _service.ToggleGlobalExpressionsAsync(ctx.Guild.Id);
+        if (result)
+            await Response().Confirm(strs.expr_global_disabled).SendAsync();
+        else
+            await Response().Confirm(strs.expr_global_enabled).SendAsync();
+    }
+
+    [Cmd]
+    [UserPerm(GuildPerm.Administrator)]
+    public async Task ExprAddServer(string key, [Leftover] string message)
+    {
+        if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
+        {
+            return;
+        }
+
+        await ExprAddInternalAsync(key, message);
+    }
+
+
+    [Cmd]
+    public async Task ExprAdd(string trigger, [Leftover] string response)
+    {
+        if (string.IsNullOrWhiteSpace(response) || string.IsNullOrWhiteSpace(trigger))
+        {
+            return;
+        }
+
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        await ExprAddInternalAsync(trigger, response);
+    }
+
+    [Cmd]
+    public async Task ExprEdit(kwum id, [Leftover] string message)
+    {
+        var channel = ctx.Channel as ITextChannel;
+        if (string.IsNullOrWhiteSpace(message) || id < 0)
+        {
+            return;
+        }
+
+        if (!IsValidExprEditor())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        var ex = await _service.EditAsync(ctx.Guild?.Id, id, message);
+        if (ex is not null)
+        {
+            await Response()
+                  .Embed(_sender.CreateEmbed()
+                                .WithOkColor()
+                                .WithTitle(GetText(strs.expr_edited))
+                                .WithDescription($"#{id}")
+                                .AddField(GetText(strs.trigger), ex.Trigger)
+                                .AddField(GetText(strs.response),
+                                    message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
+                  .SendAsync();
+        }
+        else
+        {
+            await Response().Error(strs.expr_no_found_id).SendAsync();
+        }
+    }
+
+    private bool IsValidExprEditor()
+        => (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator)
+           || (ctx.Guild is null && _creds.IsOwner(ctx.User));
+
+    [Cmd]
+    [Priority(1)]
+    public async Task ExprList(int page = 1)
+    {
+        if (--page < 0 || page > 999)
+        {
+            return;
+        }
+
+        var allExpressions = _service.GetExpressionsFor(ctx.Guild?.Id)
+                                     .OrderBy(x => x.Trigger)
+                                     .ToArray();
+
+        if (!allExpressions.Any())
+        {
+            await Response().Error(strs.expr_no_found).SendAsync();
+            return;
+        }
+
+        await Response()
+              .Paginated()
+              .Items(allExpressions)
+              .PageSize(20)
+              .CurrentPage(page)
+              .Page((exprs, _) =>
+              {
+                  var desc = exprs
+                             .Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "◾")}"
+                                           + $"{(ex.DmResponse ? "✉" : "◾")}"
+                                           + $"{(ex.AutoDeleteTrigger ? "❌" : "◾")}"
+                                           + $"`{(kwum)ex.Id}` {ex.Trigger}"
+                                           + (string.IsNullOrWhiteSpace(ex.Reactions)
+                                               ? string.Empty
+                                               : " // " + string.Join(" ", ex.GetReactions())))
+                             .Join('\n');
+
+                  return _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
+              })
+              .SendAsync();
+    }
+
+    [Cmd]
+    public async Task ExprShow(kwum id)
+    {
+        var found = _service.GetExpression(ctx.Guild?.Id, id);
+
+        if (found is null)
+        {
+            await Response().Error(strs.expr_no_found_id).SendAsync();
+            return;
+        }
+
+        var inter = CreateEditInteraction(id, found);
+
+        await Response()
+              .Interaction(IsValidExprEditor() ? inter : null)
+              .Embed(_sender.CreateEmbed()
+                            .WithOkColor()
+                            .WithDescription($"#{id}")
+                            .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
+                            .AddField(GetText(strs.response),
+                                found.Response.TrimTo(1000).Replace("](", "]\\(")))
+              .SendAsync();
+    }
+
+    private EllieInteractionBase CreateEditInteraction(kwum id, EllieExpression found)
+    {
+        var modal = new ModalBuilder()
+                    .WithCustomId("expr:edit_modal")
+                    .WithTitle($"Edit expression {id}")
+                    .AddTextInput(new TextInputBuilder()
+                                  .WithLabel(GetText(strs.response))
+                                  .WithValue(found.Response)
+                                  .WithMinLength(1)
+                                  .WithCustomId("expr:edit_modal:response")
+                                  .WithStyle(TextInputStyle.Paragraph));
+
+        var inter = _inter.Create(ctx.User.Id,
+            new ButtonBuilder()
+                .WithEmote(Emoji.Parse("📝"))
+                .WithLabel("Edit")
+                .WithStyle(ButtonStyle.Primary)
+                .WithCustomId("test"),
+            modal,
+            async (sm) =>
+            {
+                var msg = sm.Data.Components.FirstOrDefault()?.Value;
+
+                await ExprEdit(id, msg);
+            }
+        );
+        return inter;
+    }
+
+    public async Task ExprDeleteInternalAsync(kwum id)
+    {
+        var ex = await _service.DeleteAsync(ctx.Guild?.Id, id);
+
+        if (ex is not null)
+        {
+            await Response()
+                  .Embed(_sender.CreateEmbed()
+                                .WithOkColor()
+                                .WithTitle(GetText(strs.expr_deleted))
+                                .WithDescription($"#{id}")
+                                .AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
+                                .AddField(GetText(strs.response), ex.Response.TrimTo(1024)))
+                  .SendAsync();
+        }
+        else
+        {
+            await Response().Error(strs.expr_no_found_id).SendAsync();
+        }
+    }
+
+    [Cmd]
+    [UserPerm(GuildPerm.Administrator)]
+    [RequireContext(ContextType.Guild)]
+    public async Task ExprDeleteServer(kwum id)
+        => await ExprDeleteInternalAsync(id);
+
+    [Cmd]
+    public async Task ExprDelete(kwum id)
+    {
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        await ExprDeleteInternalAsync(id);
+    }
+
+    [Cmd]
+    public async Task ExprReact(kwum id, params string[] emojiStrs)
+    {
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        var ex = _service.GetExpression(ctx.Guild?.Id, id);
+        if (ex is null)
+        {
+            await Response().Error(strs.expr_no_found_id).SendAsync();
+            return;
+        }
+
+        if (emojiStrs.Length == 0)
+        {
+            await _service.ResetExprReactions(ctx.Guild?.Id, id);
+            await Response().Confirm(strs.expr_reset(Format.Bold(id.ToString()))).SendAsync();
+            return;
+        }
+
+        var succ = new List<string>();
+        foreach (var emojiStr in emojiStrs)
+        {
+            var emote = emojiStr.ToIEmote();
+
+            // i should try adding these emojis right away to the message, to make sure the bot can react with these emojis. If it fails, skip that emoji
+            try
+            {
+                await ctx.Message.AddReactionAsync(emote);
+                await Task.Delay(100);
+                succ.Add(emojiStr);
+
+                if (succ.Count >= 3)
+                {
+                    break;
+                }
+            }
+            catch { }
+        }
+
+        if (succ.Count == 0)
+        {
+            await Response().Error(strs.invalid_emojis).SendAsync();
+            return;
+        }
+
+        await _service.SetExprReactions(ctx.Guild?.Id, id, succ);
+
+
+        await Response()
+              .Confirm(strs.expr_set(Format.Bold(id.ToString()),
+                  succ.Select(static x => x.ToString()).Join(", ")))
+              .SendAsync();
+    }
+
+    [Cmd]
+    public Task ExprCa(kwum id)
+        => InternalExprEdit(id, ExprField.ContainsAnywhere);
+
+    [Cmd]
+    public Task ExprDm(kwum id)
+        => InternalExprEdit(id, ExprField.DmResponse);
+
+    [Cmd]
+    public Task ExprAd(kwum id)
+        => InternalExprEdit(id, ExprField.AutoDelete);
+
+    [Cmd]
+    public Task ExprAt(kwum id)
+        => InternalExprEdit(id, ExprField.AllowTarget);
+
+    [Cmd]
+    [OwnerOnly]
+    public async Task ExprsReload()
+    {
+        await _service.TriggerReloadExpressions();
+
+        await ctx.OkAsync();
+    }
+
+    private async Task InternalExprEdit(kwum id, ExprField option)
+    {
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        var (success, newVal) = await _service.ToggleExprOptionAsync(ctx.Guild?.Id, id, option);
+        if (!success)
+        {
+            await Response().Error(strs.expr_no_found_id).SendAsync();
+            return;
+        }
+
+        if (newVal)
+        {
+            await Response()
+                  .Confirm(strs.option_enabled(Format.Code(option.ToString()),
+                      Format.Code(id.ToString())))
+                  .SendAsync();
+        }
+        else
+        {
+            await Response()
+                  .Confirm(strs.option_disabled(Format.Code(option.ToString()),
+                      Format.Code(id.ToString())))
+                  .SendAsync();
+        }
+    }
+
+    [Cmd]
+    [RequireContext(ContextType.Guild)]
+    [UserPerm(GuildPerm.Administrator)]
+    public async Task ExprClear()
+    {
+        if (await PromptUserConfirmAsync(_sender.CreateEmbed()
+                                                .WithTitle("Expression clear")
+                                                .WithDescription("This will delete all expressions on this server.")))
+        {
+            var count = _service.DeleteAllExpressions(ctx.Guild.Id);
+            await Response().Confirm(strs.exprs_cleared(count)).SendAsync();
+        }
+    }
+
+    [Cmd]
+    public async Task ExprsExport()
+    {
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        _ = ctx.Channel.TriggerTypingAsync();
+
+        var serialized = _service.ExportExpressions(ctx.Guild?.Id);
+        await using var stream = await serialized.ToStream();
+        await ctx.Channel.SendFileAsync(stream, "exprs-export.yml");
+    }
+
+    [Cmd]
+#if GLOBAL_ELLIE
+    [OwnerOnly]
+#endif
+    public async Task ExprsImport([Leftover] string input = null)
+    {
+        if (!AdminInGuildOrOwnerInDm())
+        {
+            await Response().Error(strs.expr_insuff_perms).SendAsync();
+            return;
+        }
+
+        input = input?.Trim();
+
+        _ = ctx.Channel.TriggerTypingAsync();
+
+        if (input is null)
+        {
+            var attachment = ctx.Message.Attachments.FirstOrDefault();
+            if (attachment is null)
+            {
+                await Response().Error(strs.expr_import_no_input).SendAsync();
+                return;
+            }
+
+            using var client = _clientFactory.CreateClient();
+            input = await client.GetStringAsync(attachment.Url);
+
+            if (string.IsNullOrWhiteSpace(input))
+            {
+                await Response().Error(strs.expr_import_no_input).SendAsync();
+                return;
+            }
+        }
+
+        var succ = await _service.ImportExpressionsAsync(ctx.Guild?.Id, input);
+        if (!succ)
+        {
+            await Response().Error(strs.expr_import_invalid_data).SendAsync();
+            return;
+        }
+
+        await ctx.OkAsync();
+    }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs
new file mode 100644
index 0000000..2f0b740
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs
@@ -0,0 +1,801 @@
+#nullable disable
+using Microsoft.EntityFrameworkCore;
+using EllieBot.Common.ModuleBehaviors;
+using EllieBot.Common.Yml;
+using EllieBot.Db;
+using EllieBot.Db.Models;
+using System.Runtime.CompilerServices;
+using LinqToDB.EntityFrameworkCore;
+using EllieBot.Modules.Permissions.Services;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace EllieBot.Modules.EllieExpressions;
+
+public sealed class EllieExpressionsService : IExecOnMessage, IReadyExecutor
+{
+    private const string MENTION_PH = "%bot.mention%";
+
+    private const string PREPEND_EXPORT =
+        """
+        # Keys are triggers, Each key has a LIST of expressions in the following format:
+        # - res: Response string
+        #   id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.)
+        #   react:
+        #     - <List
+        #     -  of
+        #     - reactions>
+        #   at: Whether expression allows targets (see .h .exprat)
+        #   ca: Whether expression expects trigger anywhere (see .h .exprca)
+        #   dm: Whether expression DMs the response (see .h .exprdm)
+        #   ad: Whether expression automatically deletes triggering message (see .h .exprad)
+
+
+        """;
+
+    private static readonly ISerializer _exportSerializer = new SerializerBuilder()
+                                                            .WithEventEmitter(args
+                                                                => new MultilineScalarFlowStyleEmitter(args))
+                                                            .WithNamingConvention(CamelCaseNamingConvention.Instance)
+                                                            .WithIndentedSequences()
+                                                            .ConfigureDefaultValuesHandling(DefaultValuesHandling
+                                                                .OmitDefaults)
+                                                            .DisableAliases()
+                                                            .Build();
+
+    public int Priority
+        => 0;
+
+    private readonly object _gexprWriteLock = new();
+
+    private readonly TypedKey<EllieExpression> _gexprAddedKey = new("gexpr.added");
+    private readonly TypedKey<int> _gexprDeletedkey = new("gexpr.deleted");
+    private readonly TypedKey<EllieExpression> _gexprEditedKey = new("gexpr.edited");
+    private readonly TypedKey<bool> _exprsReloadedKey = new("exprs.reloaded");
+
+    // it is perfectly fine to have global expressions as an array
+    // 1. expressions are almost never added (compared to how many times they are being looped through)
+    // 2. only need write locks for this as we'll rebuild+replace the array on every edit
+    // 3. there's never many of them (at most a thousand, usually < 100)
+    private EllieExpression[] globalExpressions = Array.Empty<EllieExpression>();
+    private ConcurrentDictionary<ulong, EllieExpression[]> newguildExpressions = new();
+
+    private readonly DbService _db;
+
+    private readonly DiscordSocketClient _client;
+
+    // private readonly PermissionService _perms;
+    // private readonly GlobalPermissionService _gperm;
+    // private readonly CmdCdService _cmdCds;
+    private readonly IPermissionChecker _permChecker;
+    private readonly ICommandHandler _cmd;
+    private readonly IBotStrings _strings;
+    private readonly IBot _bot;
+    private readonly IPubSub _pubSub;
+    private readonly IMessageSenderService _sender;
+    private readonly IReplacementService _repSvc;
+    private readonly Random _rng;
+
+    private bool ready;
+    private ConcurrentHashSet<ulong> _disabledGlobalExpressionGuilds;
+    private readonly PermissionService _pc;
+
+    public EllieExpressionsService(
+        DbService db,
+        IBotStrings strings,
+        IBot bot,
+        DiscordSocketClient client,
+        ICommandHandler cmd,
+        IPubSub pubSub,
+        IMessageSenderService sender,
+        IReplacementService repSvc,
+        IPermissionChecker permChecker,
+        PermissionService pc)
+    {
+        _db = db;
+        _client = client;
+        _cmd = cmd;
+        _strings = strings;
+        _bot = bot;
+        _pubSub = pubSub;
+        _sender = sender;
+        _repSvc = repSvc;
+        _permChecker = permChecker;
+        _pc = pc;
+        _rng = new EllieRandom();
+
+        _pubSub.Sub(_exprsReloadedKey, OnExprsShouldReload);
+        pubSub.Sub(_gexprAddedKey, OnGexprAdded);
+        pubSub.Sub(_gexprDeletedkey, OnGexprDeleted);
+        pubSub.Sub(_gexprEditedKey, OnGexprEdited);
+
+        bot.JoinedGuild += OnJoinedGuild;
+        _client.LeftGuild += OnLeftGuild;
+    }
+
+    private async Task ReloadInternal(IReadOnlyList<ulong> allGuildIds)
+    {
+        await using var uow = _db.GetDbContext();
+        var guildItems = await uow.Set<EllieExpression>()
+                                  .AsNoTracking()
+                                  .Where(x => allGuildIds.Contains(x.GuildId.Value))
+                                  .ToListAsync();
+
+        newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value)
+                                        .ToDictionary(g => g.Key,
+                                            g => g.Select(x =>
+                                            {
+                                                x.Trigger = x.Trigger.Replace(MENTION_PH,
+                                                    _client.CurrentUser.Mention);
+                                                return x;
+                                            })
+                                                  .ToArray())
+                                        .ToConcurrent();
+
+        _disabledGlobalExpressionGuilds = new(await uow.Set<GuildConfig>()
+                                                       .Where(x => x.DisableGlobalExpressions)
+                                                       .Select(x => x.GuildId)
+                                                       .ToListAsyncLinqToDB());
+
+        lock (_gexprWriteLock)
+        {
+            var globalItems = uow.Set<EllieExpression>()
+                                 .AsNoTracking()
+                                 .Where(x => x.GuildId == null || x.GuildId == 0)
+                                 .Where(x => x.Trigger != null)
+                                 .AsEnumerable()
+                                 .Select(x =>
+                                 {
+                                     x.Trigger = x.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
+                                     return x;
+                                 })
+                                 .ToArray();
+
+            globalExpressions = globalItems;
+        }
+
+        ready = true;
+    }
+
+    private EllieExpression TryGetExpression(IUserMessage umsg)
+    {
+        if (!ready)
+            return null;
+
+        if (umsg.Channel is not SocketTextChannel channel)
+            return null;
+
+        var content = umsg.Content.Trim().ToLowerInvariant();
+
+        if (newguildExpressions.TryGetValue(channel.Guild.Id, out var expressions) && expressions.Length > 0)
+        {
+            var expr = MatchExpressions(content, expressions);
+            if (expr is not null)
+                return expr;
+        }
+
+        if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id))
+            return null;
+
+        var localGrs = globalExpressions;
+
+        return MatchExpressions(content, localGrs);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    private EllieExpression MatchExpressions(in ReadOnlySpan<char> content, EllieExpression[] exprs)
+    {
+        var result = new List<EllieExpression>(1);
+        for (var i = 0; i < exprs.Length; i++)
+        {
+            var expr = exprs[i];
+            var trigger = expr.Trigger;
+            if (content.Length > trigger.Length)
+            {
+                // if input is greater than the trigger, it can only work if:
+                // it has CA enabled
+                if (expr.ContainsAnywhere)
+                {
+                    // if ca is enabled, we have to check if it is a word within the content
+                    var wp = content.GetWordPosition(trigger);
+
+                    // if it is, then that's valid
+                    if (wp != WordPosition.None)
+                        result.Add(expr);
+
+                    // if it's not, then it cant' work under any circumstance,
+                    // because content is greater than the trigger length
+                    // so it can't be equal, and it's not contained as a word
+                    continue;
+                }
+
+                // if CA is disabled, and expr has AllowTarget, then the
+                // content has to start with the trigger followed by a space
+                if (expr.AllowTarget
+                    && content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
+                    && content[trigger.Length] == ' ')
+                    result.Add(expr);
+            }
+            else if (content.Length < expr.Trigger.Length)
+            {
+                // if input length is less than trigger length, it means
+                // that the reaction can never be triggered
+            }
+            else
+            {
+                // if input length is the same as trigger length
+                // reaction can only trigger if the strings are equal
+                if (content.SequenceEqual(expr.Trigger))
+                    result.Add(expr);
+            }
+        }
+
+        if (result.Count == 0)
+            return null;
+
+        var cancelled = result.FirstOrDefault(x => x.Response == "-");
+        if (cancelled is not null)
+            return cancelled;
+
+        return result[_rng.Next(0, result.Count)];
+    }
+
+    public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage msg)
+    {
+        // maybe this message is an expression
+        var expr = TryGetExpression(msg);
+
+        if (expr is null || expr.Response == "-")
+            return false;
+
+        try
+        {
+            if (guild is SocketGuild sg)
+            {
+                var result = await _permChecker.CheckPermsAsync(
+                    guild,
+                    msg.Channel,
+                    msg.Author,
+                    "ACTUALEXPRESSIONS",
+                    expr.Trigger
+                );
+
+                if (!result.IsAllowed)
+                {
+                    var cache = _pc.GetCacheFor(guild.Id);
+                    if (cache.Verbose)
+                    {
+                        if (result.TryPickT3(out var disallowed, out _))
+                        {
+                            var permissionMessage = _strings.GetText(strs.perm_prevent(disallowed.PermIndex + 1,
+                                    Format.Bold(disallowed.PermText)),
+                                sg.Id);
+
+                            try
+                            {
+                                await _sender.Response(msg.Channel)
+                                             .Error(permissionMessage)
+                                             .SendAsync();
+                            }
+                            catch
+                            {
+                            }
+
+                            Log.Information("{PermissionMessage}", permissionMessage);
+                        }
+                    }
+
+                    return true;
+                }
+            }
+
+            var sentMsg = await expr.Send(msg, _repSvc, _client, _sender);
+
+            var reactions = expr.GetReactions();
+            foreach (var reaction in reactions)
+            {
+                try
+                {
+                    await sentMsg.AddReactionAsync(reaction.ToIEmote());
+                }
+                catch
+                {
+                    Log.Warning("Unable to add reactions to message {Message} in server {GuildId}",
+                        sentMsg.Id,
+                        expr.GuildId);
+                    break;
+                }
+
+                await Task.Delay(1000);
+            }
+
+            if (expr.AutoDeleteTrigger)
+            {
+                try
+                {
+                    await msg.DeleteAsync();
+                }
+                catch
+                {
+                }
+            }
+
+            Log.Information("s: {GuildId} c: {ChannelId} u: {UserId} | {UserName} executed expression {Expr}",
+                guild.Id,
+                msg.Channel.Id,
+                msg.Author.Id,
+                msg.Author.ToString(),
+                expr.Trigger);
+
+            return true;
+        }
+        catch (Exception ex)
+        {
+            Log.Warning(ex, "Error in Expression RunBehavior: {ErrorMessage}", ex.Message);
+        }
+
+        return false;
+    }
+
+    public async Task ResetExprReactions(ulong? maybeGuildId, int id)
+    {
+        EllieExpression expr;
+        await using var uow = _db.GetDbContext();
+        expr = uow.Set<EllieExpression>().GetById(id);
+        if (expr is null)
+            return;
+
+        expr.Reactions = string.Empty;
+
+        await uow.SaveChangesAsync();
+    }
+
+    private Task UpdateInternalAsync(ulong? maybeGuildId, EllieExpression expr)
+    {
+        if (maybeGuildId is { } guildId)
+            UpdateInternal(guildId, expr);
+        else
+            return _pubSub.Pub(_gexprEditedKey, expr);
+
+        return Task.CompletedTask;
+    }
+
+    private void UpdateInternal(ulong? maybeGuildId, EllieExpression expr)
+    {
+        if (maybeGuildId is { } guildId)
+        {
+            newguildExpressions.AddOrUpdate(guildId,
+                [expr],
+                (_, old) =>
+                {
+                    var newArray = old.ToArray();
+                    for (var i = 0; i < newArray.Length; i++)
+                    {
+                        if (newArray[i].Id == expr.Id)
+                            newArray[i] = expr;
+                    }
+
+                    return newArray;
+                });
+        }
+        else
+        {
+            lock (_gexprWriteLock)
+            {
+                var exprs = globalExpressions;
+                for (var i = 0; i < exprs.Length; i++)
+                {
+                    if (exprs[i].Id == expr.Id)
+                        exprs[i] = expr;
+                }
+            }
+        }
+    }
+
+    private Task AddInternalAsync(ulong? maybeGuildId, EllieExpression expr)
+    {
+        // only do this for perf purposes
+        expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
+
+        if (maybeGuildId is { } guildId)
+            newguildExpressions.AddOrUpdate(guildId, [expr], (_, old) => old.With(expr));
+        else
+            return _pubSub.Pub(_gexprAddedKey, expr);
+
+        return Task.CompletedTask;
+    }
+
+    private Task DeleteInternalAsync(ulong? maybeGuildId, int id)
+    {
+        if (maybeGuildId is { } guildId)
+        {
+            newguildExpressions.AddOrUpdate(guildId,
+                Array.Empty<EllieExpression>(),
+                (key, old) => DeleteInternal(old, id, out _));
+
+            return Task.CompletedTask;
+        }
+
+        lock (_gexprWriteLock)
+        {
+            var expr = Array.Find(globalExpressions, item => item.Id == id);
+            if (expr is not null)
+                return _pubSub.Pub(_gexprDeletedkey, expr.Id);
+        }
+
+        return Task.CompletedTask;
+    }
+
+    private EllieExpression[] DeleteInternal(
+        IReadOnlyList<EllieExpression> exprs,
+        int id,
+        out EllieExpression deleted)
+    {
+        deleted = null;
+        if (exprs is null || exprs.Count == 0)
+            return exprs as EllieExpression[] ?? exprs?.ToArray();
+
+        var newExprs = new EllieExpression[exprs.Count - 1];
+        for (int i = 0, k = 0; i < exprs.Count; i++, k++)
+        {
+            if (exprs[i].Id == id)
+            {
+                deleted = exprs[i];
+                k--;
+                continue;
+            }
+
+            newExprs[k] = exprs[i];
+        }
+
+        return newExprs;
+    }
+
+    public async Task SetExprReactions(ulong? guildId, int id, IEnumerable<string> emojis)
+    {
+        EllieExpression expr;
+        await using (var uow = _db.GetDbContext())
+        {
+            expr = uow.Set<EllieExpression>().GetById(id);
+            if (expr is null)
+                return;
+
+            expr.Reactions = string.Join("@@@", emojis);
+
+            await uow.SaveChangesAsync();
+        }
+
+        await UpdateInternalAsync(guildId, expr);
+    }
+
+    public async Task<(bool Sucess, bool NewValue)> ToggleExprOptionAsync(ulong? guildId, int id, ExprField field)
+    {
+        var newVal = false;
+        EllieExpression expr;
+        await using (var uow = _db.GetDbContext())
+        {
+            expr = uow.Set<EllieExpression>().GetById(id);
+
+            if (expr is null || expr.GuildId != guildId)
+                return (false, false);
+            if (field == ExprField.AutoDelete)
+                newVal = expr.AutoDeleteTrigger = !expr.AutoDeleteTrigger;
+            else if (field == ExprField.ContainsAnywhere)
+                newVal = expr.ContainsAnywhere = !expr.ContainsAnywhere;
+            else if (field == ExprField.DmResponse)
+                newVal = expr.DmResponse = !expr.DmResponse;
+            else if (field == ExprField.AllowTarget)
+                newVal = expr.AllowTarget = !expr.AllowTarget;
+
+            await uow.SaveChangesAsync();
+        }
+
+        await UpdateInternalAsync(guildId, expr);
+
+        return (true, newVal);
+    }
+
+    public EllieExpression GetExpression(ulong? guildId, int id)
+    {
+        using var uow = _db.GetDbContext();
+        var expr = uow.Set<EllieExpression>().GetById(id);
+        if (expr is null || expr.GuildId != guildId)
+            return null;
+
+        return expr;
+    }
+
+    public int DeleteAllExpressions(ulong guildId)
+    {
+        using var uow = _db.GetDbContext();
+        var count = uow.Set<EllieExpression>().ClearFromGuild(guildId);
+        uow.SaveChanges();
+
+        newguildExpressions.TryRemove(guildId, out _);
+
+        return count;
+    }
+
+    public bool ExpressionExists(ulong? guildId, string input)
+    {
+        input = input.ToLowerInvariant();
+
+        var gexprs = globalExpressions;
+        foreach (var t in gexprs)
+        {
+            if (t.Trigger == input)
+                return true;
+        }
+
+        if (guildId is ulong gid && newguildExpressions.TryGetValue(gid, out var guildExprs))
+        {
+            foreach (var t in guildExprs)
+            {
+                if (t.Trigger == input)
+                    return true;
+            }
+        }
+
+        return false;
+    }
+
+    public string ExportExpressions(ulong? guildId)
+    {
+        var exprs = GetExpressionsFor(guildId);
+
+        var exprsDict = exprs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
+
+        return PREPEND_EXPORT + _exportSerializer.Serialize(exprsDict).UnescapeUnicodeCodePoints();
+    }
+
+    public async Task<bool> ImportExpressionsAsync(ulong? guildId, string input)
+    {
+        Dictionary<string, List<ExportedExpr>> data;
+        try
+        {
+            data = Yaml.Deserializer.Deserialize<Dictionary<string, List<ExportedExpr>>>(input);
+            if (data.Sum(x => x.Value.Count) == 0)
+                return false;
+        }
+        catch
+        {
+            return false;
+        }
+
+        await using var uow = _db.GetDbContext();
+        foreach (var entry in data)
+        {
+            var trigger = entry.Key;
+            await uow.Set<EllieExpression>()
+                     .AddRangeAsync(entry.Value
+                                         .Where(expr => !string.IsNullOrWhiteSpace(expr.Res))
+                                         .Select(expr => new EllieExpression
+                                         {
+                                             GuildId = guildId,
+                                             Response = expr.Res,
+                                             Reactions = expr.React?.Join("@@@"),
+                                             Trigger = trigger,
+                                             AllowTarget = expr.At,
+                                             ContainsAnywhere = expr.Ca,
+                                             DmResponse = expr.Dm,
+                                             AutoDeleteTrigger = expr.Ad
+                                         }));
+        }
+
+        await uow.SaveChangesAsync();
+        await TriggerReloadExpressions();
+        return true;
+    }
+
+    #region Event Handlers
+
+    public async Task OnReadyAsync()
+        => await OnExprsShouldReload(true);
+
+    private ValueTask OnExprsShouldReload(bool _)
+        => new(ReloadInternal(_bot.GetCurrentGuildIds()));
+
+    private ValueTask OnGexprAdded(EllieExpression c)
+    {
+        lock (_gexprWriteLock)
+        {
+            var newGlobalReactions = new EllieExpression[globalExpressions.Length + 1];
+            Array.Copy(globalExpressions, newGlobalReactions, globalExpressions.Length);
+            newGlobalReactions[globalExpressions.Length] = c;
+            globalExpressions = newGlobalReactions;
+        }
+
+        return default;
+    }
+
+    private ValueTask OnGexprEdited(EllieExpression c)
+    {
+        lock (_gexprWriteLock)
+        {
+            for (var i = 0; i < globalExpressions.Length; i++)
+            {
+                if (globalExpressions[i].Id == c.Id)
+                {
+                    globalExpressions[i] = c;
+                    return default;
+                }
+            }
+
+            // if edited expr is not found?!
+            // add it
+            OnGexprAdded(c);
+        }
+
+        return default;
+    }
+
+    private ValueTask OnGexprDeleted(int id)
+    {
+        lock (_gexprWriteLock)
+        {
+            var newGlobalReactions = DeleteInternal(globalExpressions, id, out _);
+            globalExpressions = newGlobalReactions;
+        }
+
+        return default;
+    }
+
+    public Task TriggerReloadExpressions()
+        => _pubSub.Pub(_exprsReloadedKey, true);
+
+    #endregion
+
+    #region Client Event Handlers
+
+    private Task OnLeftGuild(SocketGuild arg)
+    {
+        newguildExpressions.TryRemove(arg.Id, out _);
+
+        return Task.CompletedTask;
+    }
+
+    private async Task OnJoinedGuild(GuildConfig gc)
+    {
+        await using var uow = _db.GetDbContext();
+        var exprs = await uow.Set<EllieExpression>().AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
+
+        newguildExpressions[gc.GuildId] = exprs;
+    }
+
+    #endregion
+
+    #region Basic Operations
+
+    public async Task<EllieExpression> AddAsync(
+        ulong? guildId,
+        string key,
+        string message,
+        bool ca = false,
+        bool ad = false,
+        bool dm = false)
+    {
+        key = key.ToLowerInvariant();
+        var expr = new EllieExpression
+        {
+            GuildId = guildId,
+            Trigger = key,
+            Response = message,
+            ContainsAnywhere = ca,
+            AutoDeleteTrigger = ad,
+            DmResponse = dm
+        };
+
+        if (expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
+            expr.AllowTarget = true;
+
+        await using (var uow = _db.GetDbContext())
+        {
+            uow.Set<EllieExpression>().Add(expr);
+            await uow.SaveChangesAsync();
+        }
+
+        await AddInternalAsync(guildId, expr);
+
+        return expr;
+    }
+
+    public async Task<EllieExpression> EditAsync(
+        ulong? guildId,
+        int id,
+        string message,
+        bool? ca = null,
+        bool? ad = null,
+        bool? dm = null)
+    {
+        await using var uow = _db.GetDbContext();
+        var expr = uow.Set<EllieExpression>().GetById(id);
+
+        if (expr is null || expr.GuildId != guildId)
+            return null;
+
+        // disable allowtarget if message had target, but it was removed from it
+        if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase)
+            && expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
+            expr.AllowTarget = false;
+
+        expr.Response = message;
+
+        // enable allow target if message is edited to contain target
+        if (expr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
+            expr.AllowTarget = true;
+
+        expr.ContainsAnywhere = ca ?? expr.ContainsAnywhere;
+        expr.AutoDeleteTrigger = ad ?? expr.AutoDeleteTrigger;
+        expr.DmResponse = dm ?? expr.DmResponse;
+
+        await uow.SaveChangesAsync();
+        await UpdateInternalAsync(guildId, expr);
+
+        return expr;
+    }
+
+
+    public async Task<EllieExpression> DeleteAsync(ulong? guildId, int id)
+    {
+        await using var uow = _db.GetDbContext();
+        var toDelete = uow.Set<EllieExpression>().GetById(id);
+
+        if (toDelete is null)
+            return null;
+
+        if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
+        {
+            uow.Set<EllieExpression>().Remove(toDelete);
+            await uow.SaveChangesAsync();
+            await DeleteInternalAsync(guildId, id);
+            return toDelete;
+        }
+
+        return null;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public EllieExpression[] GetExpressionsFor(ulong? maybeGuildId)
+    {
+        if (maybeGuildId is { } guildId)
+            return newguildExpressions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<EllieExpression>();
+
+        return globalExpressions;
+    }
+
+    #endregion
+
+    public async Task<bool> ToggleGlobalExpressionsAsync(ulong guildId)
+    {
+        await using var ctx = _db.GetDbContext();
+        var gc = ctx.GuildConfigsForId(guildId, set => set);
+        var toReturn = gc.DisableGlobalExpressions = !gc.DisableGlobalExpressions;
+        await ctx.SaveChangesAsync();
+
+        if (toReturn)
+            _disabledGlobalExpressionGuilds.Add(guildId);
+        else
+            _disabledGlobalExpressionGuilds.TryRemove(guildId);
+
+        return toReturn;
+    }
+
+
+    public async Task<(IReadOnlyCollection<EllieExpression> Exprs, int TotalCount)> FindExpressionsAsync(
+        ulong guildId,
+        string query,
+        int page)
+    {
+        await using var ctx = _db.GetDbContext();
+
+        if (newguildExpressions.TryGetValue(guildId, out var exprs))
+        {
+            return (exprs.Where(x => x.Trigger.Contains(query))
+                         .Skip(page * 9)
+                         .Take(9)
+                         .ToArray(), exprs.Length);
+        }
+
+        return ([], 0);
+    }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Expressions/ExportedExpr.cs b/src/EllieBot/Modules/Expressions/ExportedExpr.cs
new file mode 100644
index 0000000..c45fbdc
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/ExportedExpr.cs
@@ -0,0 +1,27 @@
+#nullable disable
+using EllieBot.Db.Models;
+
+namespace EllieBot.Modules.EllieExpressions;
+
+public class ExportedExpr
+{
+    public string Res { get; set; }
+    public string Id { get; set; }
+    public bool Ad { get; set; }
+    public bool Dm { get; set; }
+    public bool At { get; set; }
+    public bool Ca { get; set; }
+    public string[] React;
+
+    public static ExportedExpr FromModel(EllieExpression cr)
+        => new()
+        {
+            Res = cr.Response,
+            Id = ((kwum)cr.Id).ToString(),
+            Ad = cr.AutoDeleteTrigger,
+            At = cr.AllowTarget,
+            Ca = cr.ContainsAnywhere,
+            Dm = cr.DmResponse,
+            React = string.IsNullOrWhiteSpace(cr.Reactions) ? null : cr.GetReactions()
+        };
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Expressions/ExprField.cs b/src/EllieBot/Modules/Expressions/ExprField.cs
new file mode 100644
index 0000000..9b9fa2f
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/ExprField.cs
@@ -0,0 +1,10 @@
+namespace EllieBot.Modules.EllieExpressions;
+
+public enum ExprField
+{
+    AutoDelete,
+    DmResponse,
+    AllowTarget,
+    ContainsAnywhere,
+    Message
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Expressions/TypeReaders/CommandOrExprTypeReader.cs b/src/EllieBot/Modules/Expressions/TypeReaders/CommandOrExprTypeReader.cs
new file mode 100644
index 0000000..716735e
--- /dev/null
+++ b/src/EllieBot/Modules/Expressions/TypeReaders/CommandOrExprTypeReader.cs
@@ -0,0 +1,33 @@
+#nullable disable
+using EllieBot.Modules.EllieExpressions;
+
+namespace EllieBot.Common.TypeReaders;
+
+public sealed class CommandOrExprTypeReader : EllieTypeReader<CommandOrExprInfo>
+{
+    private readonly CommandService _cmds;
+    private readonly ICommandHandler _commandHandler;
+    private readonly EllieExpressionsService _exprs;
+
+    public CommandOrExprTypeReader(CommandService cmds, EllieExpressionsService exprs, ICommandHandler commandHandler)
+    {
+        _cmds = cmds;
+        _exprs = exprs;
+        _commandHandler = commandHandler;
+    }
+
+    public override async ValueTask<TypeReaderResult<CommandOrExprInfo>> ReadAsync(ICommandContext ctx, string input)
+    {
+        if (_exprs.ExpressionExists(ctx.Guild?.Id, input))
+            return TypeReaderResult.FromSuccess(new CommandOrExprInfo(input, CommandOrExprInfo.Type.Custom));
+
+        var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(ctx, input);
+        if (cmd.IsSuccess)
+        {
+            return TypeReaderResult.FromSuccess(new CommandOrExprInfo(((CommandInfo)cmd.Values.First().Value).Name,
+                CommandOrExprInfo.Type.Normal));
+        }
+
+        return TypeReaderResult.FromError<CommandOrExprInfo>(CommandError.ParseFailed, "No such command or expression found.");
+    }
+}
\ No newline at end of file