This guide exists to solve one specific headache: you built a cosmetic menu, it looks fine on open, and then it lies to players. A hat shows as locked after they bought it. A rank discount never appears. A “wearing” tag stays stuck until the player reconnects. Those bugs almost all come down to how placeholders are resolved inside a GUI, and how often they are refreshed. Here is the setup, the syntax, the conditional logic, and the performance side.
It is written for server owners and administrators who already run a cosmetic plugin and want the menu to react to permissions, ranks, and economy values. We tested the workflow on a Paper 1.21 test server with PlaceholderAPI, LuckPerms, and a DeluxeMenus-style menu plugin, using three expansions. Anything we could not measure is labeled as a starting recommendation rather than a benchmark.
How PlaceholderAPI Fits Into a Cosmetic Menu
PlaceholderAPI is a bridge. It does not know anything about hats or trails. It takes a token like %player_name%, hands it to whichever expansion registered that identifier, and returns text. Your menu plugin then displays that text in a GUI slot. That separation is why a cosmetic menu can show live data at all: the cosmetic plugin exposes state, the expansion translates it, and PlaceholderAPI stitches them together.
Installation is short, and the temptation is to overdo it:
- Drop PlaceholderAPI into
/pluginsand restart. Never hot-load a plugin that other plugins hook into. - Run
/papi ecloud download <expansion>for each expansion you need, then/papi reload. - Restart the server once after downloading, so every dependent plugin registers its own placeholders in the right order.
- Check the active list with
/papi list. If an expansion is missing here, it is not installed, no matter what the config says.
For a cosmetic menu you usually need only three expansions: Player for name and world, LuckPerms for rank labels, and whatever your cosmetic plugin ships. Treat three as a starting recommendation and trim anything you are not parsing. The full catalog lives in the placeholder list.
Testing Placeholders Before You Touch the GUI
Most GUI bugs are placeholder bugs wearing a costume. Test the raw string in chat first. The syntax is always %identifier_params%, and the identifier before the first underscore decides which expansion runs.
The two commands you will live in
/papi parse me %player_name%resolves the placeholder as yourself. Use it constantly while building item lore./papi parse <player> <text>resolves it as another player, which is how you check that a locked item unlocks for staff ranks./papi bcparse <player> <text>parses the text and broadcasts the result to everyone online, the fastest way to confirm a template resolves for a group at once.
If a placeholder returns its own token unchanged, the expansion is not loaded. If it returns empty, the expansion loaded but has no data for that player, which usually means the permission node is absent. Those outcomes look similar in a menu and mean completely different things, which is why you test outside the GUI. Keep a scratch file with one line per placeholder your menu uses and rerun those lines after every plugin update.
Owned vs Locked Items From Permissions
The cleanest cosmetic menus do not query “do they own it” through a database call on every slot. They read a permission. Your cosmetic plugin grants a node when a player unlocks an item, and the menu checks that node. That is one boolean lookup, it is cached by the permission plugin, and it survives server restarts without extra storage.
Two ways to wire it, and the tradeoff is real:
- Permission check in the menu plugin. A requirement type like “has permission” decides which item variant renders. Fast, but the item name and lore live in two config branches.
- Placeholder boolean in a single item. Parse
%checkitem_...or your cosmetic plugin’s own placeholder, then let a conditional decide the display. One item definition, more parsing work.
For menus up to roughly 54 slots, both approaches perform the same. The practical difference is maintenance: the single-definition version stays consistent when you rename a cosmetic, and the two-branch version is easier to reason about when a requirement is complex.
| Menu state | Data source | What the player sees | Refresh cost |
|---|---|---|---|
| Owned, not equipped | Permission node granted | Green name, “Click to equip” | Low (cached) |
| Owned and equipped | Cosmetic plugin state placeholder | Glowing item, “Equipped” | Low |
| Locked, purchasable | Permission absent + price placeholder | Gray name, price in lore | Medium (economy call) |
| Locked, rank only | LuckPerms group placeholder | Gray name, “Requires VIP” | Low |
Notice the third row: price is the only expensive lookup in that table, which is where caching decisions start to matter. The PlaceholderAPI wiki on GitHub documents the expansion API if you plan to write your own.
LuckPerms Placeholders for Rank-Aware Labels and Pricing
Rank-aware cosmetics are where the menu stops being a shop and starts being a perk. Download the LuckPerms expansion first, then use its placeholders in item names and lore rather than hardcoding group names.
%luckperms_prefix%for decorated rank text in the menu title or a status line.%luckperms_primary_group_name%to branch pricing and labels by group.%luckperms_meta_<key>%for custom values such as a discount multiplier stored as metadata.%luckperms_in_group_<group>%for a simple yes/no gate on a rank-locked cosmetic.
A worked example: players in vip get cosmetics at seventy percent of list price. Store cosmetic_discount=0.7 as LuckPerms metadata instead of building four item variants, then reference that key in a menu requirement. When pricing changes, you edit metadata with one command and no config reload. The LuckPerms wiki covers metadata and inheritance in detail.
Keep the logic in one place. If the discount lives in metadata, do not also hardcode it in the menu config, or the two will drift within a month. Pair it with the [LuckPerms Guide: Advanced Permissions for Minecraft Servers] when you plan inheritance, so the nodes your menu checks are the ones the plugin actually grants. Combined with [The Best Minecraft 1.21 Cosmetic Plugins], this gives you one source of truth for availability and price.
Refresh Strategy: On-Open vs Repeating Tasks
Every GUI refresh re-parses every placeholder in every visible slot. A fifty-four slot menu with six placeholders per item is three hundred and twenty-four parse calls per refresh. Run that every tick for thirty players and you have a TPS problem. Run it on open only and you have stale data. The answer depends on what the value actually is.
| Approach | Best for | Recommended starting interval | Risk |
|---|---|---|---|
| Refresh on open only | Static prices, permission-gated items | Once per open | Values go stale if the player keeps the menu open |
| Repeating task on the inventory | Equipped state, balance, live counters | 40 ticks (two seconds) | Wasted parses on idle menus |
| Per-tick parsing | Nothing in a cosmetic menu | Not recommended | Tick time loss and doubled inventory traffic |
Start at forty ticks for anything that changes while the menu is open, and use on-open-only for everything else. That number is a starting recommendation from our test server, not a law; the right value is the slowest refresh a player will not notice.
Caching and parse limits
- Compute expensive values once and store them in a map keyed by player UUID, with a short expiry, rather than calling the economy plugin for every slot.
- Never parse inside a per-slot loop when every slot uses the same value.
- Cancel the repeating task when the inventory closes. A leaked task is the most common cause of gradual TPS decay on cosmetic servers.
- Test on a staging server, never while players are online.
Pros and Cons of a Placeholder-Driven Menu
Pros. One config produces different menus for different ranks. Prices and labels update without restarts. Permission state is already cached by LuckPerms, so ownership checks are cheap. You can add a cosmetic type without writing a new menu file.
Cons. Every placeholder is a dependency, and dependencies break on updates. Debugging is slower because a wrong label could come from the menu config, the expansion, or the permission. Refresh tuning takes discipline, and a careless repeating task costs more than the static menu it replaced. Without a test server, on-open-only is the safer default.
Common Mistakes and How to Debug Them
- Placeholder shows raw text after an update. The expansion failed to load or was renamed. Run
/papi list, then/papi ecloud download <expansion>and restart. - Menu opens empty. A requirement failed for every item, often because the placeholder returned empty. Test with
/papi parse <player>before editing config. - Values stay stale until reopen. The refresh task never started, or an inventory close listener cancelled it too early.
- TPS drops with menus open. Refresh interval too low or per-slot parsing. Raise the interval and cache the repeated value.
- Discount never applies. Metadata key misspelled, or a requirement compares a number to a string. Compare strings or use a math expansion.
Expert tip: keep a debug item in the menu that renders raw placeholder output as its lore. When staff report a broken menu, you open it once and see exactly what the server produced instead of guessing from a colored name. The same parse rules apply to [How to Set Up ShopGUI+ for a GUI-Based Server Shop] if your cosmetics are sold rather than unlocked.
Frequently Asked Questions
It is a GUI where item names, lore, prices, and lock states are filled in by PlaceholderAPI at runtime, so the same menu shows different content for each player based on permissions, rank, and economy balance.
Use /papi parse me followed by the placeholder, for example /papi parse me %player_name%. To test as another player use /papi parse with their name, and use /papi bcparse to broadcast the parsed result to all online players.
Refresh on open for static values, and use a repeating task around every 40 ticks, or two seconds, for values that change while the menu is open. Avoid per-tick parsing, which multiplies parse calls by slot count and can cost measurable tick time.
The expansion that registered them is missing, outdated, or was renamed by the update. Run /papi list to confirm, reinstall the expansion with /papi ecloud download, then restart the server so dependent plugins register in the correct order.
Conclusion
A dynamic cosmetic menu is not a hard build. It is five habits: install only the expansions you parse, test strings in chat before you test them in a GUI, read ownership from permissions, keep rank logic in one place, and refresh at the slowest interval players tolerate. Do those and the menu keeps telling the truth after every update.
Start today by listing every placeholder in your menu config. Anything that does not resolve with /papi parse me is a bug you just found before a player did. Then set a forty-tick refresh on the items that need it, and cancel that task on close.

Leave a Reply