UltraRanks
Cosmetic tags, a rank shop, a rankup ladder and prestige. One plugin, built on LuckPerms.
Most servers end up running three plugins for this: one for tags, one for rankup, one to sell ranks. They each keep their own data, they each have their own menu style, and none of them talk to each other.
UltraRanks does all of it, and keeps every bit of player data in LuckPerms rather than in a store of its own.
That last part is the whole idea. Your equipped tag is a LuckPerms prefix. An owned tag is a permission node. A rented rank is a temporary inheritance node with its own expiry. Prestige is a meta value. So:
- Your LuckPerms backup already contains every bit of player data. There is no second copy to lose.
- Put LuckPerms on MySQL and tags, ranks and prestige follow players across your whole network. No config here, it just works.
- Staff grant a tag with /lp user Steve permission set ultraranks.tag.ninja. Crates, vote listeners and web stores can do the same.
- A rental expires even if the server is offline, because LuckPerms owns the expiry.
To be straight about it: the plugin does have a storage section, and it does support MySQL. That is not for player data - it is for the handful of things that belong to the server rather than to one player: the leaderboard, running boosters, purchase cooldowns and the audit log. Those sit in flat files out of the box and you never have to touch them. Point them at a database only if you want them shared across a network. Details at the bottom.
The requirement engine
Every requirement in the plugin uses the same syntax, and it works in rankup.yml, ranks.yml, tags.yml and on prestige:
Code (YAML):
requirements
:
- "money:250000" # withdrawn on success
- "playtime:20h"
- "blocks:STONE:10000" # any block, or one specific block
- "mobs:ZOMBIE:100"
- "kills:25" # also deaths, level, exp, prestige
- "statistic:FISH_CAUGHT:100" # any Bukkit statistic
- "statistic:USE_ITEM:BOW:500"
- "permission:!blacklist.rankup" # ! negates
- "placeholder:%jobs_total_level%>=50" # any PlaceholderAPI value
- "money:250000" # withdrawn on success
- "playtime:20h"
- "blocks:STONE:10000" # any block, or one specific block
- "mobs:ZOMBIE:100"
- "kills:25" # also deaths, level, exp, prestige
- "statistic:FISH_CAUGHT:100" # any Bukkit statistic
- "statistic:USE_ITEM:BOW:500"
- "permission:!blacklist.rankup" # ! negates
- "placeholder:%jobs_total_level%>=50" # any PlaceholderAPI value
Rewards use the same idea:
Code (YAML):
rewards
:
- "money:15000"
- "item:NETHERITE_INGOT:2"
- "command:crate give %player% legendary 1"
- "permission:ultraranks.tag.veteran"
- "group:builder"
- "title:&6RANK UP;&7You are now Veteran"
- "broadcast:&e%player% reached Veteran"
- "sound:ENTITY_PLAYER_LEVELUP:1:1.2"
- "money:15000"
- "item:NETHERITE_INGOT:2"
- "command:crate give %player% legendary 1"
- "permission:ultraranks.tag.veteran"
- "group:builder"
- "title:&6RANK UP;&7You are now Veteran"
- "broadcast:&e%player% reached Veteran"
- "sound:ENTITY_PLAYER_LEVELUP:1:1.2"
What's in it
Tags
Paginated menu with categories and rarities. Tags set a prefix, a suffix or both. Unlock them with a permission, or put a price on them and let players buy them in game. Buyable tags can carry their own requirements, so a premium tag can still demand playtime instead of being pure pay to win. Tags can also be rented for a duration, same as ranks.
Rank shop
Rent ranks by duration or sell them permanently. Gifting, per offer sales, permission based discounts ( ultraranks.discount.25), purchase cooldowns and confirmation screens above a price you choose. Buying a rank you already rent extends the time you have left instead of overwriting it. Players get warned before a rental runs out, and you decide what happens when it does.
Rankup ladder
Unlimited rungs. /rankup max climbs as far as the player can afford in a single transaction: one balance check, one withdrawal, one permission write, rewards for every rung passed. The ladder menu shows live progress bars and a tick/cross checklist per requirement.
Auto rankup
A background check promotes players the moment they qualify. Players toggle it themselves with /autorankup. If you'd rather keep /rankup a deliberate action, set notify-only and it just nudges them on the action bar instead.
Prestige and seasons
At the top of the ladder players reset for a permanent prestige level, and every numeric requirement is multiplied after that, linear or compounding. /ultraranks season reset pays out the top players first, then puts everyone back at the start. Cosmetic tags are never touched by a reset.
Boosters
Server wide events. A weekend booster that halves rankup costs and takes 20% off the shop, or one you start by hand for an hour. A booster only discounts what a player actually pays: playtime and blocks are never scaled down, because a sale shouldn't cheapen effort somebody already put in.
Leaderboard
/rankup top and /prestige top, ranked by prestige then ladder position. Exposed as placeholders for holograms and scoreboards.
Bedrock support
If you run Geyser and Floodgate, Bedrock players get a native form instead of a chest menu. Chest GUIs misalign on Bedrock and phone players can't hover to read item lore at all, so the line that matters (price, LOCKED, CLICK TO BUY) is folded into the button label, and confirmations become a proper two button modal. Servers without Floodgate are unaffected.
Signs and vouchers
Put [UltraRanks] on a sign with rankup, tags, shop or top underneath. Vouchers are items that grant a tag or rank on right click, which is how you hand ranks out from crates and giveaways. What a voucher is worth lives in the item's persistent data, not its name, so renaming it in an anvil does nothing.
Audit log
Every purchase, rankup, prestige and refund is written down and readable with /ultraranks log. When somebody says the shop ate their coins, you have an answer.
Menus are data
Size, title, background, buttons, and what each button does all live in menus.yml. Buttons can be hidden behind a permission or behind any requirement string, so the menu reacts to the player. Gradients work everywhere: <gradient:#7B2FF7:#00D4FF>ASCENDANT</gradient>
Commands
| Command | What it does | Permission |
| /tags [tag|category|off|list] | Tag menu, or equip directly | ultraranks.command.tags |
| /ranks [rank] | Rank shop, or buy directly | ultraranks.command.ranks |
| /ranks gift <player> <rank> | Buy a rank for someone else | ultraranks.command.ranks |
| /ranks stats | Your progress and spending | ultraranks.command.ranks |
| /rankup | Advance one rank | ultraranks.command.rankup |
| /rankup max | Climb as far as you can afford | ultraranks.command.rankup.max |
| /rankup menu | The ladder screen | ultraranks.command.rankup |
| /rankup top | Leaderboard | ultraranks.command.rankup |
| /prestige [info] | Reset for a prestige level | ultraranks.command.prestige |
| /autorankup [on|off] | Toggle automatic promotion | ultraranks.autorankup |
| /ultraranks help | Help screen, filtered by permission | none |
Admin, all under ultraranks.admin:
Code (Text):
/ur reload reload every config file
/ur info plugin and hook status
/ur debug full diagnostic dump
/ur log [player] [lines] transaction log
/ur tag give|take|set|clear <player> [tag]
/ur rank give|take <player> <rank> [duration]
/ur rankup <player> [steps] negative steps rank down
/ur prestige set <player> <level>
/ur voucher tag|rank <id> [amount] [player]
/ur booster list|start <id> <time>|stop
/ur season info|reset confirm
/ur leaderboard [reset]
/ur storage [migrate <yaml|sql>]
/ur info plugin and hook status
/ur debug full diagnostic dump
/ur log [player] [lines] transaction log
/ur tag give|take|set|clear <player> [tag]
/ur rank give|take <player> <rank> [duration]
/ur rankup <player> [steps] negative steps rank down
/ur prestige set <player> <level>
/ur voucher tag|rank <id> [amount] [player]
/ur booster list|start <id> <time>|stop
/ur season info|reset confirm
/ur leaderboard [reset]
/ur storage [migrate <yaml|sql>]
| Node | Default | Notes |
| ultraranks.command.tags | true | |
| ultraranks.command.ranks | true | |
| ultraranks.command.rankup | true | |
| ultraranks.command.rankup.max | true | Chained rankups |
| ultraranks.command.prestige | true | |
| ultraranks.autorankup | true | Eligible for automatic promotion |
| ultraranks.admin | op | All admin commands, sign creation, update notices |
| ultraranks.tag.<id> | false | Owning a tag. ultraranks.tag.* grants all |
| ultraranks.discount.<percent> | false | e.g. ultraranks.discount.25 pays 75%. Highest wins, capped in config |
Placeholders
Code (Text):
%ultraranks_tag% %ultraranks_tag_raw% %ultraranks_tag_id%
%ultraranks_tag_name% %ultraranks_suffix%
%ultraranks_tags_unlocked% %ultraranks_tags_total%
%ultraranks_rank% %ultraranks_rank_group% %ultraranks_rank_index%
%ultraranks_next_rank% %ultraranks_next_rank_group%
%ultraranks_next_rank_cost% %ultraranks_rankup_ready%
%ultraranks_rankup_progress% %ultraranks_rankup_progress_bar%
%ultraranks_prestige% %ultraranks_prestige_max% %ultraranks_prestige_multiplier%
%ultraranks_ladder_size% %ultraranks_ladder_position%
%ultraranks_discount% %ultraranks_autorankup%
%ultraranks_position% %ultraranks_leaderboard_size%
%ultraranks_top_1_name% %ultraranks_top_1_rank% %ultraranks_top_1_prestige%
%ultraranks_booster% %ultraranks_booster_time% %ultraranks_booster_discount%
%ultraranks_remaining_<offer>% %ultraranks_owns_<offer>%
%ultraranks_tag_name% %ultraranks_suffix%
%ultraranks_tags_unlocked% %ultraranks_tags_total%
%ultraranks_rank% %ultraranks_rank_group% %ultraranks_rank_index%
%ultraranks_next_rank% %ultraranks_next_rank_group%
%ultraranks_next_rank_cost% %ultraranks_rankup_ready%
%ultraranks_rankup_progress% %ultraranks_rankup_progress_bar%
%ultraranks_prestige% %ultraranks_prestige_max% %ultraranks_prestige_multiplier%
%ultraranks_ladder_size% %ultraranks_ladder_position%
%ultraranks_discount% %ultraranks_autorankup%
%ultraranks_position% %ultraranks_leaderboard_size%
%ultraranks_top_1_name% %ultraranks_top_1_rank% %ultraranks_top_1_prestige%
%ultraranks_booster% %ultraranks_booster_time% %ultraranks_booster_discount%
%ultraranks_remaining_<offer>% %ultraranks_owns_<offer>%
- Minecraft 1.20 to 26.2, Spigot / Paper / Purpur. One jar for all of it, no NMS.
- Java 17+
- LuckPerms
- Vault plus an economy plugin (EssentialsX, CMI, whatever you use)
- PlaceholderAPI
- Optional: Floodgate for Bedrock forms
Setup
- Drop the jar in plugins/ and start the server.
- Create the groups the default config references: /lp creategroup miner and so on. The console tells you exactly which ones are missing on startup, so you can't get this wrong quietly.
- Edit the configs and run /ur reload.
6 config files, split by what they do: config.yml, tags.yml, ranks.yml, rankup.yml, menus.yml and lang/.
14 languages ship with it, translated properly rather than run through a machine: English, Slovak, Czech, Polish, Russian, Ukrainian, German, French, Spanish, Portuguese, Italian, Dutch, Turkish and Simplified Chinese. Set general.language and you are done - or copy any file and write your own. Anything a translation misses falls back to English, so a half finished translation still works, and an update that adds messages never breaks the file you already translated.
Storage
On a single server you can skip this section entirely. It ships on flat files, works out of the box, and nothing here needs configuring.
Player data is in LuckPerms, as described at the top. What this section covers is the leftovers - the leaderboard, running boosters, purchase cooldowns and the audit log - because those belong to the server, not to one player, and LuckPerms has nowhere sensible to put them.
Flat files by default. If you run a network and want those shared, point storage.type at mysql, mariadb, sqlite or postgres. Connections come from HikariCP, and the MySQL driver is declared in plugin.yml so the server fetches it once by itself - there is no jar for you to hunt down.
Switching backends only redirects new data, so /ur storage migrate yaml copies across what you already have and leaves the source untouched. That means you can run it twice, or run it back the other way, without losing anything.
If the database is unreachable at startup the plugin falls back to flat files and says so in the console rather than refusing to load. Worth knowing: connectTimeout in storage.sql.properties is how long a dead database delays your startup before that fallback kicks in.
For developers
Code (Java):
RegisteredServiceProvider
<UltraRanksAPI
> rsp
=
Bukkit. getServicesManager ( ). getRegistration (UltraRanksAPI. class ) ;
UltraRanksAPI api = rsp. getProvider ( ) ;
api. setTag (player, api. getTag ( "ninja" ) ) ;
double percent = api. getRankupProgress (player ) ;
Bukkit. getServicesManager ( ). getRegistration (UltraRanksAPI. class ) ;
UltraRanksAPI api = rsp. getProvider ( ) ;
api. setTag (player, api. getTag ( "ninja" ) ) ;
double percent = api. getRankupProgress (player ) ;
Notes
Every LuckPerms write goes through modifyUser, which runs off the main thread. Economy checks and withdrawals run async too, so an SQL backed economy can't stall your tick. A per player lock covers the whole check, withdraw and permission write sequence, so spam clicking a menu can't pass one balance check twice. If a permission write fails after payment, the money goes back and the player is told.
This is free. If it saves you setting up three plugins, leave a review, that's payment enough. Bug reports and feature requests go in the discussion tab and I read all of them.
UltraRanks is a free Minecraft Java mod. Compatible with Minecraft 1.20, 1.20.6, 1.21, 26.1 and newer. Downloaded 4 times (via Spigot). Download it and open it directly in the game.