diff --git a/pom.xml b/pom.xml
index 7e6678146f3d960d0c4787f3d1fda7e64369c019..d0c08f8b32382bdba429e8c7ccfb78c4e8c7b9ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>net.dappergoose</groupId>
     <artifactId>SimpleChatFilter</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <name>SimpleChatFilter</name>
@@ -24,8 +24,8 @@
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.8.1</version>
                 <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
+                    <source>16</source>
+                    <target>16</target>
                 </configuration>
             </plugin>
             <plugin>
@@ -71,5 +71,17 @@
             <version>1.19-R0.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.velocitypowered</groupId>
+            <artifactId>velocity-api</artifactId>
+            <version>3.3.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>de.exlll</groupId>
+            <artifactId>configlib-velocity</artifactId>
+            <version>4.5.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterBungee.java
similarity index 98%
rename from src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java
rename to src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterBungee.java
index 74ce92332518721bec0d5d52a0e135b84a0ce82a..73137d80371332baf93db64782f5b21d844c76c6 100644
--- a/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java
+++ b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterBungee.java
@@ -17,7 +17,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
-public final class SimpleChatFilter extends Plugin implements Listener {
+public final class SimpleChatFilterBungee extends Plugin implements Listener {
 
     public void makeConfig() throws IOException {
         // Create plugin config folder if it doesn't exist
diff --git a/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterVelocity.java b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterVelocity.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0a5cc49341124b4dc964d6b835fdd395eae9ee0
--- /dev/null
+++ b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilterVelocity.java
@@ -0,0 +1,138 @@
+package net.dappergoose.simplechatfilter;
+
+import com.google.inject.Inject;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.player.PlayerChatEvent;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ProxyServer;
+import de.exlll.configlib.Comment;
+import de.exlll.configlib.Configuration;
+import de.exlll.configlib.YamlConfigurations;
+import net.kyori.adventure.text.Component;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+public class SimpleChatFilterVelocity {
+
+    private final ProxyServer server;
+    private final Logger logger;
+    private final Path dataDirectory;
+
+    private BaseConfiguration config;
+
+    @Configuration
+    public static class BaseConfiguration {
+        @Comment("Categories of blocked words and phrases")
+        Map<String, Category> categories = Map.of(
+                "advertising-pfcloud",
+                new Category(List.of(
+                        "a friendly bot designed to join servers",
+                        "If you are searching for a good server hosting provider",
+                        "https://shop.pfcloud.io"),
+                        List.of(
+                                "ipban {username} Bot/Advertising -s"),
+                        "&cPlease do not advertise!"
+                )
+        );
+        @Comment("Message sent to staff when a player is blocked")
+        String staffMessage = "&c{displayname} &7tried to say &c{message} &7in &c{server} &7but was blocked for &c{category}";
+    }
+
+    public record Category(List<String> blocked, List<String> commands, String message) {}
+
+    @Inject
+    public SimpleChatFilterVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
+        this.server = server;
+        this.logger = logger;
+        this.dataDirectory = dataDirectory;
+        this.config = new BaseConfiguration();
+    }
+
+    public void makeConfig() throws IOException {
+        // Create plugin config folder if it doesn't exist
+        File dataPath = dataDirectory.toFile();
+        if (!dataPath.exists()) {
+            logger.info("Created config folder: " + dataPath.mkdir());
+        }
+
+        File configFile = new File(dataPath, "config.yml");
+
+        // Copy default config if it doesn't exist
+        if (!configFile.exists()) {
+            FileOutputStream outputStream = new FileOutputStream(configFile); // Throws IOException
+            InputStream in = this.getClass().getClassLoader().getResourceAsStream("config.yml"); // This file must exist in the jar resources folder
+            in.transferTo(outputStream); // Throws IOException
+        }
+    }
+
+    @Subscribe
+    public void onProxyInitialization(ProxyInitializeEvent event) {
+        try {
+            makeConfig();
+            Path configPath = dataDirectory.resolve("config.yml");
+            this.config = YamlConfigurations.load(configPath, BaseConfiguration.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Subscribe
+    public void onPlayerChat(PlayerChatEvent e) {
+        Player p = e.getPlayer();
+
+        String message = e.getMessage();
+        boolean violated = false;
+        String matchedCategory = null;
+        for(String categoryKey : config.categories.keySet()) {
+            Category category = config.categories.get(categoryKey);
+            if(p.hasPermission("chatfilter.bypass." + category)) continue;
+            for(String word : category.blocked) {
+                if(message.contains(word)) {
+                    violated = true;
+                    matchedCategory = categoryKey;
+                    break;
+                }
+            }
+            if(violated) break;
+        }
+
+        if(violated) {
+            e.setResult(PlayerChatEvent.ChatResult.denied());
+            AtomicReference<String> serverName = new AtomicReference<>("unknown");
+            p.getCurrentServer().ifPresent(s -> serverName.set(s.getServerInfo().getName()));
+            sendStaffMessage(
+                    Component.text(config.staffMessage
+                        .replace("{displayname}", p.getUsername())
+                        .replace("{category}", matchedCategory)
+                        .replace("{message}", message)
+                        .replace("{server}", serverName.get())
+                    )
+            );
+
+            p.sendMessage(Component.text(
+                    config.categories.get(matchedCategory).message
+            ));
+            for(String command : config.categories.get(matchedCategory).commands) {
+                server.getCommandManager().executeAsync(server.getConsoleCommandSource(), command.replace("{username}", p.getUsername()));
+            }
+        }
+    }
+
+    private void sendStaffMessage(Component component) {
+        for(Player p : server.getAllPlayers()) {
+            if(p.hasPermission("chatfilter.staff")) {
+                p.sendMessage(component);
+            }
+        }
+    }
+}
diff --git a/src/main/resources/bungee.yml b/src/main/resources/bungee.yml
index cf15c94abbedc316d550ca2ffced483d19d1b447..bc862af8845fa3dda336b710da72ce9aadfebfa6 100644
--- a/src/main/resources/bungee.yml
+++ b/src/main/resources/bungee.yml
@@ -1,3 +1,3 @@
 name: SimpleChatFilter
 version: '${project.version}'
-main: net.dappergoose.simplechatfilter.SimpleChatFilter
+main: net.dappergoose.simplechatfilter.SimpleChatFilterBungee
diff --git a/src/main/resources/velocity-plugin.json b/src/main/resources/velocity-plugin.json
new file mode 100644
index 0000000000000000000000000000000000000000..c06f4080ae89a92bf67dd1f54c8f02f08fd5895f
--- /dev/null
+++ b/src/main/resources/velocity-plugin.json
@@ -0,0 +1,9 @@
+{
+  "id":"simplechatfilter",
+  "name":"SimpleChatFilter",
+  "version":"${project.version}",
+  "url":"https://git.rb9.xyz/dappergoose/simplechatfilter",
+  "authors":["Radialbog9"],
+  "dependencies":[],
+  "main":"net.dappergoose.simplechatfilter.SimpleChatFilterVelocity"
+}
\ No newline at end of file