Skip to main content

Overview

PocketMine-MP uses a hierarchical permission system where permissions are represented as nodes (e.g., myplugin.command.teleport). Permissions can be granted to players, groups, or operators.

Permission Architecture

Permission Class

Permission.php:35
class Permission {
    private Translatable|string $description;
    
    public function __construct(
        private string $name,
        Translatable|string|null $description = null,
        private array $children = []
    ) {
        $this->description = $description ?? "";
        $this->recalculatePermissibles();
    }
    
    public function getName(): string {
        return $this->name;
    }
    
    public function getChildren(): array {
        return $this->children;
    }
    
    public function addChild(string $name, bool $value): void {
        $this->children[$name] = $value;
        $this->recalculatePermissibles();
    }
}

PermissionManager

Central registry for all permissions:
use pocketmine\permission\PermissionManager;

$permManager = PermissionManager::getInstance();

// Get a permission
$permission = $permManager->getPermission("myplugin.admin");

// Check if exists
if ($permManager->getPermission("some.permission") !== null) {
    // Permission is registered
}

// Get all permissions
$allPermissions = $permManager->getPermissions();

Permission Nodes

Naming Convention

Permissions use dot-notation hierarchy:
myplugin.command.teleport
│       │       └─ Specific action
│       └───────── Category
└───────────────── Plugin name

Examples

pocketmine.command.give       // Use /give command
pocketmine.command.gamemode   // Use /gamemode command
myplugin.admin                // Admin access
myplugin.command.heal         // Use /heal
myplugin.feature.fly          // Enable flying

Registering Permissions

Via plugin.yml

plugin.yml
name: MyPlugin
version: 1.0.0
main: MyNamespace\MyPlugin
api: 5.0.0

permissions:
  myplugin.admin:
    description: "Admin access to MyPlugin"
    default: op
    
  myplugin.command.heal:
    description: "Use /heal command"
    default: true
    
  myplugin.command.kill:
    description: "Use /kill command"
    default: op
    children:
      myplugin.command.kill.others:
        description: "Kill other players"
        default: false

Via Code

use pocketmine\permission\Permission;
use pocketmine\permission\PermissionManager;
use pocketmine\permission\DefaultPermissions;

class MyPlugin extends PluginBase {
    
    protected function onEnable(): void {
        $permManager = PermissionManager::getInstance();
        
        // Register single permission
        $permission = new Permission(
            "myplugin.admin",
            "Admin access to MyPlugin"
        );
        $permManager->addPermission($permission);
        
        // Register with children
        $parentPerm = new Permission(
            "myplugin.moderator",
            "Moderator access",
            [
                "myplugin.command.kick" => true,
                "myplugin.command.ban" => true,
                "myplugin.command.mute" => true
            ]
        );
        $permManager->addPermission($parentPerm);
    }
}

Default Permission Levels

Default Values

PermissionParser::DEFAULT_TRUE      // Everyone has permission
PermissionParser::DEFAULT_FALSE     // No one has permission by default
PermissionParser::DEFAULT_OP        // Only operators have permission
PermissionParser::DEFAULT_NOT_OP    // Only non-operators have permission

Setting Defaults

plugin.yml
permissions:
  myplugin.user:
    default: true        # Everyone
    
  myplugin.moderator:
    default: op          # Only operators
    
  myplugin.premium:
    default: false       # Explicitly granted only
    
  myplugin.nonop:
    default: not op      # Non-operators only

Checking Permissions

Player Permissions

use pocketmine\player\Player;
use pocketmine\command\CommandSender;

function handleCommand(Player $player): void {
    // Simple check
    if ($player->hasPermission("myplugin.admin")) {
        $player->sendMessage("You have admin access!");
    } else {
        $player->sendMessage("Access denied!");
        return;
    }
    
    // Check multiple permissions
    if ($player->hasPermission("myplugin.moderate") || $player->hasPermission("myplugin.admin")) {
        // Has moderator or admin
    }
}

Command Senders

use pocketmine\command\Command;
use pocketmine\command\CommandSender;

class MyCommand extends Command {
    
    public function execute(CommandSender $sender, string $label, array $args): void {
        if (!$sender->hasPermission("myplugin.command.mycommand")) {
            $sender->sendMessage("You don't have permission to use this command!");
            return;
        }
        
        // Execute command
    }
}

Console Always Has Permissions

use pocketmine\console\ConsoleCommandSender;

if ($sender instanceof ConsoleCommandSender) {
    // Console has all permissions
    $sender->sendMessage("Console has unlimited access");
}

// Or check explicitly
if ($sender->hasPermission("pocketmine.command.ban")) {
    // Both players with permission AND console pass this check
}
The console (ConsoleCommandSender) always has all permissions, regardless of configuration.

Permission Attachments

Temporarily grant permissions to players:
use pocketmine\permission\PermissionAttachment;

$player = $event->getPlayer();

// Create attachment
$attachment = $player->addAttachment($this); // $this = plugin instance

// Grant permissions
$attachment->setPermission("myplugin.fly", true);
$attachment->setPermission("myplugin.speed", true);

// Revoke permission
$attachment->unsetPermission("myplugin.fly");

// Remove entire attachment
$player->removeAttachment($attachment);

Temporary Permissions

class TempPermissionManager {
    
    private array $attachments = [];
    
    public function grantTempPermission(Player $player, string $permission, int $seconds): void {
        $attachment = $player->addAttachment($this->plugin);
        $attachment->setPermission($permission, true);
        
        $this->attachments[$player->getName()] = $attachment;
        
        // Schedule removal
        $this->plugin->getScheduler()->scheduleDelayedTask(
            new ClosureTask(function() use ($player, $attachment): void {
                $player->removeAttachment($attachment);
                unset($this->attachments[$player->getName()]);
            }),
            $seconds * 20 // Convert seconds to ticks
        );
    }
}

