diff --git a/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java
index e5c361a20d4c945ccb3b80bb09fbfb3558c5243d..a44331738d543b4133aa939f21908a04db05a49d 100644
--- a/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java
+++ b/src/main/java/net/dappergoose/simplechatfilter/SimpleChatFilter.java
@@ -1,16 +1,112 @@
 package net.dappergoose.simplechatfilter;
 
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.TextComponent;
+import net.md_5.bungee.api.connection.ProxiedPlayer;
+import net.md_5.bungee.api.event.ChatEvent;
+import net.md_5.bungee.api.plugin.Listener;
 import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.config.Configuration;
+import net.md_5.bungee.config.ConfigurationProvider;
+import net.md_5.bungee.config.YamlConfiguration;
+import net.md_5.bungee.event.EventHandler;
 
-public final class SimpleChatFilter extends Plugin {
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public final class SimpleChatFilter extends Plugin implements Listener {
+
+    public void makeConfig() throws IOException {
+        // Create plugin config folder if it doesn't exist
+        if (!getDataFolder().exists()) {
+            getLogger().info("Created config folder: " + getDataFolder().mkdir());
+        }
+
+        File configFile = new File(getDataFolder(), "config.yml");
+
+        // Copy default config if it doesn't exist
+        if (!configFile.exists()) {
+            FileOutputStream outputStream = new FileOutputStream(configFile); // Throws IOException
+            InputStream in = getResourceAsStream("config.yml"); // This file must exist in the jar resources folder
+            in.transferTo(outputStream); // Throws IOException
+        }
+    }
+
+    private Configuration pluginConfig;
 
     @Override
     public void onEnable() {
-        // Plugin startup logic
+        try {
+            makeConfig();
+            pluginConfig =
+                    ConfigurationProvider.getProvider(YamlConfiguration.class)
+                            .load(new File(getDataFolder(), "config.yml"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        getProxy().getPluginManager().registerListener(this, this);
     }
 
     @Override
     public void onDisable() {
-        // Plugin shutdown logic
+        getProxy().getPluginManager().unregisterListener(this);
     }
+
+    @EventHandler
+    public void onChatEvent(ChatEvent e) {
+        if(!(e.getSender() instanceof ProxiedPlayer)) return;
+        ProxiedPlayer p = (ProxiedPlayer) e.getSender();
+
+        if (e.isCommand()) // TODO message commands
+            return; // ignore commands
+
+        String message = e.getMessage();
+        boolean violated = false;
+        String matchedCategory = null;
+        for(String category : pluginConfig.getSection("categories").getKeys()) {
+            if(p.hasPermission("chatfilter.bypass." + category)) continue;
+            for(String word : pluginConfig.getStringList("categories." + category + ".blocked")) {
+                if(message.contains(word)) {
+                    violated = true;
+                    matchedCategory = category;
+                    break;
+                }
+            }
+            if(violated) break;
+        }
+
+        if(violated) {
+            e.setCancelled(true);
+            sendStaffMessage(new TextComponent(
+                    ChatColor.translateAlternateColorCodes('&',
+                            pluginConfig.getString("staff-message")
+                                    .replace("{player}", p.getDisplayName())
+                                    .replace("{category}", matchedCategory)
+                                    .replace("{message}", message)
+                                    .replace("{server}", p.getServer().getInfo().getName())
+                    )
+            ));
+            p.sendMessage(new TextComponent(
+                    ChatColor.translateAlternateColorCodes('&',
+                            pluginConfig.getString("categories." + matchedCategory + ".message")
+                    )
+            ));
+            for(String command : pluginConfig.getStringList("categories." + matchedCategory + ".commands")) {
+                getProxy().getPluginManager().dispatchCommand(getProxy().getConsole(), command.replace("{player}", p.getName()));
+            }
+        }
+    }
+
+    private void sendStaffMessage(BaseComponent component) {
+        for(ProxiedPlayer p : getProxy().getPlayers()) {
+            if(p.hasPermission("chatfilter.staff")) {
+                p.sendMessage(component);
+            }
+        }
+    }
+
+
 }
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ba01025e7d78202b2e45864ef901606bc1979b9e
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,12 @@
+categories:
+  # A list of categories - !! most severe first !!
+  advertising-pfcloud:
+    blocked:
+      - a friendly bot designed to join servers
+      - If you are searching for a good server hosting provider
+      - https://shop.pfcloud.io
+    commands:
+      - ipban {username} Bot/Advertising -s
+    message: '&cPlease do not advertise!'
+
+staff-message: '&a[ChatFilter] &c{username} &7 broke rule &c{category} &7on server &c{server}&7.'
\ No newline at end of file