Skip to content

Placeholders

Petrus Pradella edited this page Jul 23, 2026 · 2 revisions

Placeholders

What this page covers: the placeholder engine - Replacer / RegexReplacer and the Manipulator extension - plus how it bridges to PlaceholderAPI. A RegexReplacer<O> scans a string for closures (%key% or {key}), looks each one up against parsers you registered, and substitutes the result. This is the regex engine behind dynamic text; for the simpler literal %token% swaps used when sending a message, see Localization.


The 30-second version

import br.com.finalcraft.evernifecore.placeholder.replacer.RegexReplacer;

// A replacer bound to some object type O (here: a Guild).
RegexReplacer<Guild> guildReplacer = new RegexReplacer<Guild>()
        .addParser("guild_name",    Guild::getName)
        .addParser("guild_members", guild -> guild.getMembers().size())
        .addParser("guild_level",   Guild::getLevel);

String line = guildReplacer.apply("&e%guild_name% &7(lvl %guild_level%, %guild_members% members)", guild);
// -> "&eDragons (lvl 4, 12 members)"

Each %key% in the text is resolved by the parser registered under key, passing your object. A placeholder with no matching parser is left untouched.


Closures: %...% and {...}

A closure is the delimiter pair the engine looks for. Two are built in:

Closure Delimiters Pattern source
PERCENT %key% Closures.PERCENT (default)
BRACKET {key} Closures.BRACKET

A bare new RegexReplacer<>() uses %...%. To resolve {...} closures instead, pass the pattern:

RegexReplacer<Guild> braces = new RegexReplacer<>(Closures.BRACKET.getPattern())
        .addParser("guild_name", Guild::getName);
braces.apply("Welcome to {guild_name}", guild); // -> "Welcome to Dragons"

apply(List<String>, O) runs the same substitution over a whole list (e.g. an item lore).


Built-in replacers

FCRegexReplacers ships ready-made replacers for the common player types, so you rarely start from scratch:

import br.com.finalcraft.evernifecore.placeholder.FCRegexReplacers;

String s = FCRegexReplacers.PLAYER_DATA.apply("&a%player% &7last seen %player_last_seen%", playerData);

FCRegexReplacers.PLAYER_DATA (an RegexReplacer<IPlayerData>) provides:

Placeholder Value
%player% / %player_name% Player name
%player_uuid% Player UUID
%player_is_online% Whether the player is online
%player_ontime% Formatted play time
%player_last_seen% / %player_last_seen_millis% Last-seen timestamp
%player_first_seen% / %player_first_seen_millis% First-seen timestamp

FCRegexReplacers.PLAYER (an RegexReplacer<FPlayer>) provides %player%, %player_name%, %player_uuid%, %player_isonline%.


Registering parsers

RegexReplacer implements a small provider API. The parser you register receives your object and returns the value (any type - it's stringified):

replacer.addParser("key", obj -> obj.something());                 // dynamic value
replacer.addParser("key", "static description", obj -> obj.x());   // + a description (docs/help)
replacer.addParser("static_key", "literal value");                 // constant value
replacer.addParser(new String[]{"hp", "health"}, obj -> obj.hp()); // several aliases -> one parser

A default parser handles any closure that no named parser matched - useful for a computed family of keys:

replacer.setDefaultParser((obj, key) -> key.startsWith("stat_")
        ? obj.getStat(key.substring("stat_".length()))
        : null);   // return null to leave the placeholder untouched

Combining replacers - CompoundReplacer

A single message often needs several data sources (the player, a guild, a PlaceholderAPI user). A CompoundReplacer chains multiple RegexReplacers, each bound to its own object, and applies them in order:

import br.com.finalcraft.evernifecore.placeholder.replacer.CompoundReplacer;

CompoundReplacer compound = FCRegexReplacers.PLAYER_DATA.compound(playerData) // replacer + its object
        .appendReplacer(guildReplacer, guild)
        .usePAPI(fplayer);                                                    // optional, see below

String result = compound.apply("&a%player% of &e%guild_name%: %vault_eco_balance%");

A CompoundReplacer is what FancyText and Localization messages accept via fancyText.replace(compoundReplacer) and message.addReplacer(compoundReplacer) - so you can push a fully-composed set of placeholders into a message in one call.


Manipulators - parametric placeholders

For placeholders that carry an argument inside the token - %top_kills_1%, %balance_of_Alice% - use a manipulator. You give a template with a {closure} where the argument sits, and read the captured value from the context:

guildReplacer.addManipulator("member_rank_{memberName}", (guild, ctx) -> {
    String memberName = ctx.getString("{memberName}");  // the captured argument
    return guild.rankOf(memberName);
});

guildReplacer.apply("&7Rank: %member_rank_Alice%", guild); // -> "&7Rank: Officer"

ctx.getString("{closure}") returns the captured text; ctx.getArgumento("{closure}") wraps it as an Argumento for typed access. A template may contain multiple closures (kills_{world}_{player}).

📌 Note - manipulators are an advanced tool for a family of placeholders keyed by an argument. For a fixed set of keys, plain addParser(...) is simpler and faster.


PlaceholderAPI integration

Two independent directions, both routed through the Platform Abstraction (IPlatform):

Consuming other plugins' %papi% placeholders inside your text - add a PAPI user to a CompoundReplacer, or parse directly:

compound.usePAPI(fplayer);                                   // PAPI runs after your own replacers
// or, one-off:
String parsed = EverNifeCore.getPlatform().parse(fplayer, "&aBalance: %vault_eco_balance%");
boolean papiHere = EverNifeCore.getPlatform().isPAPIPresent();

Publishing your own data so other plugins can read it through PAPI - register an integration under a base identifier; it returns a RegexReplacer you populate, and each parser becomes a %<baseId>_<key>% PlaceholderAPI placeholder:

RegexReplacer<PlayerData> papi = EverNifeCore.getPlatform()
        .createPlaceholderIntegration(ecPluginData, "myplugin", PlayerData.class);

papi.addParser("coins", pd -> pd.getPDSection(Profile.class).join().coins);
// other plugins can now use %myplugin_coins%

The integration auto-detects legacy vs modern PlaceholderAPI and registers the hook accordingly; you just add parsers.


See also

  • Localization - @FCLocale messages, the literal %token% replace, and addReplacer(...).
  • FancyText - replace(CompoundReplacer) to apply placeholders to rich text.
  • Platform Abstraction - IPlatform.parse / isPAPIPresent / createPlaceholderIntegration.
  • Integrations - PlaceholderAPI and the other third-party hooks EverNifeCore ships with.

Clone this wiki locally