Hierarchical Permissions

Parent-Child Relationships

// Register parent with children
$adminPerm = new Permission(
    "myplugin.admin",
    "Full admin access",
    [
        "myplugin.command.ban" => true,
        "myplugin.command.kick" => true,
        "myplugin.command.mute" => true,
        "myplugin.moderator" => true  // Includes moderator perms
    ]
);

$modPerm = new Permission(
    "myplugin.moderator",
    "Moderator access",
    [
        "myplugin.command.warn" => true,
        "myplugin.command.freeze" => true
    ]
);

PermissionManager::getInstance()->addPermission($adminPerm);
PermissionManager::getInstance()->addPermission($modPerm);
Now players with myplugin.admin automatically have:
  • myplugin.command.ban
  • myplugin.command.kick
  • myplugin.command.mute
  • myplugin.moderator (and its children)
    • myplugin.command.warn
    • myplugin.command.freeze

Wildcards

plugin.yml
permissions:
  myplugin.*:
    description: "All MyPlugin permissions"
    children:
      myplugin.admin: true
      myplugin.moderator: true
      myplugin.user: true
if ($player->hasPermission("myplugin.*")) {
    // Player has all myplugin permissions
}

Operators

Managing Operators

$server = Server::getInstance();

// Make player operator
$player->setOp(true);

// Remove operator
$player->setOp(false);

// Check if operator
if ($player->isOp()) {
    $player->sendMessage("You are an operator!");
}

// Get all operators (from ops.txt)
$ops = $server->getOps();

Operator Permissions

Operators automatically receive all permissions with default: op:
use pocketmine\permission\DefaultPermissions;

// Root operator permission (all operators have this)
$opRoot = PermissionManager::getInstance()->getPermission(
    DefaultPermissions::ROOT_OPERATOR
);

// Add child to operator root
$opRoot->addChild("myplugin.admin", true);
// Now all operators have myplugin.admin

Default Permissions

Built-in Permission Roots

use pocketmine\permission\DefaultPermissions;

// Root for all operators
DefaultPermissions::ROOT_OPERATOR = "pocketmine.group.operator";

// Root for all users
DefaultPermissions::ROOT_USER = "pocketmine.group.user";

Registering to Roots

protected function onEnable(): void {
    $permManager = PermissionManager::getInstance();
    
    // Create permission
    $perm = new Permission("myplugin.user.feature", "Access user feature");
    $permManager->addPermission($perm);
    
    // Add to user root (everyone gets it)
    $userRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
    $userRoot->addChild($perm->getName(), true);
    
    // Create admin permission
    $adminPerm = new Permission("myplugin.admin.panel", "Access admin panel");
    $permManager->addPermission($adminPerm);
    
    // Add to operator root (only ops get it)
    $opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
    $opRoot->addChild($adminPerm->getName(), true);
}

Permission Events

Monitor permission changes:
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerLoginEvent;
use pocketmine\permission\PermissionAttachment;

class PermissionListener implements Listener {
    
    private array $attachments = [];
    
    public function onLogin(PlayerLoginEvent $event): void {
        $player = $event->getPlayer();
        
        // Grant permissions based on custom logic
        if ($this->isVIP($player)) {
            $attachment = $player->addAttachment($this->plugin);
            $attachment->setPermission("myplugin.vip", true);
            $this->attachments[$player->getName()] = $attachment;
        }
    }
    
    private function isVIP(Player $player): bool {
        // Check database, config, etc.
        return true;
    }
}

Common Patterns

Pattern 1: Command Permission Check

class TeleportCommand extends Command {
    
    public function __construct() {
        parent::__construct(
            "teleport",
            "Teleport to a player",
            "/teleport <player>"
        );
        $this->setPermission("myplugin.command.teleport");
    }
    
    public function execute(CommandSender $sender, string $label, array $args): void {
        if (!$this->testPermission($sender)) {
            return; // Automatically sends permission message
        }
        
        // Execute command
    }
}

Pattern 2: Feature Toggle

public function onPlayerMove(PlayerMoveEvent $event): void {
    $player = $event->getPlayer();
    
    if ($player->hasPermission("myplugin.fly")) {
        $player->setAllowFlight(true);
    } else {
        $player->setAllowFlight(false);
    }
}

Pattern 3: Tiered Permissions

plugin.yml
permissions:
  myplugin.homes.1:
    description: "Set 1 home"
    default: true
  
  myplugin.homes.3:
    description: "Set 3 homes"
    default: false
  
  myplugin.homes.unlimited:
    description: "Unlimited homes"
    default: op
function getMaxHomes(Player $player): int {
    if ($player->hasPermission("myplugin.homes.unlimited")) {
        return PHP_INT_MAX;
    }
    if ($player->hasPermission("myplugin.homes.3")) {
        return 3;
    }
    if ($player->hasPermission("myplugin.homes.1")) {
        return 1;
    }
    return 0;
}

Best Practices

// Good
myplugin.command.teleport
myplugin.feature.fly
myplugin.admin.panel

// Bad
myplugin.tp
myplugin.fly
myplugin.admin
Always add descriptions in plugin.yml:
permissions:
  myplugin.admin:
    description: "Full administrative access to MyPlugin"
    default: op
Group related permissions:
myplugin.command.*
  ├─ myplugin.command.teleport
  ├─ myplugin.command.heal
  └─ myplugin.command.feed
Remove attachments when no longer needed:
public function onQuit(PlayerQuitEvent $event): void {
    $player = $event->getPlayer();
    if (isset($this->attachments[$player->getName()])) {
        $player->removeAttachment($this->attachments[$player->getName()]);
    }
}

Build docs developers (and LLMs) love