Skip to main content

Nexal Titles

A comprehensive title system for Minecraft servers featuring unlockable titles that can be displayed in chat and above player heads. Titles can be earned through achievements (both custom statistics and vanilla Minecraft advancements), purchased using Vault economy, or granted via permissions.

Features​

  1. Title Storage - Dual storage system supporting both JSON and SQL (MariaDB/MySQL) for player titles, active selections and statistics
  2. Config-Based Title Definitions - Define unlimited titles in config.yml with display names, descriptions, rarities, categories, and unlock requirements
  3. Custom Achievement Tracking - Automatically track player statistics including kills, deaths, playtime, blocks mined/placed, and mobs killed to unlock achievement-based titles
  4. Vanilla Advancement Integration - Unlock titles when players complete specific Minecraft advancements (e.g., kill Ender Dragon, obtain Elytra)
  5. Purchasable Titles - Allow players to buy titles using Vault economy with configurable prices and price format ($1,000); every purchase asks for a second click to confirm. Vault is optional: without it everything else works
  6. Permission-Based Titles - Auto-grant titles based on permissions with periodic checking for VIP/donor ranks
  7. Interactive GUI Browser - Category-based GUI for browsing and selecting titles with real-time progress tracking
  8. Title Descriptions - Free text for each title (lore, memes, who won it and when), shown in the GUI for every title, as a chat hover (Paper and Spigot) and through placeholders
  9. Scoreboard Team Display - Show active titles above player heads using scoreboard teams with configurable prefix/suffix positioning
  10. PlaceholderAPI Integration - 15+ placeholders for displaying titles, descriptions, prices, stats, and progress in other plugins
  11. Admin Menu - /titles admin opens a menu with the settings, every title (edit its name, description and category), every player's titles and statistics (grant and revoke with a click), reload, export and import
  12. Title Categories - Organize titles into categories (Starter, Achievement, Purchase, VIP, Event, Special, Admin)
  13. Rarity System - Five rarity tiers (Common, Uncommon, Rare, Epic, Legendary) with color-coded display
  14. Export/Import System - Migrate titles and statistics between JSON and SQL storage with simple export/import commands
  15. Hot-Reload Support - Config and message files support hot-reload without server restart

Technologies​

  • Paper API 1.20.1+ - Minecraft server API (Spigot compatible)
  • Utilizer v0.21.2 - Configuration, messages, items, tasks, and GUI framework
  • ACF (Aikar Command Framework) - Annotation-based command system
  • nexal-core - JSON and SQL persistence with HikariCP connection pooling
  • Vault API - Economy integration for purchasable titles (optional)
  • PlaceholderAPI - Placeholder expansion for cross-plugin integration

Commands​

Player Commands​

CommandDescriptionPermission
/titlesOpen title browser GUI (the help from the console)nexaltitles.use
/titles select \<title>Select a title to displaynexaltitles.use
/titles unequipUnequip current titlenexaltitles.use
/titles listList all owned titlesnexaltitles.use
/titles stats [player]View a player's titles and progress, online or offlinenexaltitles.use
/titles helpShow help informationnexaltitles.use

Admin Commands​

Admin commands are hidden from players without the nexaltitles.admin permission.

CommandDescriptionPermission
/titles adminOpen the admin menu (the admin commands from the console)nexaltitles.admin
/titles admin helpShow the admin commandsnexaltitles.admin
/titles admin grant \<player> \<title>Grant a title to a player, online or offlinenexaltitles.admin
/titles admin revoke \<player> \<title>Revoke a title from a player, online or offlinenexaltitles.admin
/titles admin reloadReload configurationnexaltitles.admin
/titles admin exportExport titles and statistics to JSON filesnexaltitles.admin
/titles admin importImport titles and statistics from JSON filesnexaltitles.admin

Admin menu​

/titles admin opens a menu with:

  • Configuration - Chat prefix, above-head display and position, max length, title purchases, achievement tracking and save interval, rank titles and their check interval. Changes are saved to config.yml and apply at once.

  • Titles - Every title with its category, rarity, requirement and how many players own it. Click one to edit it:

    • Name - type the new name in chat (MiniMessage or & colour codes); cancel stops.
    • Description - left click and type it in chat, one line per message, then done; right click clears it.
    • Category - pick one of the seven categories.

    Changes are saved to config.yml and the titles reload at once, names above heads included.

  • Players - Every stored player, online first. Open one to see their statistics, left click a title to grant it and right click to revoke it.

  • Reload, Export Data and Import Data (shift click), as the commands above.

Permissions​

