The catalogue
How the files under cosmetics/ work: one per type, the fields every entry shares, the colour grammar, and what happens when an entry is wrong.
Everything a player can wear is declared in plugins/ExyliaChatCosmetics/cosmetics/, one file per
type. They are ordinary YAML, they are meant to be edited by hand, and /cca reload reads them again.
They are deliberately not schema-managed the way config.yml is. A catalogue is curated: it has
section banners, a description written as a sentence in one entry and as a list in the next, comments
explaining why an id is spelled the way it is. A schema writer round-trips the file through a loader
that keeps none of that. config.yml belongs to the schema; these files belong to whoever edits them.
One file per type
| Type id | File | Section | Worn |
|---|---|---|---|
tag | tags.yml | tags | one |
nick_color | nick-colors.yml | nick_colors | one |
rank_color | rank-colors.yml | rank_colors | one |
chat_color | chat-colors.yml | chat_colors | one |
shadow_color | shadow-colors.yml | shadow_colors | one |
font | fonts.yml | fonts | one |
modifier | modifiers.yml | modifiers | several at once |
customtag | — player rows | — | one |
customcolor | — player rows | — | one |
customnick | — player rows | — | one |
customrank | — player rows | — | one |
The four custom* types have no file: they are what players write for themselves, one database row
each, and they are documented in Custom cosmetics.
cosmetics/animations.yml sits in the same folder but is not a type. It is a library of movement any
other entry names through animation: — see Animations.
How a cosmetic is named
One cosmetic is type:id everywhere — in a grant, a favourite, a loadout, a placeholder, a command:
tag:mvp, chat_color:aurora, customtag:42. One shape means a value written by any of them is
readable by all of them.
Both halves are normalised on the way in: trimmed, lower-cased, spaces turned into underscores, and
anything that is not a-z, 0-9, _ or - dropped. So MVP and mvp are the same id, and a row
an admin wrote years ago as Tag:MVP still resolves. An id that normalises to nothing at all is
reported and skipped — there is no cosmetic left to name.
Tabs
Every file opens with a categories section. Each entry there is one tab of the menu that browses
that type, and priority is the order the tabs sit in, lowest first.
categories:
symbols:
name: '{primary}&lSYMBOLS'
icon: NAME_TAG
priority: 1
premium:
name: '{primary}&lPREMIUM'
icon: NETHER_STAR
priority: 7A category id is normalised like every other key. icon accepts material: as its other spelling,
and falls back to PAPER. An entry that names a category no one declared is still loaded — it is
simply not reachable from a tab, and a command can still grant it — but the mistake is reported so you
can see it.
What every entry declares
Whatever the type, these are the keys the menu, the ownership check and the placeholders read without knowing what kind of cosmetic they are looking at.
| Key | Default | Means |
|---|---|---|
category | other | the tab it lives in |
name | the id | what the menu calls it |
icon | NAME_TAG | the item drawn in the menu; material: is accepted for it |
description | none | one line or a list; lore: is accepted for it |
priority | 999 | order inside the tab, lowest first; ties break on the id |
permission | true | whether exyliachatcosmetics.<type>.<id> owns it |
requirement | none | read from the file and carried on the definition, but nothing evaluates it yet — see below |
hidden | false | keeps it out of the menu; a grant still equips it |
metadata | none | free key: value pairs another plugin can read |
tags:
mvp:
category: premium
name: '{primary}&lMVP'
icon: GOLD_INGOT
description: 'Reserved for the {highlight}top players.'
priority: 1
permission: true
hidden: false
metadata:
season: '2026'Every entry may carry a requirement, it is read without complaint and it reaches the definition —
but no code path evaluates it in 1.0.0. What actually keeps a cosmetic out of a menu is hidden, and
what decides whether somebody may wear one is ownership. Write the key if you like; expect nothing
from it until a release says otherwise.
name and description are parsed like any other Exylia text: palette tokens, & codes and
MiniMessage all work in them.
On top of these, each type adds the keys that make it that type — display and head for a tag,
color or gradient for a colour, alphabet for a font, decoration for a modifier. Those live on
the pages for tags, colours,
fonts and custom cosmetics.
permission defaults to true in every file but rank-colors.yml, where a rank colour is usually
owned by the rank rather than bought. Written out in Colours.
Ownership by permission reads exyliachatcosmetics.<type>.<id>,
exyliachatcosmetics.<type>.category.<category>, exyliachatcosmetics.<type>.* or
exyliachatcosmetics.*. Setting permission: false leaves only grants —
see Entitlements.
The colour grammar
Wherever a colour is written — color:, a stop inside gradient:, a colour a player mixes,
chat.default-color — it is read the same way.
| Written | Means |
|---|---|
#8a51c4 | one colour, as six hex digits after a hash |
a51c4, 8a51c4, <#8a51c4> | the same colour, in the other shapes people type it |
gold, red, dark_aqua | one of the sixteen named colours |
{primary}, {accent}, {highlight} | an ExyliaLib palette token, which follows your colors.yml |
#a:#b | a gradient from one colour to the other |
#a:#b:#c | a gradient through three stops, and so on |
gradient:#a:#b | the same, spelled out |
<gradient:#a:#b> | the same again, for anyone who writes MiniMessage by habit |
In a file, color: takes one of those as a string and gradient: takes a list of them; a list of one
is read as a solid colour rather than refused. A hash has to be followed by exactly six digits — #12
is not a short hex, it is a mistake, and it is reported as one.
Because palette tokens are resolved when the file is read, a cosmetic written with {primary} follows
your server's palette rather than a fixed hex value: change colors.yml, reload, and it changes with
it.
shadow-colors.yml is the exception. A shadow is not painted with a gradient, and it can be asked to
follow the message rather than name a colour of its own, so its color: accepts #rrggbbaa, auto,
auto:0.5 and none as well. The full table is in Colours.
When an entry is wrong
Nothing in a catalogue is thrown. Every problem is reported with the path it came from and the entry is skipped, because one mistyped tag must not cost a server its other two hundred.
A report names the file, the entry and the field:
cosmetics/tags.yml tags.mvp.display: is missing, so there is nothing to draw
cosmetics/rank-colors.yml rank_colors.azure.color: cannot be read: 'blurple'
cosmetics/tags.yml tags.event: names the category 'seasonal', which is not declaredWhat each kind of problem costs you:
| Problem | What happens |
|---|---|
| A field an entry cannot do without is missing or unreadable | that entry is skipped, the rest of the file loads |
| An entry is not a section, or its id normalises to nothing | the same |
| It names a category nobody declared | it is kept and loaded, but no tab lists it |
The categories section is missing | the file loads with no tabs |
| The entries section is missing | the server has none of that type |
A type's catalogue is published as one snapshot and replaced whole, so a reload never leaves a menu showing half a file.
Reloading
/cca reload re-reads the configuration, every catalogue, the animations and the menus. Players stay
online, what they are wearing stays on, and a menu that was open picks up the new palette.
The problems above are warnings, not errors: the server keeps running and the file keeps most of its entries. If a cosmetic you just wrote is not in the menu, the reason is one line in the console.
Per type
display, head, format, and what a tag may draw on which client.
Nick, chat, shadow and rank colours, and which one wins where.
Fonts and modifiersAlphabets, digits, extra pairs, and the five decorations.
AnimationsThe six kinds of movement any entry can name with animation:.
The four types with no file: what players write for themselves.
EntitlementsPermissions, grants that expire, and the difference between owning and wearing.
Something missing on this page? Tell us on Discord