PlaceholderAPI/docs/developers/creating-a-placeholderexpansion.md
2024-08-01 15:37:22 -04:00

19 KiB

description
Comprehensive guide on how to create a PlaceholderExpansion for other plugins to use through PlaceholderAPI.

Creating a PlaceholderExpansion

This page will cover how you can create your own PlaceholderExpansion which you can either integrate into your own plugin (Recommended) or upload to the eCloud.

It's worth noting that PlaceholderAPI relies on expansions being installed. PlaceholderAPI only acts as the core replacing utility while the expansions allow other plugins to use any installed placeholder in their own messages.
You can download expansions either directly from the eCloud yourself, or download them through the download command of PlaceholderAPI.

Table of contents

Getting started

For starters, you need to decide what type of PlaceholderExpansion you want to create. There are various ways to create an expansion. This page will cover the most common ones.

Common Expansion Parts

All shown examples will share the same common parts that belong the the PlaceholderExpansion class.
In order to not repeat the same basic info for each method throughout this page, and to greatly reduce the overall length, we will cover the most basic/necessary ones here.

Basic PlaceholderExpansion Structure

/// note | Tab the :material-plus-circle: icons in the code block below for additional information. ///

package at.helpch.placeholderapi.example.expansion;

import me.clip.placeholderapi.expansion.PlaceholderExpansion;

public class SomeExpansion extends PlaceholderExpansion {
    
    @Override
    public String getAuthor() {
        return "Author"; // (1)
    }
    
    @Override
    public String getIdentifier() {
        return "example"; // (2)
    }
    
    @Override
    public String getVersion() {
        return "1.0.0"; // (3)
    }
}
  1. This method allows you to set the name of the expansion's author.

  2. The identifier is the part in the placeholder that is between the first % (or { if bracket placeholders are used) and the first _.
    The identifier may not contain %, {, } or _.

    If you still want to use them in your expansion name, override the getName() method.

  3. This method returns the version of the expansion.
    Due to it being a string are you not limited to numbers alone, but it is recommended to stick with a number pattern.

    PlaceholderAPI uses this String to compare with the latest version on the eCloud (if uploaded to it) to see if a new version is available.
    If your expansion is included in a plugin, this does not matter.

You must also choose between one of these two methods for handling the actual parsing of placeholders (Exception being expansions providing relational placeholders):

  • onRequest(OfflinePlayer, String)
    The first parameter is the player that the placeholders are parsed against, given as an OfflinePlayer instance. This can be null.
    The second parameter is the content of the placeholder after the first _ and before the closing % (or } if bracket placeholders are used). This String is never null.

    If not explicity overriden, this will automatically call onPlaceholderRequest(Player, String), passing the parameters as-is to it.
    This method is recommended as it allows the usage of offline players, meaning the player does not need to be online to obtain certain certain data from them such as name or UUID.

  • onPlaceholderRequest(Player, String)
    The first parameter is the player that the placeholders are parsed against, given as a Player instance. This can be null.
    The second parameter is the content of the placeholder after the first _ and before the closing % (or } if bracket placeholders are used). This String is never null.

    If not set, this method will return null which PlaceholderAPI sees as an invalid placeholder.

/// note PlaceholderAPI always calls onRequest(Player, String) in a PlaceholderExpansion. ///


Making an Internal Expansion

Internal PlaceholderExpansions are classes directly integrated in the plugin they depend on.
This method of creating a PlaceholderExpansion is recommended as it has the following benefits:

  • No canRegister() method override required. Since your expansion is part of the plugin it depends on is this override not required.
  • Easier access to plugin data. Using dependency injection, you can more easily access data of your plugin such as config values.

/// warning | Important! Internal PlaceholderExpansions are not automatically registered by PlaceholderAPI, due to them not being a separate jar file located in the expansion folder.
Please see the Regsister your Expansion section for more details.

You are also required to override and set persist() to true. This tells PlaceholderAPI to not unload your expansion during plugin reload, as it would otherwise unregister your expansion, making it no longer work. ///

/// details | Full Example attrs: { id: full-example-internal } type: example

//// note | Please see the Basic PlaceholderExpansion Structure section for an explanation of all common methods in this example.

Tab the :material-plus-circle: icons in the code block below for additional information. ////

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;

public class SomeExpansion extends PlaceholderExpansion {
    
    private final SomePlugin plugin; // (1)
    