PermissionDescriptionDefault
nexaltitles.useUse basic title commands and GUItrue
nexaltitles.adminAccess admin commandsop
nexaltitles.title.vipUnlocks the VIP titlefalse
nexaltitles.title.mvpUnlocks the MVP titlefalse
nexaltitles.title.legendUnlocks the LEGEND titlefalse
nexaltitles.title.adminUnlocks the ADMIN titlefalse
nexaltitles.title.developerUnlocks the DEV titlefalse
nexaltitles.title.season3championUnlocks the S3 Champion titlefalse
nexaltitles.title.lagsurvivorUnlocks the Lag Survivor titlefalse

The nexaltitles.title.* nodes belong to the default permission titles in config.yml. The node of any permission title you add is registered as default: false too, so operators do not get rank titles unless you grant the node. Configs from before 1.0.2 used titles.\<name>; the plugin renames them to nexaltitles.title.\<name> on startup, so grant the new nodes in your permissions plugin.

Placeholders​

Requires PlaceholderAPI.

PlaceholderDescription
%titles_prefix%Active title (as prefix)
%titles_suffix%Active title (as suffix)
%titles_display%Active title with brackets [Title]
%titles_description%Active title's description, lines joined with a newline (for a hover)
%titles_description_\<titleid>%Any title's description, lines joined with a newline
%titles_price_\<titleid>%Price of a purchasable title, as economy.price-format shows it
%titles_active%Active title ID or "none"
%titles_count%Number of titles owned
%titles_total%Total number of titles available
%titles_percentage%Percentage of titles owned (e.g., "45.2")
%titles_stat_player_kills%Player kill count
%titles_stat_player_deaths%Player death count
%titles_stat_playtime%Playtime in seconds, the current session included
%titles_stat_blocks_mined%Blocks mined count
%titles_stat_blocks_placed%Blocks placed count
%titles_stat_mobs_killed%Mobs killed count
%titles_has_\<titleid>%Check if player has title (true/false)
%titles_is_active_\<titleid>%Check if title is active (true/false)

Title Descriptions​

Every title can have a description: free text, one line or a list, in MiniMessage or & colour codes. Use it for the server's lore, a joke, or the story of the title ("Winner of Season 3", "Crowned on 30 June 2026"). It shows:

  • in the /titles GUI, under every title, locked or not, so players can browse them all;
  • in the unlock announcement (%description% in title-unlocked);
  • as a hover on the title in chat, when display.chat-prefix is on (see below);
  • through %titles_description% (the equipped title) and %titles_description_\<titleid>%.

The chat hover on Paper. The plugin wraps Paper's chat renderer, so the format another chat plugin set is kept and nothing is cancelled or re-sent.

The chat hover on Spigot. A Spigot chat format is plain text and cannot carry a hover, so for a title with a description the plugin sends the chat line itself, as BungeeCord components:

  1. At HIGH priority the title goes in front of the format your chat plugin set, as for every title.
  2. At HIGHEST the plugin sends the finished line, the title with its hover, to the message's recipients, and empties the recipient list so the server does not send a plain copy as well. The message is not cancelled: the console, Discord bridges and chat logs still get it, title included.

What this means alongside other chat plugins on Spigot:

  • Cancelled messages are left alone. Chat plugins that cancel the message and send their own lines (ChatControl, VentureChat and similar) do not show the plugin's title; use %titles_prefix% in their format, as described below.
  • A listener that runs after Nexal Titles (MONITOR, or a HIGHEST one registered later) sees an empty recipient list, and a change it makes to the format or message no longer reaches players.
  • When a plugin replaces the format after the title went in, or fixes the recipient list so it cannot be changed, the line goes out as the server sends it: without the hover, and without the title if the new format dropped it.
  • Titles without a description are only put in front of the format; the server sends those lines as usual.

Hover with your own chat plugin. If your chat plugin draws the title itself from %titles_prefix%, turn display.chat-prefix off and give that part %titles_description% as its hover text, in chat plugins that support hovers (ChatControl, VentureChat, CarbonChat and similar tooltip or hover settings). EssentialsX Chat formats plain text and has no hovers.

Admins can edit descriptions in game: /titles admin > Titles > a title > Description.

Configuration​

config.yml​

# Storage settings
storage:
type: json # json or sql
sql:
host: localhost
port: 3306
database: minecraft
username: root
password: ""

# Achievement tracking settings
achievements:
enabled: true
# Save statistics, add the current session to playtime and check playtime titles (Veteran)
# every 1200 ticks (60 seconds). Statistics are also saved when a player leaves and on shutdown.
save-interval: 1200

