Custom Block API
This guide covers the advanced Custom Block API for creating blocks with custom client-side rendering properties
such as custom geometry, textures, transformations, and state-dependent appearances.
What you will learn:
How to create custom blocks with the function-based API
Configuring geometry, materials, and transformations
Creating state-dependent rendering (different appearances per block state)
Server Module Required
The Custom Block API requires access to the server module . In your build.gradle.kts, you must set
apiOnly = false in the allay block:
allay {
apiOnly = false // Required for Custom Block API
// ... other settings
}
Note that internal APIs may change between versions. See AllayGradle documentation
for more details.
Prerequisites
Before diving into custom blocks, make sure you understand the basics of the Block API covered in
Block API Tutorial .
Overview
The Custom Block API uses a function-based approach where you provide a function that maps each BlockState
to its rendering properties via BlockStateDefinition. The system automatically:
Iterates all possible block state combinations
Collects rendering properties for each state
Quick Start
Simple Textured Block
The simplest way to create a custom block with a texture:
import org.allaymc.server.block.type.AllayBlockType ;
import org.allaymc.server.block.type.CustomBlockDefinitionGenerator ;
AllayBlockType . builder ( MyBlockImpl . class )
. identifier ( "myplugin:ruby_block" )
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . ofTexture ( "ruby_block" ))
. build ();
Block with Custom Geometry
import org.allaymc.server.block.type.BlockStateDefinition ;
import org.allaymc.server.block.type.BlockStateDefinition.Geometry ;
import org.allaymc.server.block.type.BlockStateDefinition.Materials ;
AllayBlockType . builder ( MyBlockImpl . class )
. identifier ( "myplugin:custom_lamp" )
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . ofConstant (
BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.custom_lamp" ))
. materials ( Materials . builder (). any ( "lamp_texture" ))
. displayName ( "Custom Lamp" )
. build ()))
. build ();
State-Dependent Rendering
The most powerful feature - different appearances based on block state:
import org.allaymc.api.block.property.type.BlockPropertyTypes ;
import org.allaymc.server.block.type.BlockStateDefinition.Geometry ;
AllayBlockType . builder ( MyDoorImpl . class )
. identifier ( "myplugin:custom_door" )
. setProperties ( BlockPropertyTypes . OPEN_BIT )
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . of ( state -> {
boolean isOpen = state . getPropertyValue ( BlockPropertyTypes . OPEN_BIT );
return BlockStateDefinition . builder ()
. geometry ( Geometry . of ( isOpen ? "geometry.door_open" : "geometry.door_closed" ))
. materials ( Materials . builder (). any ( "door_texture" ))
. build ();
}))
. build ();
CustomBlockDefinitionGenerator
The main entry point for custom block definitions.
Factory Methods
Method
Description
of(Function<BlockState, BlockStateDefinition>)
Full control - provide a function for each state
ofConstant(BlockStateDefinition)
All states have the same appearance
ofTexture(String texture)
Simple texture-only block
Using of() - Full Control
CustomBlockDefinitionGenerator . of ( state -> {
// Access block state properties
int age = state . getPropertyValue ( BlockPropertyTypes . AGE );
boolean powered = state . getPropertyValue ( BlockPropertyTypes . POWERED_BIT );
// Return different definitions based on state
return BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.crop_stage_" + age ))
. materials ( Materials . builder (). any ( powered ? "crop_powered" : "crop_normal" ))
. build ();
});
Using ofConstant() - Same Appearance
CustomBlockDefinitionGenerator . ofConstant (
BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.my_block" ))
. materials ( Materials . builder (). any ( "my_texture" ))
. transformation ( Transformation . builder (). ry ( 45 ). build ())
. build ()
);
Using ofTexture() - Simple Texture
// All faces use the same texture with default opaque rendering
CustomBlockDefinitionGenerator . ofTexture ( "ruby_block" );
BlockStateDefinition
Holds the client-side rendering properties for a block state.
BlockStateDefinition . builder ()
. geometry ( Geometry ) // Geometry configuration
. materials ( Materials ) // Material instances
. transformation ( Transformation ) // Model transformation
. displayName ( String ) // Display name in inventory
. build ()
Properties
Property
Type
Description
geometry
Geometry
Geometry configuration with identifier and advanced properties, null for default cube
materials
Materials
Material/texture configuration for block faces
transformation
Transformation
Rotation, scale, and translation of the model
displayName
String
Name shown in inventory, null uses block identifier
Geometry
Configure the 3D model (geometry) for block rendering. Supports both simple identifier-only form and
advanced object form with bone visibility, culling, and UV lock settings.
For basic geometry with just an identifier:
import org.allaymc.server.block.type.BlockStateDefinition.Geometry ;
// Simple string form - most common usage
Geometry . of ( "geometry.custom_block" )
For advanced features like bone visibility control and culling optimization:
import org.allaymc.api.block.property.type.BlockPropertyTypes ;
// Object form with bone visibility based on block properties
Geometry . builder ()
. identifier ( "geometry.door" )
. boneVisibility ( "hinge" , false ) // Always hidden
. boneVisibility ( "handle" , BlockPropertyTypes . OPEN_BIT , true ) // Visible when open
. build ()
// With culling optimization
Geometry . builder ()
. identifier ( "geometry.leaves" )
. culling ( "custom:culling.leaves" )
. build ()
// With UV lock
Geometry . builder ()
. identifier ( "geometry.rotatable" )
. uvLockAll () // Lock UVs for all bones when rotating
. build ()
Properties
Property
Type
Description
identifier
String
Required. Geometry identifier (e.g., "geometry.custom_block")
boneVisibility
Map<String, BoneVisibility>
Map of bone names to visibility conditions
culling
String
Culling rules identifier (format: namespace:culling.name)
cullingLayer
String
Culling layer for optimization
uvLockBones
List<String>
List of specific bone names to lock UVs
uvLockAll
boolean
Whether to lock UVs for all bones
Bone Visibility
Control which bones of a geometry are visible. Use property-based conditions for
dynamic visibility based on block state:
Geometry . builder ()
. identifier ( "geometry.complex_model" )
// Static visibility - always hidden
. boneVisibility ( "decoration" , false )
// Property-based visibility - visible when powered
. boneVisibility ( "indicator" , BlockPropertyTypes . POWERED_BIT )
// Property-based visibility with specific value
. boneVisibility ( "stage_2" , BlockPropertyTypes . AGE , 2 )
. build ()
Bone Visibility Methods
Method
Description
boneVisibility(String, boolean)
Static visibility (always visible/hidden)
boneVisibility(String, BlockPropertyType<Boolean>)
Visible when boolean property is true
boneVisibility(String, BlockPropertyType<T>, T)
Visible when property equals value
boneVisibilityMolang(String, String)
Raw Molang expression (advanced)
Culling
Configure block culling for performance optimization:
Geometry . builder ()
. identifier ( "geometry.leaves" )
. culling ( "myplugin:culling.leaves" ) // Custom culling rules
. cullingLayer ( "minecraft:culling_layer.leaves" )
. build ()
UV Lock
Prevent UV coordinates from rotating when the block model is transformed:
// Lock all bones
Geometry . builder ()
. identifier ( "geometry.log" )
. uvLockAll ()
. build ()
// Lock specific bones only
Geometry . builder ()
. identifier ( "geometry.complex" )
. uvLock ( "top" , "bottom" ) // Only lock these bones
. build ()
Materials
Configure textures for each block face.
Builder Pattern
import org.allaymc.server.block.type.BlockStateDefinition.Materials ;
import org.allaymc.api.block.data.BlockFace ;
Materials . builder ()
. any ( "default_texture" ) // All unspecified faces
. face ( BlockFace . UP , "top_texture" ) // Override top face
. face ( BlockFace . DOWN , "bottom_texture" ) // Override bottom face
. sides ( "side_texture" ) // All horizontal faces
Methods
Method
Description
any(String texture)
Set texture for all unspecified faces (wildcard)
any(MaterialInstance)
Set material for all unspecified faces
face(BlockFace, String)
Set texture for a specific face
face(BlockFace, MaterialInstance)
Set material for a specific face
sides(String texture)
Set texture for all horizontal faces (N/S/E/W)
sides(MaterialInstance)
Set material for all horizontal faces
Practical Examples
Simple single texture:
Materials . builder ()
. any ( "stone_texture" )
Different top and bottom:
Materials . builder ()
. any ( "log_side" ) // Default for all faces
. face ( BlockFace . UP , "log_top" ) // Override top
. face ( BlockFace . DOWN , "log_top" ) // Override bottom
Furnace-style (front face different):
Materials . builder ()
. any ( "furnace_side" )
. face ( BlockFace . UP , "furnace_top" )
. face ( BlockFace . NORTH , "furnace_front" )
MaterialInstance
Configure advanced rendering properties for a face.
import org.allaymc.server.block.type.BlockStateDefinition.MaterialInstance ;
import org.allaymc.server.block.type.BlockStateDefinition.RenderMethod ;
MaterialInstance . builder ()
. texture ( "my_texture" )
. renderMethod ( RenderMethod . ALPHA_TEST )
. faceDimming ( true )
. ambientOcclusion ( true )
. randomUVRotation ( false )
. textureVariation ( false )
. build ()
Builder Properties
Property
Type
Default
Description
texture
String
Required
Texture name from resource pack
renderMethod
RenderMethod
OPAQUE
How the texture is rendered
faceDimming
boolean
true
Apply directional light dimming
ambientOcclusion
boolean
true
Apply ambient occlusion shadows
randomUVRotation
boolean
false
Randomly rotate texture to avoid tiling patterns
textureVariation
boolean
false
Enable texture variation
Factory Methods
Method
Description
MaterialInstance.of(String texture)
Opaque texture with defaults
MaterialInstance.opaque(String)
Explicit opaque rendering
MaterialInstance.alphaTest(String)
Binary transparency (solid/transparent)
MaterialInstance.alphaTestSingleSided(String)
Binary transparency, single-sided
MaterialInstance.blend(String)
Smooth transparency blending
MaterialInstance.doubleSided(String)
Visible from both sides
Render Methods
RenderMethod
Description
Use Case
OPAQUE
Fully opaque
Solid blocks like stone
ALPHA_TEST
Binary transparency
Leaves, flowers, crops
ALPHA_TEST_SINGLE_SIDED
Binary, one-sided
Glass panes
ALPHA_TEST_TO_OPAQUE
Binary transparency, opaque at distance
Performance-optimized leaves
ALPHA_TEST_SINGLE_SIDED_TO_OPAQUE
Single-sided, opaque at distance
Performance-optimized panes
BLEND
Smooth transparency
Stained glass, water
BLEND_TO_OPAQUE
Smooth transparency, opaque at distance
Performance-optimized glass
DOUBLE_SIDED
Both sides visible
Vines, banners
Example with Transparency
Materials . builder (). any ( MaterialInstance . alphaTest ( "leaves_texture" ))
Apply rotation, scale, and translation to the block model.
import org.allaymc.server.block.type.BlockStateDefinition.Transformation ;
Transformation . builder ()
. rx ( 0 ). ry ( 90 ). rz ( 0 ) // Rotation in degrees (0, 90, 180, 270)
. sx ( 1 ). sy ( 1 ). sz ( 1 ) // Scale (default 1.0)
. tx ( 0 ). ty ( 0 ). tz ( 0 ) // Translation
. build ()
Properties
Property
Type
Description
rx, ry, rz
int
Rotation in degrees (must be 0, 90, 180, or 270)
sx, sy, sz
float
Scale factors (default 1.0)
tx, ty, tz
float
Translation offsets
Rotation Constraints
Rotation values must be 0, 90, 180, or 270 degrees. Other values will cause rendering issues.
Rotation Example
Create a block that rotates based on facing direction:
CustomBlockDefinitionGenerator . of ( state -> {
var facing = state . getPropertyValue ( BlockPropertyTypes . MINECRAFT_CARDINAL_DIRECTION );
int rotation = switch ( facing ) {
case NORTH -> 0 ;
case EAST -> 90 ;
case SOUTH -> 180 ;
case WEST -> 270 ;
};
return BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.directional_block" ))
. transformation ( Transformation . builder (). ry ( rotation ). build ())
. build ();
});
Complete Examples
Growth Stage Crop
import org.allaymc.api.block.property.type.IntPropertyType ;
// Define growth property (0-7)
var AGE = IntPropertyType . of ( "age" , 0 , 7 , 0 );
AllayBlockType . builder ( CustomCropImpl . class )
. identifier ( "myplugin:magic_crop" )
. setProperties ( AGE )
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . of ( state -> {
int age = state . getPropertyValue ( AGE );
return BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.crop" ))
. materials ( Materials . builder (). any ( MaterialInstance . alphaTest ( "magic_crop_stage_" + age )))
. build ();
}))
. build ();
Multi-State Machine Block
import org.allaymc.api.block.property.type.BooleanPropertyType ;
import org.allaymc.api.block.property.type.EnumPropertyType ;
enum MachineState { OFF , IDLE , RUNNING }
var POWERED = BooleanPropertyType . of ( "powered" , false );
var STATE = EnumPropertyType . of ( "state" , MachineState . class , MachineState . OFF );
AllayBlockType . builder ( MachineBlockImpl . class )
. identifier ( "myplugin:processing_machine" )
. setProperties ( POWERED , STATE )
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . of ( state -> {
boolean powered = state . getPropertyValue ( POWERED );
MachineState machineState = state . getPropertyValue ( STATE );
String geometry = powered ? "geometry.machine_on" : "geometry.machine_off" ;
String texture = switch ( machineState ) {
case OFF -> "machine_off" ;
case IDLE -> "machine_idle" ;
case RUNNING -> "machine_running" ;
};
return BlockStateDefinition . builder ()
. geometry ( Geometry . of ( geometry ))
. materials ( Materials . builder (). any ( texture ))
. displayName ( "Processing Machine" )
. build ();
}))
. build ();
Rotatable Block with Custom NBT Components
import org.cloudburstmc.nbt.NbtMap ;
import java.util.Map ;
// Custom components (advanced usage)
Map < String , NbtMap > customComponents = Map . of (
"minecraft:destructible_by_explosion" , NbtMap . builder ()
. putFloat ( "explosion_resistance" , 100.0f )
. build ()
);
AllayBlockType . builder ( ReinforcedBlockImpl . class )
. identifier ( "myplugin:reinforced_block" )
. blockDefinitionGenerator ( new CustomBlockDefinitionGenerator (
state -> BlockStateDefinition . builder ()
. geometry ( Geometry . of ( "geometry.reinforced" ))
. materials ( Materials . builder (). any ( "reinforced_texture" ))
. build (),
customComponents ))
. build ();
Physical Properties
Physical properties like collision shape, light emission, and friction are automatically read from
BlockStateData and don't need to be specified in BlockStateDefinition:
Collision shape
Selection box
Light emission
Light dampening
Friction coefficient
These are configured through block components, not the definition generator.
Certain block tags automatically add components to your custom block:
Block Tag
Effect
REPLACEABLE
Block can be replaced when placing others
POTTABLE_PLANT
Block can be placed in a flower pot
import org.allaymc.api.block.tag.BlockTags ;
AllayBlockType . builder ( CustomFlowerImpl . class )
. identifier ( "myplugin:custom_flower" )
. addBlockTag ( BlockTags . POTTABLE_PLANT ) // Can be placed in flower pots
. blockDefinitionGenerator (
CustomBlockDefinitionGenerator . ofTexture ( "custom_flower" ))
. build ();
Tips and Best Practices
Start Simple
Begin with ofTexture() or ofConstant() and only use of() when you need state-dependent rendering.
Use Descriptive Geometry Names
Name your geometry files clearly: geometry.my_block_open, geometry.my_block_closed.
Geometry Must Exist
The geometry identifier must reference an actual geometry file in your resource pack.
Missing geometries will cause client-side rendering issues.
Texture Names
Texture names must match entries in your resource pack's terrain_texture.json or textures/item_texture.json.
March 24, 2026
March 24, 2026