Provider handlers
AbstractMenus has five provider sections: economy, permissions, levels, placeholders, skins. Each one is an api.providers().<section>() accessor that returns a ProviderSection<T>. You can register multiple handlers per section, give each one an id and a priority, and the operator picks which is the default in config.conf. Per-action overrides in HOCON win over the config default.
The pre-2.0 static Handlers facade is gone. Everything happens through ProviderSection.
Sections at a glance
Section titled “Sections at a glance”| Section | Returns | Purpose |
|---|---|---|
api.providers().economy() | ProviderSection<EconomyHandler> | takeMoney / giveMoney / hasMoney |
api.providers().permissions() | ProviderSection<PermissionsHandler> | permission / group rules, group and node mutation |
api.providers().levels() | ProviderSection<LevelHandler> | giveXp / takeXp / giveLevel / takeLevel, xp / level rules |
api.providers().placeholders() | ProviderSection<PlaceholderHandler> | placeholder substitution |
api.providers().skins() | ProviderSection<SkinHandler> | setSkin / resetSkin |
The bundled defaults register at priority 50:
- economy —
vault(Vault),playerpointsif you install PlayerPointsAddon - permissions —
vault,luckperms - levels —
bukkit(vanilla XP) - placeholders —
papi(PlaceholderAPI),internal(built-ins) - skins —
skinsrestorer
Addon-supplied providers typically register at 100 so they win auto-resolve when both a default and an addon are present.
Register a handler
Section titled “Register a handler”Implement the relevant *Handler interface, then register it from your MenuExtension.onEnable(api):
public final class MyAddon implements MenuExtension {
@Override public void onEnable(AbstractMenusApi api) { api.providers().economy().register( "playerpoints", // id new PlayerPointsEconomy(playerPointsApi), // handler 100, // priority — higher wins auto-resolve this); // owner — AbstractMenus uses this for cleanup }}The id is what HOCON menus and config.conf reference (provider: "playerpoints"). It’s case-insensitive. The handler instance is reused for every call.
Resolve at runtime
Section titled “Resolve at runtime”EconomyHandler eco = api.providers().economy().resolve(); // configured default, or highest priorityEconomyHandler vault = api.providers().economy().resolve("vault"); // by id, or nullboolean hasPP = api.providers().economy().has("playerpoints");Set<String> ids = api.providers().economy().ids();Collection<EconomyHandler> all = api.providers().economy().all();resolve() first checks config.conf providers.<section>. If the operator pinned an explicit id, that wins. If they left it on "auto", the highest-priority registered handler wins. The lookup is atomic: a concurrent unregister cannot return a stale handler.
resolve(String) is what menu actions use when an HOCON entry has provider: "...". The per-action override always beats the config default.
Example: a custom economy backend
Section titled “Example: a custom economy backend”Override the default economy with one that stores balances in a Map:
public final class MapEconomy implements EconomyHandler {
private final Map<UUID, Double> balance = new ConcurrentHashMap<>();
@Override public boolean hasBalance(Player player, double amount) { return balance.getOrDefault(player.getUniqueId(), 0.0) >= amount; }
@Override public void takeBalance(Player player, double amount) { balance.merge(player.getUniqueId(), -amount, (current, delta) -> Math.max(0, current + delta)); }
@Override public void giveBalance(Player player, double amount) { balance.merge(player.getUniqueId(), amount, Double::sum); }}Register it from your addon:
@Overridepublic void onEnable(AbstractMenusApi api) { api.providers().economy().register( "memory", new MapEconomy(), 100, this);}To make it the server-wide default, the operator sets:
providers { economy = "memory"}Or per-menu, leaving the global default alone:
actions { click: [ { type: takeMoney, amount: 100, provider: "memory" } ]}Handler interfaces
Section titled “Handler interfaces”All five live under ru.abstractmenus.api.handler.*:
| Interface | Methods |
|---|---|
EconomyHandler | hasBalance, takeBalance, giveBalance |
PermissionsHandler | addPermission, removePermission, hasPermission, addGroup, removeGroup, hasGroup |
LevelHandler | getXp, giveXp, takeXp, getLevel, giveLevel, takeLevel |
PlaceholderHandler | replacePlaceholder, replace(Player, String), replace(Player, List<String>), registerAll |
SkinHandler | setSkin, resetSkin |
Method names and signatures are stable across the 2.0 line.
Cleanup
Section titled “Cleanup”When your addon’s onDisable runs, AbstractMenus drops every provider you registered automatically. You don’t (and can’t) call unregisterAll yourself — the public ProviderRegistry interface deliberately doesn’t expose it. One addon can’t wipe another’s providers.
For Path 1, cleanup is automatic: AbstractMenus tracks the addon through its AddonManager and drops the registrations on disable. For Path 2, “disable” means JavaPlugin.onDisable, and auto-cleanup doesn’t fire — your registration sits in the owner-tracking map until AbstractMenus itself shuts down. Re-registering under the same id from a fresh onEnable overwrites the live entry, so this isn’t a leak in practice.