# Title display settings
display:
chat-prefix: true # equipped title in front of chat messages (turn off if your chat plugin uses %titles_prefix%)
above-head: true # turning it off removes the titles already shown
above-head-format: prefix # prefix, suffix, or both
max-length: 16 # visible characters above heads; colour codes do not count, 0 = no limit

# Economy settings
economy:
enabled: true # off (or no Vault): purchasable titles show "Purchases are disabled"
# How prices show in the GUI, messages and placeholders
price-format: "$%amount%" # e.g. "%amount% coins" or "€%amount%"
number-format: "#,##0.00" # Java DecimalFormat: , groups thousands, .00 two decimals
hide-zero-decimals: true # $1,000 rather than $1,000.00 (fractions keep them: $1,000.50)

# Permission-based titles
permission-titles:
auto-grant: true
check-interval: 6000 # Check every 5 minutes

# Title categories (display order in GUI)
categories:
- STARTER
- ACHIEVEMENT
- PURCHASE
- VIP
- EVENT
- SPECIAL
- ADMIN

# Title definitions
titles:
warrior:
display-name: "&cWarrior"
description: "&7Kill 100 players"
category: ACHIEVEMENT
rarity: UNCOMMON
requirement:
type: ACHIEVEMENT
achievement: PLAYER_KILLS
amount: 100

dragon_slayer:
display-name: "&5&lDragon Slayer"
# Optional free text: one line, or a list of lines (MiniMessage or & codes)
description:
- "\<dark_purple>Few who enter the End return.\</dark_purple>"
- "\<dark_purple>Fewer return with its breath in a bottle.\</dark_purple>"
category: ACHIEVEMENT
rarity: EPIC
requirement:
type: ADVANCEMENT
advancement: minecraft:end/kill_dragon
display: "Defeat the Ender Dragon" # optional: how the requirement reads to players

season3_champion:
display-name: "&6&lS3 Champion"
description:
- "\<gold>Winner of Season 3\</gold>"
- "\<gray>Crowned on 30 June 2026\</gray>"
category: EVENT
rarity: LEGENDARY
requirement:
type: PERMISSION # nobody has the node: hand it out with /titles admin grant
permission: nexaltitles.title.season3champion
display: "Win Season 3"

rich:
display-name: "&6&lRich"
description: "&7Show off your wealth"
category: PURCHASE
rarity: UNCOMMON
requirement:
type: PURCHASE
price: 10000

vip:
display-name: "&a&lVIP"
description: "&7Very Important Player"
category: VIP
rarity: RARE
requirement:
type: PERMISSION
permission: nexaltitles.title.vip

# Rarity colors
rarity-colors:
COMMON: "&7"
UNCOMMON: "&a"
RARE: "&9"
EPIC: "&5"
LEGENDARY: "&6"

messages.yml​

