diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java
index 0ff2a0123..339c5a9e9 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricMod.java
@@ -67,9 +67,14 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
 
     private GeyserConnector connector;
     private Path dataFolder;
-    private List<String> playerCommands;
     private MinecraftServer server;
 
+    /**
+     * Commands that don't require any permission level to ran
+     */
+    private List<String> playerCommands;
+    private final List<GeyserFabricCommandExecutor> commandExecutors = new ArrayList<>();
+
     private GeyserFabricCommandManager geyserCommandManager;
     private GeyserFabricConfiguration geyserConfig;
     private GeyserFabricLogger geyserLogger;
@@ -167,14 +172,17 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
 
         // Start command building
         // Set just "geyser" as the help command
-        LiteralArgumentBuilder<ServerCommandSource> builder = net.minecraft.server.command.CommandManager.literal("geyser")
-                .executes(new GeyserFabricCommandExecutor(connector, connector.getCommandManager().getCommands().get("help"),
-                        !playerCommands.contains("help")));
+        GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(connector,
+                connector.getCommandManager().getCommands().get("help"), !playerCommands.contains("help"));
+        commandExecutors.add(helpExecutor);
+        LiteralArgumentBuilder<ServerCommandSource> builder = net.minecraft.server.command.CommandManager.literal("geyser").executes(helpExecutor);
+
+        // Register all subcommands as valid
         for (Map.Entry<String, GeyserCommand> command : connector.getCommandManager().getCommands().entrySet()) {
-            // Register all subcommands as valid
-            builder.then(net.minecraft.server.command.CommandManager.literal(
-                    command.getKey()).executes(new GeyserFabricCommandExecutor(connector, command.getValue(),
-                    !playerCommands.contains(command.getKey()))));
+            GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(connector, command.getValue(),
+                    !playerCommands.contains(command.getKey()));
+            commandExecutors.add(executor);
+            builder.then(net.minecraft.server.command.CommandManager.literal(command.getKey()).executes(executor));
         }
         server.getCommandManager().getDispatcher().register(builder);
     }
@@ -258,6 +266,10 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
         return file;
     }
 
+    public List<GeyserFabricCommandExecutor> getCommandExecutors() {
+        return commandExecutors;
+    }
+
     public static GeyserFabricMod getInstance() {
         return instance;
     }
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java
index 1a446bca6..fc08b052a 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/GeyserFabricPermissions.java
@@ -25,6 +25,7 @@
 
 package org.geysermc.platform.fabric;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
@@ -34,6 +35,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class GeyserFabricPermissions {
 
+    /**
+     * The minimum permission level a command source must have in order for it to run commands that are restricted
+     */
+    @JsonIgnore
+    public static final int RESTRICTED_MIN_LEVEL = 2;
+
     @JsonProperty("commands")
     private String[] commands;
 
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java
index d49a07625..1dbec0492 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java
@@ -31,6 +31,7 @@ import net.minecraft.text.LiteralText;
 import org.geysermc.connector.GeyserConnector;
 import org.geysermc.connector.command.CommandSender;
 import org.geysermc.connector.common.ChatColor;
+import org.geysermc.platform.fabric.GeyserFabricMod;
 
 public class FabricCommandSender implements CommandSender {
 
@@ -58,4 +59,18 @@ public class FabricCommandSender implements CommandSender {
     public boolean isConsole() {
         return !(source.getEntity() instanceof ServerPlayerEntity);
     }
+
+    @Override
+    public boolean hasPermission(String s) {
+        // Mostly copied from fabric's world manager since the method there takes a GeyserSession
+
+        // Workaround for our commands because fabric doesn't have native permissions
+        for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
+            if (executor.getCommand().getPermission().equals(s)) {
+                return executor.canRun(source);
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java
index a8469364f..bd2cdcad2 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java
@@ -35,6 +35,7 @@ import org.geysermc.connector.common.ChatColor;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.utils.LanguageUtils;
 import org.geysermc.platform.fabric.GeyserFabricMod;
+import org.geysermc.platform.fabric.GeyserFabricPermissions;
 
 public class GeyserFabricCommandExecutor extends CommandExecutor implements Command<ServerCommandSource> {
 
@@ -50,11 +51,22 @@ public class GeyserFabricCommandExecutor extends CommandExecutor implements Comm
         this.requiresPermission = requiresPermission;
     }
 
+    /**
+     * Determine whether or not a command source is allowed to run a given executor.
+     *
+     * @param source The command source attempting to run the command
+     * @return True if the command source is allowed to
+     */
+    public boolean canRun(ServerCommandSource source) {
+        return !requiresPermission() || source.hasPermissionLevel(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL);
+    }
+
     @Override
     public int run(CommandContext context) {
         ServerCommandSource source = (ServerCommandSource) context.getSource();
         FabricCommandSender sender = new FabricCommandSender(source);
-        if (requiresPermission && !source.hasPermissionLevel(2)) {
+        GeyserSession session = getGeyserSession(sender);
+        if (!canRun(source)) {
             sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"));
             return 0;
         }
@@ -62,15 +74,22 @@ public class GeyserFabricCommandExecutor extends CommandExecutor implements Comm
             GeyserFabricMod.getInstance().setReloading(true);
         }
 
-        GeyserSession session = null;
-        if (command.isBedrockOnly()) {
-            session = getGeyserSession(sender);
-            if (session == null) {
-                sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale()));
-                return 0;
-            }
+        if (command.isBedrockOnly() && session == null) {
+            sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale()));
+            return 0;
         }
         command.execute(session, sender, new String[0]);
         return 0;
     }
+
+    public GeyserCommand getCommand() {
+        return command;
+    }
+
+    /**
+     * Returns whether the command requires permission level of {@link GeyserFabricPermissions#RESTRICTED_MIN_LEVEL} or higher to be ran
+     */
+    public boolean requiresPermission() {
+        return requiresPermission;
+    }
 }
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java
index dd0d629c8..412266bb4 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java
@@ -42,6 +42,8 @@ import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
 import org.geysermc.connector.network.translators.world.GeyserWorldManager;
 import org.geysermc.connector.utils.BlockEntityUtils;
+import org.geysermc.platform.fabric.GeyserFabricMod;
+import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -63,7 +65,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
     public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
         Runnable lecternGet = () -> {
             // Mostly a reimplementation of Spigot lectern support
-            PlayerEntity player = server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid());
+            PlayerEntity player = getPlayer(session);
             if (player != null) {
                 BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z));
                 if (!(blockEntity instanceof LecternBlockEntity lectern)) {
@@ -119,4 +121,21 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
         }
         return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build();
     }
+
+    @Override
+    public boolean hasPermission(GeyserSession session, String permission) {
+
+        // Workaround for our commands because fabric doesn't have native permissions
+        for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
+            if (executor.getCommand().getPermission().equals(permission)) {
+                return executor.canRun(getPlayer(session).getCommandSource());
+            }
+        }
+
+        return false;
+    }
+
+    private PlayerEntity getPlayer(GeyserSession session) {
+        return server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid());
+    }
 }
diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml
index a51d3169a..8a995f8dc 100644
--- a/bootstrap/fabric/src/main/resources/permissions.yml
+++ b/bootstrap/fabric/src/main/resources/permissions.yml
@@ -1,10 +1,12 @@
 # Uncomment any commands that you wish to be run by clients
 # Commented commands require an OP permission of 2 or greater
 commands:
-#  - dump
   - help
+  - advancements
+  - statistics
+  - settings
   - offhand
 #  - list
 #  - reload
-#  - shutdown
 #  - version
+#  - dump
\ No newline at end of file