Custom Model Render (Power Type)
Renders a custom look on a player: either re-skinning the vanilla model with a texture (texture mode) or drawing a separate 3D model made in Blockbench that…
Renders a custom look on a player: either re-skinning the vanilla model with a texture (texture mode) or drawing a separate 3D model made in Blockbench that follows the player’s pose (geometry mode). Geometry mode is a JSON-defined feature renderer — no Java, no extra mods.
Type ID: apoli:custom_model_render
This power supersedes
apoli:entity_texture_overlay, which is not a registered type — rename it toapoli:custom_model_renderin any JSON that still uses it. Themode: texturefields below are the direct equivalents of its fields, except that there is no multi-layersfield: use one power per layer.
This is a client-side rendering power. Texture mode draws its overlays on any living entity that holds the power; replacing the skin outright (
render_as_overlay: false) is for players only. Geometry mode works on players and on the minions summoned by apoli:summon_minion. Textures and models must be present in every viewer’s resource pack.
Shared fields (both modes)
| Field | Type | Default | Description |
|---|---|---|---|
mode | String | texture | texture (re-skin the vanilla model) or geometry (draw a custom Blockbench model). |
render_type | Render Type | translucent | Draw style — translucent, cutout, cutout_no_cull, solid, emissive/glow, eyes, energy_swirl. |
scroll_speed | Float | 0.0 | With render_type: energy_swirl, how fast the texture scrolls, in texture-heights per tick. 0.0 holds the texture still. See Energy swirl. |
body_parts | Body Part or Array of Body Part | whole model | In texture mode, restrict an overlay to these parts — limbs, layers or groups such as arms and upper on a humanoid model; main, flat2, flat3 on a minion. On a model with neither, the whole model is drawn. In geometry mode, render only the bones with these names — any bone name in your own model, not just the vanilla ones; a group name shows every bone bound to its limbs. Every bone you do not name is hidden, and hiding a bone hides everything nested under it. |
red / green / blue / alpha | Float | 1.0 | Colour/opacity multipliers (0.0 – 1.0). |
scale | Float | 1.0 | Scales the drawn geometry outward from the model origin (aura/shell effect above 1.0). |
hidden_slots | Array of Equipment Slot | none | Hide this render whenever any listed slot is occupied — e.g. ["head"] hides a custom hat model when a real helmet is worn. |
show_first_person | Boolean | false | Also draw on the holder’s own arm in first person. In texture mode that is the overlay texture; in geometry mode it is the model’s right_arm / left_arm bones (and everything nested under them), posed onto the vanilla first-person arm. |
Texture-mode fields (mode: texture)
| Field | Type | Default | Description |
|---|---|---|---|
wide_texture_location | Identifier or keyword | = texture_location | Texture for the wide (Steve) model, and for every entity that is not a player. Also takes a live keyword. |
slim_texture_location | Identifier or keyword | = wide | Texture for the slim (Alex) model. |
texture_location | Identifier or keyword | optional | One texture for every model. Used when wide_texture_location is left out; one of the two is required. |
render_as_overlay | Boolean | false | false replaces the skin; true draws the texture as an overlay layer honouring render_type, body_parts and the tint. |
hide_cape | Boolean | false | Hide the holder’s cape while active. |
Geometry-mode fields (mode: geometry)
| Field | Type | Default | Description |
|---|---|---|---|
model_location | Identifier | required | The Blockbench model. mymod:cape resolves to assets/mymod/geo/cape.geo.json (the standard Blockbench/GeckoLib folder) — assets/mymod/models/apoli/cape.geo.json also works. Export from Blockbench as Bedrock geometry (.geo.json). |
texture_location | Identifier or keyword | required | The texture that UV-maps onto the model, e.g. mymod:textures/entity/cape.png. Also takes a live keyword. |
render_as_overlay | Boolean | false | Minions only. false replaces the minion’s own model with yours; true draws yours on top of it. Ignored on players, where geometry is always drawn over the player model. |
animations | Model Animation or Array of them | none | Bedrock animations to play on the model. The first entry whose condition passes is the one that plays. |
Live textures
Every texture field here takes a keyword instead of a texture id, resolved per frame against the entity being drawn:
| Keyword | Resolves to |
|---|---|
player | That entity’s skin file, if it is a player. |
player_cape | That entity’s cape. |
entity | Whatever texture that entity’s renderer normally uses — so a model can be skinned with the mob it is attached to. |
{
"type": "apoli:custom_model_render",
"mode": "geometry",
"model_location": "example:wings",
"texture_location": "player"
} That draws the wings model UV-mapped onto the wearer’s own skin, so it recolours itself per player without a texture per player.
Keywords resolve on the client, from the entity being rendered.
held_itemandoffhand_itemare item models, not textures, so they do nothing here — use them on apoli:overlay or apoli:fire_projectile instead.
On resource (re)load, the log prints Loaded N custom model(s) for custom_model_render. — if your model isn’t drawing, check N and confirm the file sits at one of the two paths above with the .geo.json extension.
What you model is what you get. The model is placed exactly where Blockbench shows it, relative to the player’s feet: a bone at Bedrock pivot [0, 0, 0] sits on the ground, one at [0, 24, 0] sits at the head pivot. Per-cube rotations, bone rotations, bone parenting, inflate and mirror all carry over unchanged, and Box UV is packed the way Blockbench’s Bedrock preview packs it — including on cubes with fractional sizes, where Blockbench rounds each dimension down before laying the six faces out.
The UV grid is sized by the
texture_width/texture_heightin the.geo.json, which is your Blockbench project’s UV size — not the pixel size of the PNG. A 64×64 PNG on a 32×32 UV project is fine (it just draws at 2×), but a PNG painted for a 64×64 UV grid on a 32×32 project will be half a texture out. Set the project’s UV size before you paint.
How geometry follows the player: bones named head, body, right_arm, left_arm, right_leg, left_leg, hat pick up the matching vanilla body part’s animation, so the model bends with the arms and turns with the head. Any other bone (a cape, a backpack, a tail…) rides along with its parent bone.
Put your body-part bones on the vanilla pivots. This is the one rule that decides whether a model animates convincingly, and it is the first thing to check if a limb looks like it is swinging in the wrong direction. Apoli rotates each bone about its own pivot, by the angle the vanilla part is turning through. So a bone gets the player’s motion, but it swings around the point you chose in Blockbench — and if that point isn’t where the player’s joint is, the limb sweeps through an arc the player’s limb never takes. Name the bone correctly and place its pivot here:
Bone Bedrock pivot head,hat,body[0, 24, 0]right_arm/left_arm[-5, 22, 0]/[5, 22, 0]right_leg/left_leg[-1.9, 12, 0]/[1.9, 12, 0]Only the pivot has to match — the cubes hanging off the bone can be any shape or size you like. The easiest way to get this right is to start from the vanilla player template in Blockbench and rebuild the geometry on top of it, leaving the bones where they are.
Deliberately off-pivot bones still work, and are the right call for anything that isn’t a limb: an aura ring pivoted at the feet, or a floating orb pivoted at the head, will orbit the player instead of tracking a joint.
Names are matched loosely, so you rarely have to rename anything Blockbench gave you. Case, spaces, _ and - are ignored (Head, RightArm and right arm all work), and these spellings bind to the same body part:
| Body part | Also accepted |
|---|---|
head | — |
hat | headwear, hat_layer, head_layer |
body | torso, waist, jacket, body_layer |
right_arm / left_arm | arm_right / arm_left, right_sleeve / left_sleeve, right_arm_layer / left_arm_layer |
right_leg / left_leg | leg_right / leg_left, right_pants / left_pants, right_leg_layer / left_leg_layer |
Every bone whose name matches a body part binds to it, not just the first one. So a rig with right_arm and right_sleeve — or head and hat_layer, or body and jacket — animates all of them together. A second-layer bone that only bound at rest used to stay behind while the limb it sat on moved, which read as the model coming apart on any pose that moves a part rather than only rotating it: sneaking, blocking with a shield, winding up a trident.
Nesting is fine: a body-part bone animates from the player whether it sits at the top level, inside a wrapper group like bb_main, or under your Body bone. It always swings the way the player’s own limb swings, never twice over.
A body-part bone always animates in the player’s frame, so it is lifted out of any parent group when the model loads. If that parent group has a rotation of its own in Blockbench, the rotation stops applying to that bone — the load log names the bone and the group when this happens. Rotate the bone itself, or give the group a non-body-part name so it stays a plain group.
Animations
Blockbench animations export as a .animation.json next to your model. Drop it at assets/<namespace>/animations/<path>.animation.json and name it in animations:
{
"type": "apoli:custom_model_render",
"mode": "geometry",
"model_location": "example:wings",
"texture_location": "example:textures/entity/wings.png",
"render_type": "cutout_no_cull",
"animations": {
"animation": "example:wings",
"name": "animation.wings.idle"
}
} animations takes an array as well, and works exactly like hud_render does: the entries are read top to bottom and the first one whose condition passes is the one that plays. That is how you drive a model from the holder’s state without writing any Java:
"animations": [
{
"animation": "example:wings",
"name": "animation.wings.flap",
"condition": {
"type": "apoli:fall_flying"
}
},
{
"animation": "example:wings",
"name": "animation.wings.idle"
}
] Playback time restarts whenever the selected entry changes, so a non-looping animation replays each time its condition flips back on. speed scales the playback rate and loop overrides the file’s own loop flag — see Model Animation for the full field list.
Conditions here run on the client, every frame, against the state the client knows. Anything the server never sends — a
apoli:command,apoli:predicate,apoli:scoreboard,apoli:advancementorapoli:statcondition — cannot be answered in a render layer and the entry is skipped with a warning in the log. Everything the client already tracks works: pose, sneaking, sprinting, fall flying, held items, equipment, and, since Apoli 1.45.0, apoli:resource on any power holder. Before 1.45.0 a resource on a non-player holder (a summoned minion, say) only reached the client once, when the entity came into view, so a condition on it appeared to be stuck at the value it had then — the unconditional fallback entry played instead. Nothing in the data pack changes; rebuild against 1.45.0 and the conditional entries start switching.
The animation is applied on top of the pose the player’s body already gives the model, so a bone named right_arm gets the player’s arm swing and your keyframes, added together. Bones that are not body parts get the keyframes alone. Position keyframes are in Bedrock units and rotation keyframes in degrees, exactly as Blockbench writes them.
On resource (re)load the log prints Loaded N custom model animation(s) from M file(s). — check it if nothing moves.
Only the
position,rotationandscalechannels are read, with linear interpolation between keyframes (pre/postvalues on a keyframe are honoured, which is how Blockbench’s stepped keyframes come across). Both keyframe spellings work — a bare[x, y, z], which is what Blockbench’s Bedrock exporter writes, and the{"vector": [x, y, z]}wrapper the GeckoLib plugin writes. Keyframe easing is honoured:lerp_mode: "catmullrom"(Blockbench’s smooth keyframes) interpolates along a Catmull-Rom spline, and GeckoLib’seasing/easingArgs— the fulleaseInOutSine,easeOutBack,easeOutElastic,easeOutBouncefamily — shape the curve between keyframes. An easing sits on the keyframe you are moving toward. Molang expressions are not evaluated — a keyframe whose value is a formula rather than a number reads as0. Sound and particle effect keyframes are ignored.
Geometry mode on minions
A power on a minion is held by the minion. Every condition in it — including the ones on
animationsentries — is tested against the minion, never its summoner, soapoli:sneakingasks whether the minion is sneaking. Wrap it in apoli:owner to ask about the player who summoned it.
Give the power to a apoli:summon_minion minion through that action’s powers list and the minion is drawn as your Blockbench model instead of the default orb — a data-pack-only way to give a summon any shape you like.
The minion skeleton is main (the root bone, Bedrock pivot [0, 4, 0]) with flat2 and flat3 under it. A bone named main turns to face wherever the minion is looking, which is the minion’s only vanilla animation; flat2 and flat3 hold fixed poses, so a bone naming them inherits nothing. Every other bone keeps the pose you gave it in Blockbench and rides along with the root.
The minion’s texture field still applies to the minion’s own model, so it only matters when render_as_overlay is true. Your geometry always uses texture_location. scale on the summon still applies — the custom model is scaled with the minion.
{
"type": "apoli:summon_minion",
"follow_owner": true,
"follow_offset": [
0,
1,
-1
],
"max_life_ticks": 0,
"powers": [
"example:minion_wisp_model"
]
} {
"type": "apoli:custom_model_render",
"mode": "geometry",
"model_location": "example:wisp",
"texture_location": "example:textures/entity/wisp.png",
"render_type": "cutout_no_cull"
}
custom_model_renderis a client-side render power, so the minion never behaves differently — only its appearance changes.
Energy swirl
render_type: energy_swirl draws the texture the way a charged creeper’s aura and a wither’s armour are drawn: additively, so it glows over the model, with the texture sliding across it. The sideways drift is fixed; scroll_speed adds a steady scroll along the model on top, and 0.0 holds the texture completely still. Pair it with scale above 1.0 to lift the swirl off the body into a shell.
{
"type": "apoli:custom_model_render",
"texture_location": "minecraft:textures/entity/creeper/creeper_armor.png",
"render_as_overlay": true,
"render_type": "energy_swirl",
"scroll_speed": 0.01,
"scale": 1.1,
"red": 0.5,
"green": 0.5,
"blue": 0.5
} A charged-creeper aura on whoever holds the power — a player, a zombie, anything alive.
The type id apoli:energy_swirl loads as this power with the swirl already set up. It reads texture_location, size (as scale) and speed (as scroll_speed), and fills in whatever is left out with render_as_overlay: true, render_type: energy_swirl, texture_location set to the wither’s armour texture, scroll_speed: 0.01 and a 0.5 grey tint:
{
"type": "apoli:energy_swirl",
"texture_location": "example:textures/entity/rewind.png",
"size": 1.05,
"speed": 0.02
} Behaviour & limits
- Multiple active
custom_model_renderpowers stack; each is drawn. On a minion, one non-overlay geometry power is enough to hide the base model, and the rest still draw. - Conditions on the power gate the whole render dynamically (combine with
hidden_slotsfor equipment-based hiding). - Geometry mode draws in first person with
show_first_person: true, but only the arm bones — first person draws one arm, so that is all there is to draw on. It does draw in full wherever the game renders a whole player body, including the inventory preview and other entity-preview screens, regardless of which camera mode you are in, and when your body is visible in first person (while sleeping, for instance). - The first-person arm is drawn from the same bones as the third-person model, so the vanilla arm underneath it is still there. Hide it with apoli:modify_model_parts if your model is meant to replace it rather than sit over it.
- If
model_locationfails to load, the minion falls back to its normal model rather than turning invisible. - The Bedrock parser supports box UV and per-face UV (including per-cube mixing), per-cube and per-bone
pivot/rotation,inflate,mirrorand bone parenting. Onlyminecraft:geometry[0]is read, anduv_rotation(Bedrock format 1.21.0+) is not supported — a model using it logs a warning and draws that face unrotated. - A cube with a zero-size axis is a flat plane in Bedrock too, and both of its faces land on the same plane. Blockbench shows this as z-fighting and so does the game; give the cube a small
inflateor a non-zero thickness if it flickers.
Examples
Replace the skin in third person (was entity_texture_overlay replace mode):
{
"type": "apoli:custom_model_render",
"mode": "texture",
"wide_texture_location": "example:textures/entity/husk_wide.png",
"slim_texture_location": "example:textures/entity/husk_slim.png"
} Glowing eyes on the head only, over the normal skin:
{
"type": "apoli:custom_model_render",
"mode": "texture",
"render_as_overlay": true,
"render_type": "eyes",
"body_parts": "head",
"wide_texture_location": "example:textures/entity/glowing_eyes.png"
} A custom cape model that follows the body, hidden when a chestplate is worn:
{
"type": "apoli:custom_model_render",
"mode": "geometry",
"model_location": "example:cape",
"texture_location": "example:textures/entity/cape.png",
"render_type": "cutout_no_cull",
"hidden_slots": [
"chest"
]
} Clawed gauntlets you can also see on your own hands:
{
"type": "apoli:custom_model_render",
"mode": "geometry",
"model_location": "example:gauntlets",
"texture_location": "example:textures/entity/gauntlets.png",
"render_type": "cutout_no_cull",
"show_first_person": true
} The model’s right_arm and left_arm bones sit on the vanilla arm pivots, so they follow the arm in third person and are drawn onto the first-person hand as well.
On something other than a player
The power renders on whatever entity holds it, so granting it to a summon, a clone or a projectile puts the model on that entity rather than on you:
- a minion or a clone that holds the power renders with the geometry, with its humanoid bones driven by the entity’s own pose;
- a projectile fired by
apoli:fire_projectilewith atexture_locationrenders the geometry instead of its flat texture, oriented along its flight path. Grant the power from that power’sprojectile_action.
hidden_slots only applies to entities that wear equipment; on a projectile it is ignored.
While disguised
An apoli:disguise_as disguise swaps out the whole
player renderer, so the power has to be drawn onto the disguise instead. It is, whenever the
disguise’s model is humanoid — zombie, husk, drowned, skeleton, stray, wither skeleton, piglin
and the rest of the two-arms-two-legs family. Texture overlays paint onto the disguise’s limbs, and
geometry bound to head, body, right_arm, left_arm, right_leg or left_leg tracks the
disguise’s live pose.
{
"type": "apoli:multiple",
"skeleton_body": {
"type": "apoli:disguise_as",
"entity_type": "minecraft:skeleton"
},
"armor": {
"type": "apoli:custom_model_render",
"mode": "texture",
"render_as_overlay": true,
"wide_texture_location": "example:textures/models/armor/plate_wide.png",
"slim_texture_location": "example:textures/models/armor/plate_slim.png"
}
} The armour is painted over the skeleton. Which of the two textures is used still follows the player’s own skin model, not the disguise’s — a slim-armed player gets the slim sheet.
A disguise whose model is not humanoid — a creeper, a bee, a horse — has no limbs to bind to, and the power is skipped for as long as that disguise is up. A disguise as another player is already drawn by the player renderer, so nothing special happens there: the power works exactly as it does undisguised.