All messages are fully customizable with MiniMessage (<gradient:...>, \<bold>), legacy color codes (&a-&f), hex colors (&#FFFFFF), sounds, and titles. The colors are listed at the top of the file for find-and-replace. Key message sections include:

  • Title selection and unequip messages
  • Title unlock notifications: a single achievement or advancement unlock gets the full announcement (title-unlocked, with on-screen title and sound); several at once share one line (titles-unlocked-multiple); rank titles granted from permissions, typically on join, are listed in one line (permission-titles-unlocked)
  • Purchase confirmation (confirm-purchase), results and errors. %price% and %balance% already carry the currency from economy.price-format; a $ written before them in an older messages.yml is removed on startup
  • Admin command feedback, export and import
  • The admin title editor's chat prompts (title-edit-*)
  • GUI titles (gui-title-browser, gui-title-category) and category names (category-names)
  • Statistics display

Messages added by a newer version are added to an existing messages.yml on startup.

Example message with title and sound:

title-unlocked:
message:
- ""
- " <gradient:#E91E63:#F06292>\<bold>✦ NEW TITLE UNLOCKED ✦\</bold>\</gradient>"
- ""
- " %title%"
- " \<gray>%description%\</gray>"
- ""
title: "<gradient:#E91E63:#F06292>\<bold>NEW TITLE!\</bold>\</gradient>"
subtitle: "%title%"
sound: "UI_TOAST_CHALLENGE_COMPLETE"
fade-in: 10
stay: 80
fade-out: 20

Database Schema​

With JSON storage (storage.type: json) everything lives in data.json, under the keys titles_player_data and titles_player_stats. With SQL storage (storage.type: sql), the plugin creates the following tables:

titles_player_data​

Stores player title ownership and active selections.

ColumnTypeKeyDescription
playerIdVARCHAR(36)PKPlayer UUID
active_titleVARCHAR(64)Currently equipped title ID (nullable)
owned_titlesTEXTJSON array of owned title IDs
created_atBIGINTCreation timestamp
updated_atBIGINTLast modification timestamp

titles_player_stats​

Stores player achievement statistics.

ColumnTypeKeyDescription
playerIdVARCHAR(36)PKPlayer UUID
player_killsINTNumber of players killed
player_deathsINTNumber of deaths
playtime_secondsBIGINTTotal playtime in seconds
blocks_minedINTNumber of blocks mined
blocks_placedINTNumber of blocks placed
mobs_killedINTNumber of mobs killed
updated_atBIGINTLast update timestamp

Key Legend: PK = Primary Key

Title Requirement Types​

The plugin supports five types of title requirements:

NONE​

No requirement - title is available to everyone immediately: any player can equip it without being granted it. Free titles do not count as owned in /titles list, /titles stats and %titles_count%.

requirement:
type: NONE

PERMISSION​

Requires a specific permission node. Auto-granted when the player joins or at the next periodic check (permission-titles.check-interval) once they have the node. The node is default: false, so operators only get it when it is granted to them. Players who try to equip one without the node are told so (title-no-permission).

requirement:
type: PERMISSION
permission: nexaltitles.title.vip

PURCHASE​

Must be purchased with Vault economy. The first click in the GUI asks for confirmation; a second click within 10 seconds buys it. Nothing is granted if the economy plugin refuses the payment. Without Vault (or with no economy plugin registered with it) these titles cannot be bought: the GUI shows "Purchases are disabled" and a click answers with purchases-disabled.

requirement:
type: PURCHASE
price: 10000

ACHIEVEMENT​

Unlock by reaching a custom achievement threshold.

requirement:
type: ACHIEVEMENT
achievement: PLAYER_KILLS # PLAYER_KILLS, PLAYTIME, BLOCKS_MINED, BLOCKS_PLACED, MOBS_KILLED, PLAYER_DEATHS
amount: 100

ADVANCEMENT​

Unlock by completing a Minecraft advancement. Players see display as the requirement ("Defeat the Ender Dragon"), not the advancement key. Without display, the first line of the description is used, as in configs from before descriptions were free text. Any requirement type accepts display.

requirement:
type: ADVANCEMENT
advancement: minecraft:end/kill_dragon
display: "Defeat the Ender Dragon"

Storage Migration​

To migrate from JSON to SQL (or vice versa):

  1. Current storage: JSON (storage.type: json)
  2. Run /titles admin export - exports player titles and statistics to the export/ folder (player_titles.json, player_stats.json)
  3. Change config: storage.type: sql and configure SQL credentials
  4. Run /titles admin reload
  5. Run /titles admin import - imports data from the export/ folder
  6. Done! Now using SQL storage

The export/import system preserves owned titles, active selections and statistics. Both run off the main thread and report back when done.

Changing storage.type connects to the new storage off the main thread. If it cannot be reached (wrong credentials, database down), the current storage stays in use, the console says why, and /titles admin reload answers with reload-failed; fix the settings and reload again. A config.yml or messages.yml that is not valid YAML is reported the same way and not loaded.

Achievement Tracking​

The plugin automatically tracks the following player statistics:

  • Player Kills - Incremented when a player kills another player
  • Player Deaths - Incremented when a player dies
  • Playtime - Total time spent on the server (in seconds); the current session counts straight away, and playtime titles unlock while playing
  • Blocks Mined - Number of blocks broken
  • Blocks Placed - Number of blocks placed
  • Mobs Killed - Number of non-player entities killed

Statistics are saved off the main thread every achievements.save-interval (60 seconds by default), when players log out and when the server stops, including the session of every player online at a restart. Versions up to 1.0.2 kept statistics in player_stats.json; on startup they are moved into the current storage and the file is renamed to player_stats.json.migrated.

Installation​

  1. Ensure you have Paper or Spigot 1.20.1+ installed
  2. (Optional) Install Vault and an economy plugin (e.g., EssentialsX) to sell titles. Without Vault the plugin runs and everything but buying titles works; the console warns once on startup
  3. (Optional) Install PlaceholderAPI for placeholder support
  4. Download NexalTitles-X.X.X.jar
  5. Place in /plugins folder
  6. Start/restart server
  7. Configure config.yml and messages.yml, or use /titles admin
  8. Reload with /titles admin reload

Building from Source​

# From repository root
cd plugins/nexal-titles
mvn clean package

# Output: target/NexalTitles-X.X.X.jar