    public SomeExpansion(SomePlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    @NotNull
    public String getAuthor() {
        return String.join(", ", plugin.getDescription().getAuthors()); // (2)
    }
    
    @Override
    @NotNull
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    @NotNull
    public String getVersion() {
        return plugin.getDescription().getVersion(); // (3)
    }
    
    @Override
    public boolean persist() {
        return true; // (4)
    }
    
    @Override
    public String onRequest(OfflinePlayer player, @NotNull String params) {
        if (params.equalsIgnoreCase("placeholder1")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1"); // (5)
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1"); // (6)
        }
        
        return null; // (7)
    }
}
  1. Mockup plugin used to showcase the use of dependency injection to access specific plugin related data.

  2. We can use the authors set in the plugin's plugin.yml file as the authors of this expansion.

  3. Since our expansion is internal can this version be the same as the one defined in the plugin's plugin.yml file.

  4. This needs to be set, or else will PlaceholderAPI unregister our expansion during a plugin reload.

  5. Example of accessing data of the plugin's config.yml file.

  6. Example of accessing data of the plugin's config.yml file.

  7. Reaching this means that an invalid params String was given, so we return null to tell PlaceholderAPI that the placeholder was invalid. ///

Register your Expansion

Due to the PlaceholderExpansion being internal, PlaceholderAPI does not load it automatically, we'll need to do it manually.
This is being done by creating a new instance of your PlaceholderExpansion class and calling the register() method of it.

Here is a quick example:

package at.helpch.placeholderapi.example;

import at.helpch.placeholderapi.example.expansion.SomeExpansion;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class SomePlugin extends JavaPlugin {
    
    @Override
    public void onEnable() {
        if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { // (1)
            new SomeExpansion(this).register(); // (2)
        }
    }
}
  1. We check that PlaceholderAPI is present and enabled on the server, or else we would get Exceptions.
    Also, make sure you set PlaceholderAPI as depend or softdepend in your plugin's plugin.yml file!

  2. This registers our expansion in PlaceholderAPI. It also gives the Plugin class as dependency injection to the Expansion class, so that we can use it.


Making an External Expansion

External Expansions are separate Jar files located inside PlaceholderAPI's expansions folder, that contain the PlaceholderExpansion extending class.
It is recommended to only make external Expansions for the following situations.

  • Your expansion does not rely on a plugin.
  • Your expansion depends on a plugin and you can't directly include it (Plugin is not your own).

Should the above cases not match your situation, meaning your expansion is for a plugin you maintain, is the creation of an internal Expansion recommended.

Some benefits of an external expansion include automatic (re)loading of your expansion by PlaceholderAPI and having the option to upload it to the eCloud allowing the download of it through the /papi ecloud download command.
Downsides include a more tedious setup in terms of checking for a required plugin being present.

/// details | Full Example (Without Dependency) attrs: { id: full-example-external-no-dependency } type: example

//// note | Please see the Basic PlaceholderExpansion Structure section for an explanation of all common methods in this example.

Tab the :material-plus-circle: icons in the code block below for additional information. ////

This is an example expansion without any plugin dependency.

package at.helpch.placeholderapi.example.expansion;

import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;

public class SomeExpansion extends PlaceholderExpansion {
    
    @Override
    @NotNull
    public String getAuthor() {
        return "Author";
    }
    
    @Override
    @NotNull
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    @NotNull
    public String getVersion() {
        return "1.0.0";
    }
    
    @Override
    public String onRequest(OfflinePlayer player, @NotNull String params) {
        if (params.equalsIgnoreCase("placeholder1")) {
            return "text1";
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return "text2";
        }
        
        return null; // (1)
    }
}
  1. Reaching this means that an invalid params String was given, so we return null to tell PlaceholderAPI that the placeholder was invalid. ///

/// details | Full Example (With Dependency) attrs: { id: full-example-external-dependency } type: example

//// note | Please see the Basic PlaceholderExpansion Structure section for an explanation of all common methods in this example.

Tab the :material-plus-circle: icons in the code block below for additional information. ////

This is an example expansion with a plugin dependency.

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;

public class SomeExpansion extends PlaceholderExpansion {
    
    private SomePlugin plugin; // (1)
    
    @Override
    @NotNull
    public String getAuthor() {
        return "Author";
    }
    
    @Override
    @NotNull
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    @NotNull
    public String getVersion() {
        return "1.0.0"
    }
    
    @Override
    public String getRequiredPlugin() {
        return "SomePlugin"; // (2)
    }
    
    @Override
    public boolean canRegister() { // (3)
        return (plugin = (SomePlugin) Bukkit.getPluginManager().getPlugin(getRequiredPlugin())) != null;
    }
    
