Documentation Index
Fetch the complete documentation index at: https://mintlify.com/pmmp/PocketMine-MP/llms.txt
Use this file to discover all available pages before exploring further.
The Form API allows you to create custom user interfaces that players can interact with. Forms are displayed as modal dialogs in the game client.
All forms must implement the Form interface.
Interface Definition
namespace pocketmine\form;
interface Form extends \JsonSerializable {
/**
* Handles a form response from a player.
*
* @param Player $player The player who submitted the form
* @param mixed $data The form response data
* @throws FormValidationException if the data could not be processed
*/
public function handleResponse(Player $player, $data) : void;
/**
* Serialize form to JSON for sending to client.
*/
public function jsonSerialize() : mixed;
}
use pocketmine\player\Player;
// Send a form to a player
$player->sendForm($form);
While PocketMine-MP core only provides the Form interface, there are three standard form types used in Bedrock Edition:
- ModalForm - Simple dialog with two buttons
- MenuForm - List of buttons with optional icons
- CustomForm - Complex form with multiple input elements
A basic yes/no dialog.
use pocketmine\form\Form;
use pocketmine\player\Player;
class SimpleModalForm implements Form {
public function __construct(
private string $title,
private string $content,
private string $button1Text,
private string $button2Text,
private \Closure $onSubmit
){}
public function jsonSerialize() : array {
return [
"type" => "modal",
"title" => $this->title,
"content" => $this->content,
"button1" => $this->button1Text,
"button2" => $this->button2Text
];
}
public function handleResponse(Player $player, $data) : void {
if ($data === null) {
// Form was closed
return;
}
// $data is boolean: true for button1, false for button2
($this->onSubmit)($player, $data);
}
}
// Usage
$form = new SimpleModalForm(
"Confirm Action",
"Are you sure you want to teleport to spawn?",
"Yes",
"No",
function (Player $player, bool $response) : void {
if ($response) {
$player->teleport($player->getWorld()->getSpawnLocation());
$player->sendMessage("Teleported to spawn!");
} else {
$player->sendMessage("Teleport cancelled.");
}
}
);
$player->sendForm($form);
A list of clickable buttons.
use pocketmine\form\Form;
use pocketmine\player\Player;
class SimpleMenuForm implements Form {
private array $buttons = [];
public function __construct(
private string $title,
private string $content = ""
){}
public function addButton(string $text, ?string $iconPath = null, ?\Closure $onClick = null) : void {
$button = ["text" => $text];
if ($iconPath !== null) {
$button["image"] = [
"type" => "path",
"data" => $iconPath
];
}
$this->buttons[] = [
"button" => $button,
"onClick" => $onClick
];
}
public function jsonSerialize() : array {
return [
"type" => "form",
"title" => $this->title,
"content" => $this->content,
"buttons" => array_map(fn($b) => $b["button"], $this->buttons)
];
}
public function handleResponse(Player $player, $data) : void {
if ($data === null || !isset($this->buttons[$data])) {
return;
}
$onClick = $this->buttons[$data]["onClick"];
if ($onClick !== null) {
$onClick($player);
}
}
}
// Usage
$form = new SimpleMenuForm("Main Menu", "Select an option:");
$form->addButton("Teleport to Spawn", "textures/blocks/grass", function (Player $player) : void {
$player->teleport($player->getWorld()->getSpawnLocation());
$player->sendMessage("Teleported to spawn!");
});
$form->addButton("View Stats", "textures/items/book_normal", function (Player $player) : void {
$player->sendMessage("Your stats: ...");
});
$form->addButton("Settings", "textures/ui/settings_glyph_color_2x", function (Player $player) : void {
// Open settings form
});
$player->sendForm($form);
A complex form with various input types.
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\player\Player;
class SimpleCustomForm implements Form {
private array $elements = [];
public function __construct(
private string $title,
private \Closure $onSubmit
){}
public function addLabel(string $text) : void {
$this->elements[] = [
"type" => "label",
"text" => $text
];
}
public function addInput(string $text, string $placeholder = "", string $default = "") : void {
$this->elements[] = [
"type" => "input",
"text" => $text,
"placeholder" => $placeholder,
"default" => $default
];
}
public function addToggle(string $text, bool $default = false) : void {
$this->elements[] = [
"type" => "toggle",
"text" => $text,
"default" => $default
];
}
public function addSlider(string $text, float $min, float $max, float $step = 1.0, float $default = null) : void {
$this->elements[] = [
"type" => "slider",
"text" => $text,
"min" => $min,
"max" => $max,
"step" => $step,
"default" => $default ?? $min
];
}
public function addDropdown(string $text, array $options, int $default = 0) : void {
$this->elements[] = [
"type" => "dropdown",
"text" => $text,
"options" => $options,
"default" => $default
];
}
public function jsonSerialize() : array {
return [
"type" => "custom_form",
"title" => $this->title,
"content" => $this->elements
];
}
public function handleResponse(Player $player, $data) : void {
if ($data === null) {
return;
}
if (!is_array($data)) {
throw new FormValidationException("Expected array response");
}
($this->onSubmit)($player, $data);
}
}
// Usage
$form = new SimpleCustomForm(
"Player Settings",
function (Player $player, array $data) : void {
[$label, $username, $pvpEnabled, $volume, $gamemode] = $data;
// Process form data
$player->sendMessage("Settings saved!");
$player->sendMessage("PvP: " . ($pvpEnabled ? "Enabled" : "Disabled"));
$player->sendMessage("Volume: $volume");
}
);
$form->addLabel("Configure your settings:");
$form->addInput("Username", "Enter username", $player->getName());
$form->addToggle("Enable PvP", true);
$form->addSlider("Volume", 0, 100, 1, 50);
$form->addDropdown("Preferred Gamemode", ["Survival", "Creative", "Adventure"], 0);
$player->sendForm($form);
Custom forms support various input elements:
Label
[
"type" => "label",
"text" => "Display text"
]
Input (Text Field)
[
"type" => "input",
"text" => "Label",
"placeholder" => "Placeholder text",
"default" => "Default value"
]
Toggle (Checkbox)
[
"type" => "toggle",
"text" => "Label",
"default" => false
]
Slider
[
"type" => "slider",
"text" => "Label",
"min" => 0.0,
"max" => 100.0,
"step" => 1.0,
"default" => 50.0
]
Dropdown (Select)
[
"type" => "dropdown",
"text" => "Label",
"options" => ["Option 1", "Option 2", "Option 3"],
"default" => 0
]
Step Slider
[
"type" => "step_slider",
"text" => "Label",
"steps" => ["Easy", "Normal", "Hard"],
"default" => 1
]
class ShopForm implements Form {
private array $items;
public function __construct() {
$this->items = [
[
"name" => "Diamond Sword",
"price" => 100,
"item" => VanillaItems::DIAMOND_SWORD()
],
[
"name" => "Golden Apple",
"price" => 50,
"item" => VanillaItems::GOLDEN_APPLE()
],
[
"name" => "Diamond Armor Set",
"price" => 500,
"item" => null // Special case
]
];
}
public function jsonSerialize() : array {
$buttons = [];
foreach ($this->items as $item) {
$buttons[] = [
"text" => $item["name"] . "\n$" . $item["price"]
];
}
return [
"type" => "form",
"title" => "Shop",
"content" => "Select an item to purchase:",
"buttons" => $buttons
];
}
public function handleResponse(Player $player, $data) : void {
if ($data === null || !isset($this->items[$data])) {
return;
}
$item = $this->items[$data];
$economy = EconomyAPI::getInstance();
if ($economy->getMoney($player) < $item["price"]) {
$player->sendMessage("You don't have enough money!");
return;
}
// Show confirmation
$confirm = new SimpleModalForm(
"Confirm Purchase",
"Buy {$item['name']} for $" . $item["price"] . "?",
"Buy",
"Cancel",
function (Player $player, bool $response) use ($item, $economy) : void {
if (!$response) {
return;
}
$economy->reduceMoney($player, $item["price"]);
if ($item["item"] !== null) {
$player->getInventory()->addItem($item["item"]);
}
$player->sendMessage("Successfully purchased {$item['name']}!");
}
);
$player->sendForm($confirm);
}
}
Always validate form responses to prevent exploits:
public function handleResponse(Player $player, $data) : void {
if ($data === null) {
// Form was closed
return;
}
if (!is_array($data)) {
throw new FormValidationException("Expected array response");
}
// Validate number of elements
if (count($data) !== $this->expectedElements) {
throw new FormValidationException("Invalid number of form elements");
}
// Validate individual elements
if (!is_string($data[0]) || strlen($data[0]) > 100) {
throw new FormValidationException("Invalid input field");
}
if (!is_bool($data[1])) {
throw new FormValidationException("Invalid toggle field");
}
// Process validated data
}
Best Practices
-
Validate All Input: Always validate form responses to prevent client-side manipulation
-
Handle Null Responses: Players can close forms without submitting - always check for null
-
Use Closures Carefully: Be mindful of variable scope when using closures in form callbacks
-
Error Handling: Provide clear error messages when validation fails
-
User Feedback: Always send confirmation messages after successful form submission
-
Chain Forms: Create multi-step workflows by opening new forms from callbacks
-
Icons: Use texture paths for button icons to improve visual appeal
-
Keep It Simple: Don’t overwhelm players with too many form elements at once