This page explains the structure of PocketMine-MP plugins and how to properly configure them.
Folder Structure
A properly structured plugin looks like this:
MyPlugin/
├── plugin.yml # Plugin manifest (required)
├── resources/ # Embedded resources (optional)
│ ├── config.yml
│ ├── messages.yml
│ └── data/
│ └── items.json
└── src/ # Source code
└── MyNamespace/
├── Main.php # Main plugin class
├── commands/
│ └── MyCommand.php
├── events/
│ └── EventListener.php
└── tasks/
└── MyTask.php
Only plugin.yml and your main class are required. All other files are optional.
plugin.yml Reference
The plugin.yml file is the plugin manifest. It describes your plugin to the server.
Required Fields
name : MyPlugin # Plugin name (alphanumeric, spaces, _, -, .)
version : 1.0.0 # Version string
main : MyNamespace\Main # Fully qualified main class name
api : [ 5.0.0 ] # Compatible API versions (array)
Optional Fields
# Metadata
author : YourName # Single author
authors : [ Alice , Bob ] # Multiple authors
description : Plugin description
website : https://example.com
prefix : MyPlugin # Logger prefix (defaults to plugin name)
# Loading
load : POSTWORLD # STARTUP or POSTWORLD (default)
# Dependencies
depend : [ PluginA ] # Hard dependencies (required)
softdepend : [ PluginB ] # Soft dependencies (optional)
loadbefore : [ PluginC ] # Load before these plugins
# Requirements
mcpe-protocol : [ 575 , 582 ] # Compatible protocol versions
os : [ linux , windows ] # Compatible operating systems
extensions : # Required PHP extensions
- sqlite3
- curl
# Source namespace prefix (for DevTools)
src-namespace-prefix : MyNamespace
Commands
Declare commands in plugin.yml:
commands :
mycommand :
description : Does something cool
usage : /mycommand <arg>
aliases : [ mc , mycmd ]
permission : myplugin.command.mycommand
permission-message : You don't have permission to use this command
anothercommand :
description : Another command
usage : /anothercommand
permission : myplugin.command.another
All commands declared in plugin.yml MUST have a permission field in API 5.0+. This is a security requirement.
Permissions
Declare permissions in plugin.yml:
permissions :
myplugin.command.mycommand :
description : Allows using /mycommand
default : true # true, false, op, or notop
myplugin.admin :
description : Admin permissions
default : op
children :
myplugin.command.mycommand : true
myplugin.command.another : true
myplugin.vip :
description : VIP permissions
default : false
Default values:
true - Everyone has this permission
false - No one has this permission by default
op - Only operators have this permission
notop - Only non-operators have this permission
Complete plugin.yml Example
name : AdvancedPlugin
version : 2.1.0
main : Author\AdvancedPlugin\Main
api : [ 5.0.0 ]
author : PluginAuthor
description : An advanced example plugin
website : https://github.com/author/advancedplugin
prefix : AdvPlugin
load : POSTWORLD
depend : [ EconomyAPI ]
softdepend : [ WorldGuard ]
commands :
myitem :
description : Get a custom item
usage : /myitem <type>
aliases : [ mi , item ]
permission : advplugin.command.myitem
reload :
description : Reload plugin configuration
usage : /advplugin reload
permission : advplugin.command.reload
permission-message : §cYou need admin permissions!
permissions :
advplugin.command.myitem :
description : Use the myitem command
default : true
advplugin.command.reload :
description : Reload the plugin
default : op
advplugin.admin :
description : All admin permissions
default : op
children :
advplugin.command.reload : true
advplugin.bypass.cooldown : true
advplugin.bypass.cooldown :
description : Bypass item cooldowns
default : false
Main Class Structure
Your main class must extend PluginBase:
<? php
declare ( strict_types = 1 );
namespace MyNamespace ;
use pocketmine\plugin\ PluginBase ;
class Main extends PluginBase {
// Called when plugin is loaded (before onEnable)
protected function onLoad () : void {
// Early initialization
// Register custom classes
// DO NOT access Server instance here
}
// Called when plugin is enabled
protected function onEnable () : void {
// Most initialization happens here
// Load config
// Register events
// Register commands
// Start tasks
}
// Called when plugin is disabled
protected function onDisable () : void {
// Cleanup
// Save data
// Cancel tasks
}
}
Load Order
Plugins can specify when they should be loaded:
STARTUP
Loaded before worlds are loaded. Use this if your plugin:
Registers custom world generators
Modifies world loading behavior
Needs to run very early
STARTUP plugins cannot access worlds in onEnable() because they haven’t loaded yet.
POSTWORLD (Default)
Loaded after worlds are loaded. This is the default and recommended for most plugins.
Dependencies
Control plugin load order with dependencies:
Hard Dependencies (depend)
Required plugins. Server won’t load your plugin without them.
depend : [ EconomyAPI , WorldGuard ]
Soft Dependencies (softdepend)
Optional plugins. If present, they load before your plugin.
Check if soft dependencies are loaded:
if ( $this -> getServer () -> getPluginManager () -> getPlugin ( "BetterChat" ) !== null ) {
// BetterChat is loaded, integrate with it
}
Load Before (loadbefore)
Your plugin loads before these plugins.
loadbefore : [ AnotherPlugin ]
Resources Folder
The resources/ folder contains files embedded in your plugin:
MyPlugin/
└── resources/
├── config.yml # Default config
├── messages.yml
└── data/
└── items.json
Access resources in code:
// Save default config to data folder
$this -> saveDefaultConfig ();
// Save other resources
$this -> saveResource ( "messages.yml" );
// Read resource directly (doesn't save to disk)
$contents = file_get_contents ( $this -> getResourcePath ( "data/items.json" ));
Resources are read-only. To modify them, copy them to the data folder first using saveResource().
Data Folder
Each plugin gets a data folder at plugins/MyPlugin/:
// Get data folder path
$dataFolder = $this -> getDataFolder ();
// Save a file
file_put_contents ( $dataFolder . "data.json" , $jsonData );
// Read a file
$data = file_get_contents ( $dataFolder . "data.json" );
The data folder is created automatically when your plugin loads.
Namespace Best Practices
Use unique top-level namespace
Use your username or organization: namespace MyUsername\MyPlugin ;
// or
namespace MyOrg\MyPlugin ;
Match folder structure
src/
└── MyUsername/
└── MyPlugin/
├── Main.php # MyUsername\MyPlugin\Main
└── commands/
└── MyCmd.php # MyUsername\MyPlugin\commands\MyCmd
Use src-namespace-prefix for DevTools
If using DevTools to build PHAR files: src-namespace-prefix : MyUsername\MyPlugin
API Versions
Specify which API versions your plugin supports:
# Single version
api : [ 5.0.0 ]
# Multiple versions
api : [ 5.0.0 , 5.1.0 ]
# All 5.x versions (not recommended)
api : [ 5.0.0 , 5.1.0 , 5.2.0 , 5.3.0 ]
Check your server’s API version with /version in-game or server.properties.
Common Mistakes
Mistake 1: Colons in command names commands :
myplugin:command : # ❌ Invalid
description : Bad
Command names cannot contain colons. Mistake 2: Missing permission field commands :
mycommand :
description : My command # ❌ Missing permission
All commands MUST have a permission in API 5.0+. Mistake 3: Wrong namespace main : Main # ❌ No namespace
main : MyNamespace\\Main # ❌ Double backslashes
main : MyNamespace\Main # ✅ Correct
Next Steps
Event Handlers Learn to handle game events
Commands Create custom commands