Skip to content

Provider handlers

Addon developer

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.

SectionReturnsPurpose
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:

  • economyvault (Vault), playerpoints if you install PlayerPointsAddon
  • permissionsvault, luckperms
  • levelsbukkit (vanilla XP)
  • placeholderspapi (PlaceholderAPI), internal (built-ins)
  • skinsskinsrestorer

Addon-supplied providers typically register at 100 so they win auto-resolve when both a default and an addon are present.

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.

EconomyHandler eco = api.providers().economy().resolve(); // configured default, or highest priority
EconomyHandler vault = api.providers().economy().resolve("vault"); // by id, or null
boolean 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.

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:

@Override
public void onEnable(AbstractMenusApi api) {
api.providers().economy().register(
"memory",
new MapEconomy(),
100,
this);
}

To make it the server-wide default, the operator sets:

config.conf
providers {
economy = "memory"
}

Or per-menu, leaving the global default alone:

actions {
click: [
{ type: takeMoney, amount: 100, provider: "memory" }
]
}

All five live under ru.abstractmenus.api.handler.*:

InterfaceMethods
EconomyHandlerhasBalance, takeBalance, giveBalance
PermissionsHandleraddPermission, removePermission, hasPermission, addGroup, removeGroup, hasGroup
LevelHandlergetXp, giveXp, takeXp, getLevel, giveLevel, takeLevel
PlaceholderHandlerreplacePlaceholder, replace(Player, String), replace(Player, List<String>), registerAll
SkinHandlersetSkin, resetSkin

Method names and signatures are stable across the 2.0 line.

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.