    @Override
    public String onRequest(OfflinePlayer player, @NotNull String params) {
        if (params.equalsIgnoreCase("placeholder1")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1"); // (4)
        }
        
        if (params.equalsIgnoreCase("placeholder2")) {
            return plugin.getConfig().getString("placeholders.placeholder1", "default1"); // (5)
        }
        
        return null; // (6)
    }
}
  1. We set the value of this instance in the canRegister() method, which means that it can't be set to be final.

  2. The name of the plugin this expansion depends on.
    It is recommended to set this, as it would result in PlaceholderAPI reporting any missing plugin for your expansion.

  3. This does two things:

    1. It sets the plugin instance to SomePlugin using Bukkit's PluginManager to retrieve a JavaPlugin instance that is cast to SomePlugin.
    2. It checks if the retrieved instance is not null. If it is will this result in canRegister() returning false, resulting in PlaceholderAPI not loading our expansion.
  4. Example of accessing data of the plugin's config.yml file.

  5. Example of accessing data of the plugin's config.yml file.

  6. Reaching this means that an invalid params String was given, so we return null to tell PlaceholderAPI that the placeholder was invalid. ///


Making a relational Expansion

/// note Relational Placeholders always start with rel_ to properly identify them. This means that if you make a relational placeholder called friends_is_friend would the full placeholder be %rel_friends_is_friend%. ///

Relational PlaceholderExpansions are special in that they take two players as input, allowing you to give outputs based on their relation to each other.

To create a relational expansion you will need to implement the Relational interface into your expansion. You also still need to extend the PlaceholderExpansion class.
Implementing this interface will add the onPlaceholderRequest(Player, Player, String) with the first two arguments being the first and second player to use and the third argument being the content after the second _ and before the final % (Or } if bracket placeholders are used) in the placeholder.

/// details | Full Example attrs: { id: full-example-relational } type: example

//// note | Please see the Basic PlaceholderExpansion Structure section for an explanation of all common methods in this example.

Tab the :material-plus-circle: icons in the code block below for additional information. ////

This is a complete example of using relational placeholders.
For the sake of simplicity are we using the internal Expansion setup here and assume that SomePlugin offers a areFriends(Player, Player) method that returns true or false based on if the players are friends or not.

package at.helpch.placeholderapi.example.expansion;

import at.helpch.placeholderapi.example.SomePlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.Relational
import org.bukkit.ChatColor;
import org.bukkit.Player;
import org.jetbrains.annotations.NotNull;

public class SomeExpansion extends PlaceholderExpansion implements Relational {
    
    private final SomePlugin plugin; // (1)
    
    public SomeExpansion(SomePlugin plugin) {
        this.plugin = plugin;
    }
    
    @Override
    @NotNull
    public String getAuthor() {
        return String.join(", ", plugin.getDescription().getAuthors()); // (2)
    }
    
    @Override
    @NotNull
    public String getIdentifier() {
        return "example";
    }
    
    @Override
    @NotNull
    public String getVersion() {
        return plugin.getDescription().getVersion(); // (3)
    }
    
    @Override
    public boolean persist() {
        return true; // (4)
    }
    
    @Override
    public String onPlaceholderRequest(Player one, Player two, String identifier) {
        if (one == null || two == null) {
            return null; // (5)
        }
        
        if (identifier.equalsIgnoreCase("friends")) { // (6)
            if (plugin.areFriends(one, two)) {
                return ChatColor.GREEN + one.getName() + " and " + two.getName() + " are friends!";
            } else {
                return ChatColor.RED + one.getName() + " and " + two.getName() + " are not friends!";
            }
        }
        
        return null; // (7)
    }
}
  1. Mockup plugin used to showcase the use of dependency injection to access specific plugin related data.

  2. We can use the authors set in the plugin's plugin.yml file as the authors of this expansion.

  3. Since our expansion is internal can this version be the same as the one defined in the plugin's plugin.yml file.

  4. This needs to be set, or else will PlaceholderAPI unregister our expansion during a plugin reload.

  5. Our placeholder requires both players to be present, so if either one is not will this return null.

  6. In case the identifier matches (Meaning the placeholder is %rel_example_friends% or {rel_example_friends}) will we check if Player one and two are friends through our plugin's areFriends(Player, Player) method.
    Should they be friends, return green text saying they are and else return red text saying they aren't.

  7. Reaching this means that an invalid params String was given, so we return null to tell PlaceholderAPI that the placeholder was invalid.

Don't forget to register your expansion. ///