NPC skins
How Minecraft skins are stored as signed texture data, how to generate that data for any image with MineSkin, and how to apply it to Citizens NPCs (a server plugin) as well as to datapack-native NPCs — the minecraft:mannequin entity and player heads.
Citizens is a server plugin (Spigot/Paper), not a data pack — its /npc commands only exist on a server running the plugin. The skin data (texture value + signature), however, is the same everywhere, so the same MineSkin output drives a Citizens NPC, a datapack mannequin, or a player_head. Where a step is plugin-only vs. vanilla/datapack is called out below.
How a Minecraft skin is stored
A skin is not an image attached to an entity — it is a profile property named textures, carried by any player-shaped thing:
value— a Base64-encoded JSON blob that points at a hosted skin image (ahttps://textures.minecraft.net/texture/<hash>URL) plus the model (classicorslim).signature— a Yggdrasil (Mojang) signature over that value. Optional in many contexts, but required whenever the server enforces secure profiles (enforce-secure-profile=true) — without it those servers fall back to the default Steve/Alex.
So "giving an NPC a custom skin" always reduces to obtaining a value + signature pair and putting it where that NPC reads its profile. MineSkin exists to produce that pair from any image.
Generating signed skins with MineSkin
MineSkin signs skins for you: upload a skin PNG (or point it at an existing skin/URL) and it returns a signed texture — the value, the signature, a textures.minecraft.net URL, and a UUID.
The MineSkin skins gallery is a large, browsable repository of already-signed skins. For a lot of NPCs you never need to make your own image: search/scroll the gallery, open a skin, and copy its ready-made URL (for Citizens --url) or its value + signature (for a mannequin/head or Citizens -t). Each gallery entry is already signed, so it drops straight into any of the steps below.
- Open mineskin.org, upload your skin image (or pick one from the gallery), and choose the model (classic / slim).
- Copy the outputs you need: the URL (easiest for Citizens'
--url), or the value + signature (for raw application to Citizens-t, mannequins, or heads).
MineSkin also has an API — POST an image and store the returned data.texture.value / data.texture.signature in a config, so a plugin or generator can set skins programmatically instead of by hand.
Applying a skin to a Citizens NPC plugin
Select the NPC (/npc select or look at it), then use one of the /npc skin forms. Easiest for a MineSkin custom skin is --url; Citizens signs and caches it for you:
# From a player's account skin
/npc skin Notch
# Always track that player's *latest* skin (lowercase L)
/npc skin Notch -l
# From an image or texture URL (MineSkin output) — recommended for custom skins
/npc skin --url https://textures.minecraft.net/texture/<hash>
# Raw signed blob (developer/console tool): uuid, value, signature
/npc skin -t <uuid> <value> <signature>
/npc skin <name>copies a real account's skin; add-lto keep following that account's updates./npc skin --url <image>takes a MineSkin (or texture) URL — the simplest path for a custom skin./npc skin -t <uuid> <value> <signature>sets the raw signed texture; it's a long blob, so run it from the console (or a command block) rather than chat. Use thevalue/signaturestraight from MineSkin.
Only player-type NPCs render a skin. Citizens stores the skin in the NPC's data so it persists across restarts; changing it re-signs/re-caches. If a skin "won't change", it's usually a stale cache — re-run with --url or restart to clear it.
Applying a skin to a datapack NPC (mannequin / head)
For vanilla, datapack-native NPCs there is no plugin — you put the same MineSkin value/signature into the minecraft:profile profile property. This works for the minecraft:mannequin entity 1.21.9+ and for player_head items:
# A mannequin wearing a MineSkin custom skin
/summon minecraft:mannequin ~ ~ ~ {profile:{properties:[
{name:"textures", value:"<base64 value from MineSkin>", signature:"<signature>"}
]}}
# A player head with the same skin (data-component form)
/give @s minecraft:player_head[profile={properties:[
{name:"textures", value:"<value>", signature:"<signature>"}
]}]
If you just want an existing account's skin (not a custom image), the short form profile:{name:"Notch"} resolves it — no MineSkin needed. MineSkin is for custom images that aren't tied to a real account.
Which mobs can wear a player skin
A captured skin is a texture in the player (humanoid) UV layout. Whether a mob can wear one
is decided by a single question: does its model descend from HumanoidModel? Read out of the 26.2
client jar:
PlayerModel extends HumanoidModel<AvatarRenderState>
AbstractZombieModel extends HumanoidModel // zombie, husk, drowned, zombie_villager
SkeletonModel extends HumanoidModel // skeleton, stray, bogged, wither_skeleton, parched
VillagerModel extends EntityModel // NOT humanoid
IllagerModel extends EntityModel // NOT humanoid
WitchModel extends EntityModel // NOT humanoid
- Can wear a player skin:
mannequin(aMannequinis anAvatar, rendered byPlayerModel), the whole zombie family and the whole skeleton family. For these, a PlanetMinecraft-style PNG drops intoassets/minecraft/textures/entity/…and simply works — no signing needed, because a mob texture is a resource-pack file, not a profile. - Cannot, ever: villagers, wandering traders, illagers (illusioner, evoker, vindicator, pillager) and witches. Their body proportions, crossed arms and texture coordinates are unrelated to the player layout, so a player skin lands the face on the nose and the legs on the robe. The only way to restyle them is art drawn (or recoloured) in their own UV.
There are only about nine humanoid-model mob types, and a mob texture is global per type. That is the hard limit on how many distinct player-skin-looking mobs a pack can have — and why re-skinning a desert-only mob (husk, parched) is safer than a ubiquitous one (zombie) or a wrong-biome one (stray, bogged).
Appearance vs. behaviour: mannequins, mounts and villagers
A mannequin gives you any face and no behaviour whatsoever:
net.minecraft.world.entity.Avatar extends LivingEntity
net.minecraft.world.entity.Mob extends LivingEntity
Mannequin extends Avatar
GoalSelector, PathNavigation and Brain all live on Mob.
Avatar is Mob's sibling, not its parent, so a mannequin cannot walk, path, sleep, trade or
fight, and no datapack registry adds AI to an entity type. Its whole NBT surface is profile,
hidden_layers, main_hand, pose, immovable,
description, hide_description, equipment.
So a moving NPC with a custom face is a mannequin riding an invisible mob: the mount does the wandering, the pathing and (for a villager mount) the walk home to a bed at night, while the mannequin is carried — a passenger's position comes from its vehicle, so nothing ticks on your side.
The rider height is arithmetic, not taste
EntityAttachment.PASSENGER = Fallback.AT_HEIGHT -> (0, height, 0)
EntityAttachment.VEHICLE = Fallback.AT_FEET -> (0, 0, 0)
Avatar.DEFAULT_VEHICLE_ATTACHMENT = Vec3(0, 0.6, 0) // an Avatar overrides VEHICLE
feet = mount_height × mount_scale − 0.6
Solve it for zero. For a 1.95-tall villager mount that is scale = 0.6 / 1.95 = 0.3077. Note the
trap: shrinking the mount does not bring the rider closer to the ground — the mount's term
falls toward zero while the rider's own −0.6 never scales, so an over-small mount buries the
rider (at scale 0.0625 its feet sit 0.48 below the floor). Scaling also shrinks step height, so hand it back
with the minecraft:step_height attribute or the mount cannot climb a kerb.
What the mount can and cannot lend you
- Movement, sleeping, doors: free, from the mount's own AI.
- Trading: not directly — there is no command to open a villager's trade screen, and
the visible body is the mannequin, not the merchant. Use a
minecraft:interactionentity riding the mannequin (so the click travels with the body) plusminecraft:player_interacted_with_entityand aminecraft:dialogmenu. - Being killed: the mannequin's hitbox is in front, so attacks land on it rather than the
mount.
minecraft:player_hurt_entitycan relay the hit to the mount event-driven, but the reward function knows who struck and not how hard, so relayed damage is a flat number and weapon scaling is lost. For anything that must fight and die properly, re-skin a real mob instead of riding one.
Villagers specifically (26.2)
villager_trade(388 entries) andtrade_set(68) ship as datapack JSON and have mcdoc schemas — villager trading is data-driven.villager_type(7) andvillager_profession(15) are registries but ship no JSON and have no data schema — you cannot add a custom villager type or profession, so an Egyptian villager means overridingvillager/type/desert.pngfor every desert villager in the world.- Villager NBT carries
Offers(a literal fixed roster) andVillagerDataFinalized(stops it re-rolling its profession). - Brain memories
home,job_siteandmeeting_pointare the zoning hooks, but they are POI-validated —homeneeds a real bed, a meeting point needs a bell. To confine an NPC to an arbitrary marker instead, leash it on a slow clock rather than a tick.
Choosing an NPC body: the decision that actually matters
Almost every argument about “custom NPCs” is really one trade-off. A captured player face and real mob behaviour do not come in the same entity unless you are prepared to build a display rig. Decide which side you need before you build anything.
| Body | Captured face | Walks | Beds | Trade GUI | Distinct looks | Cost |
|---|---|---|---|---|---|---|
mannequin | yes, per entity | no | no | no | unlimited | trivial |
re-skinned undead (husk, skeleton) | texture only, global | yes | no | no | one per entity type | low |
wandering_trader | texture only, global | yes | no | yes | one, ever | low |
villager | texture only, global | yes | yes | yes | ~15 (see below) | low |
six item_display rig | yes, per entity | yes | yes | yes | unlimited | weeks; core shaders; breaks every version |
The last row is real, not theoretical — see
bradleyq/stable_player_display,
which renders a full player skin from a profile across six item_display entities using a
resource pack and core shaders. Judge it on maintenance, not possibility: it tracks one narrow version range
at a time and its own README says the pack breaks on every Minecraft update. Pair it with Animated Java if you
want a walk cycle.
Why a villager gives you variety and a trader does not
A villager’s skin is not one texture, it is four the game composites at render time:
villager/villager.png body and face, shared by every villager
villager/type/<biome>.png the biome costume (desert, plains, ...)
villager/profession/<job>.png the job clothes (cleric, cartographer, ...)
villager/profession_level/<m>.png the rank badge (stone..diamond)
With 7 types and 15 professions that is a large grid of separately addressable texture slots. A
wandering_trader has exactly one file
(entity/wandering_trader/wandering_trader.png — note the subfolder), so every trader you
spawn is the same person. If you need several different-looking merchants, that alone decides it.
Repurpose a profession for its texture slot and override its trades per entity; the vanilla trades attached to
that profession never appear.
Pinning a villager so it stays what you made it
VillagerData:{type,profession,level}sets it;VillagerDataFinalized:1b(boolean, since 26.1) is what keeps it. Without it the villager can lose or re-roll its profession — and since the profession selects the texture, it changes clothes as well as jobs.- Set
rewardExp:0bandxp:0on every recipe. A villager that earns trading xp levels up, and levelling grafts the profession’s vanilla trades on beside yours, so the “fixed roster” quietly stops being fixed. PersistenceRequired:1bor it despawns.- Zoning: the brain memories are POI-validated, so an arbitrary marker zone means a leash on a slow clock. Start the scan from each player and bound it, or you walk the whole entity list every tick.
The trade-cost trap: ItemCost is an EXACT component match
In NBT Offers.Recipes[].buy / buyB are
ItemStackOfComponent<DataComponentExactPredicate>. Every component you name must match
exactly. This is not the same operator you use in commands: a function can test a custom currency with
the subset form
clear @s minecraft:gold_nugget[minecraft:custom_data~{mypack:{currency:"coin"}}] 0
but a trade cost cannot. Name the currency’s components exactly as its loot table mints them, or the merchant silently refuses the player’s own coins — no error, no log line, the trade simply never lights up. Keep the literal in one place if two systems both price in it.
The data-driven side has a cleaner answer for scoping trades without touching vanilla rosters:
villager_trade entries carry a merchant_predicate, so a trade can
apply only to a merchant matching a predicate (a tag, for instance). Overriding
trade_set/wandering_trader/* instead repoints every trader in the world.
Re-skinning is always global — choose the mob for that
There is no per-entity texture switch for a mob in vanilla. Overriding
entity/illager/evoker.png makes every evoker wear it. That is usually fine, but it should
be a decision: pick mobs whose vanilla spawns do not collide with your setting (an illusioner never spawns
naturally; a husk is desert-only), and expect the override to reach the whole world.
Gotchas
- Signature vs. no signature. A
valuealone renders in most single-player/LAN contexts, but a server withenforce-secure-profile=trueneeds the matchingsignatureor it shows the default skin. Always keep the value/signature as a pair from the same MineSkin generation. - Value and signature must match. Mixing a value from one skin with a signature from another fails validation — regenerate both together.
- Model type. Slim (Alex) vs classic (Steve) is encoded in the value; pick the right one in MineSkin or arms/edges misalign.
- Citizens is server-side; mannequins are datapack. Pick the tool for your environment — the skin data is portable between them, the commands are not.
Sources
- MineSkin skins gallery and MineSkin — signed-skin generation + API.
- Citizens Wiki — Skins (the
/npc skincommand and its--url/-t/-loptions). - 26.2 client jar, read with
javap:Avatar,Mob,Mannequin,PlayerModel,AbstractZombieModel,SkeletonModel,VillagerModel,IllagerModel,WitchModel,EntityAttachmentand itsFallbackconstants. - 26.2 shipped datapack:
data/minecraft/villager_trade/(388) anddata/minecraft/trade_set/(68) exist;villager_type/andvillager_profession/do not. - Mannequin entity (this Codex) and
minecraft:profilecomponent (this Codex).