Switch Language
  1. PowerNukkitX
  2. Get Started
  3. Config

    1. server.properties
    2. nukkit.yml
    3. pnx-cli-config.ini
    4. Anti-xray
    5. Resource Encryption
    6. Compression Acceleration
    7. PNX with WaterDogPE
    8. Flat World
  4. FAQ

    1. To Newcomers
    2. How to use PNX-CLI
    3. Common Questions
    4. Terra Questions
    5. Spawn-point Questions
    6. Convert Worlds
  5. For Devs

    1. Java

      1. Goto JavaDoc
      2. Mod API

        1. Custom Block
        2. Custom Item
        3. Custom Entity
        4. Custom Enchantment
      3. Entity AI

        1. Behavior
        2. Memory
        3. Sensor
        4. Motion Controller
        5. Navigator
        6. Behavior Group
        7. Work Cycle
    2. JavaScript

      1. setup dev env
      2. Base LLSELib dev
      3. Hello World
      4. Basic knowledge
      5. Event Listener
      6. FAQ
    3. Resources

      1. Event List
      2. IDs(block & item)


author: Cool_Loong

This series of articles is always written based on the latest commit version. You can find the latest PNX version maven dependencies at Jitpack.

1. Custom Block API

Custom block related API are define under package cn.nukkit.block.customblock.
A custom block you need to implement the CustomBlock interface, first let's see which methods in the CustomBlock interface you need to implement.

Must be implement/override

double getNamespaceId()
Implement this method to set the namespace ID of the custom block,such as minecraft:test

CustomBlockDefinition getDefinition()
Implement this method to provide a custom block description for the client, where CustomBlockDefinition is a Builder class that provides a number of internal description methods

String getName()
Override this method to implement getName() for a custom block, which must be overridden using the default method provided by the interface
The override uses the return value return CustomBlock.super.getName();

int getId()
Override this method to implement getId() of the custom block, you must use the default method provided by the interface to override it
Override uses the return value return CustomBlock.super.getId();

Optional implement/override

double getFrictionFactor()
Implement this method to describe the friction factor of the custom block, the value ranges from 0 to 1, the smaller the value the higher the friction

double getResistance()
implement this method to set the explosion resistance of the custom block, the larger the value the stronger the explosion resistance

double getLightFilter()
Implement this method to set the light absorption level of the custom block, in the range of 0-15

double getLightLevel()
Implement this method to set the level of light emitted by the custom square, in the range of 0-15

boolean reverseSending()
Whether to reverse the order of parsing the properties of the custom box

About getDefinition()

The role of getDefinition() is to provide the client with a description of the definition of the custom block, its content is similar to addon, because it is a client-side definition, here we need to refer to addon tutorial, here we suggest the following two sites to learn
MC bedrock development wiki
and Microsoft documentation

Step 1: We first need to create the builder instance of CustomBlockDefinition using the CustomBlockDefinition.builder() method.

CustomBlockDefinition.builder() has a total of four overload methods, where the first parameter is an instance of the custom block, just fill in this

Overload 1: builder(CustomBlock customBlock, String texture) The texture parameter is the texture path of the custom block, which needs to be filled according to the corresponding texture definition in the textures\terrain_texture.json resource package
If you don't know what terrain_texture.json is, please check the two websites provided above.

Overload 2: builder(CustomBlock customBlock, String texture, BlockCreativeCategory blockCreativeCategory)
BlockCreativeCategory is the category of the custom block in the client creation field, it is an enum

Overload 3: builder(CustomBlock customBlock, Materials materials)
Materials is the material instance of the custom block, it can describe the material of the custom block on different sides, and the rendering method, which corresponds to addon provides minecraft:material_instances

Overload 4: builder(@NonNull CustomBlock customBlock, @NonNull Materials materials, BlockCreativeCategory blockCreativeCategory)
Materials BlockCreativeCategory same above

Step 2: Add properties to the builder according to your needs
The following can be called:

breakTime(double second) Control the time in seconds for the custom block to be mined on the client-side

creativeGroup(String creativeGroup)
Control the group of custom block in the creation bar

rotation(Vector3f rotation)
Set the rotation of the block around the center of the block in degree

transformation(Transformation transformation)
Set the block rotation and scale and translation

geometry(Geometry geometry)
Control the geometry of a custom block, which needs to be pre-defined in the material package, you can use BlockBench to make the geometry of the block
Geometry allows you to define whether each bone is displayed

permutations(Permutation... permutations)
Control the change characteristics of custom squares, you can specify molang expressions to achieve changes, for example, query the state of the square according to molang to achieve changes in different square states. The content of Permutation is a Component component and a molang expression that represents the use of the corresponding Component under the specified molang expression Component is a composite component that contains the contents of all other components except permutations

collisionBox(Vector3f origin,Vector3f size)
Set the client-side collision box for this block. Blocks that do not use a custom geometry model usually do not need to use this method, since they are unit blocks

selectionBox(Vector3f origin,Vector3f size)
Set the client selection box for this block, you don't usually need to use this method for block that doesn't use custom geometry model, because it's a unit block

clientFriction(float friction)
Set the client friction factor of this block, which represents the player's movement speed on the block.The larger the value, the faster the movement

Step 3: build instance You can choose between two types of build, the default build and customBuild

customBuild(Consumer<CompoundTag> nbt)
The custom build parameter accepts a consumer function, and you are free to modify the definition of the block that will eventually happen to the client NBT (if modified incorrectly it may cause the custom block registration to fail)

About digging time control for custom blocks

In the new version of the API, PNX implements server-side authoritative block destruction, which means that the server side can be responsible for calculating the digging time of a custom block and when the block is dug up.
You no longer need to override calculateBreakTime to control the digging time of custom blocks, if you do please remove it, this may affect the server side calculation of the block digging time.
You can use the following two ways to control the break time of a custom block:

Overriding double getHardness()
which represents the block hardness, the default value of this method is 10 and it will be used by the server side to calculate the block mining time

Use CustomBlockDefinition.Builder#breakTime(double)
controls the time in seconds for the custom block to be mined on the client side (default value 999)

You may wonder why there are two methods to control the digging time of a custom block?
Using getHardness defines the hardness of the custom block, which allows the custom block to take different amounts of time to dig when it is dug by different levels and types of tools. And using CustomBlockDefinition.Builder#breakTime(double) defines the digging time of the custom block on the client side, which is a fixed value.
The break time of the custom block depends on the minimum value of the break time on the server side and the client side.
Typically, the client-side digging time is defined to take 999 seconds, so that the digging time of the custom block depends only on the server-side calculation. The hardness values can be chosen by referring to the hardness of other blocks inside PNX, which are 2 for wood, 5 for iron, and 35 for obsidian

About the friction of custom block

clientFriction(float friction) in CustomBlockDefinition#builder represents the client friction, which is used to control the speed at which the player walks on the custom block.The larger the value, the faster the movement.
Override the double getFrictionFactor() in the Block class to represent the friction on the server side, which is used to control the speed that player drops item,entity walk and boat movement on the block.

Custom block example

The following example implements a custom slab, where explanations are provided for each line of code.
Since CustomBlock is the interface, you can extends any of the internal block classes of the core before implementing a custom block, use their funtion.Or if you are not familiar with the internal implementation, you can choose to extends the Block class. Here you choose to extends the BlockTransparentMeta class, which means that the block is transparent.
Note that a lot of comments, line breaks and some logical implementations are omitted below to save lines, if you want to see the full source code and material package, you can go to AddonApi-ExamplePlugin

public class MySlab extends BlockTransparentMeta implements CustomBlock {
    //Define two block states, the first one means this is the top half, the second one means this is a complete plank
    public final BooleanBlockProperty BRIDGE_TOP_SLOT_BIT = new BooleanBlockProperty("bridge:top_slot_bit", false);
    public final BooleanBlockProperty BRIDGE_IS_FULL_BIT = new BooleanBlockProperty("bridge:is_full_bit", false);

    //define Namespace
    public String getNamespaceId() {
        return "powernukkitx:blue_mahoe_slab";

    //override with CustomBlock interface method
    public String getName() {
        return CustomBlock.super.getName();

    //override with CustomBlock interface method
    public int getId() {
        return CustomBlock.super.getId();

    //define block states in server-side 
    public BlockProperties getProperties() {
        return new BlockProperties(

    //Description client-side definition
    public CustomBlockDefinition getDefinition() {
        return CustomBlockDefinition
                .builder(this, "blue_mahoe_planks")//The first is a block instance, the second is a block material name
                .clientFriction(0.1f)//The friction of the client is 0.1, and the player walks on it like an ice
                .geometry("geometry.custom_slab")//Use the geometry model defined in the resourcepack
                .permutations(//Set the changes in different block states
                        new Permutation(Component.builder()//The collision box and selection box for this block in the lower state
                                .collisionBox(new CollisionBox(-8, 0, -8, 16, 8, 16))
                                .selectionBox(new SelectionBox(-8, 0, -8, 16, 8, 16))
                                .geometry(new Geometry("geometry.custom_slab")
                                        .boneVisibility("lower", true)
                                        .boneVisibility("upper", false))
                                "query.block_property('bridge:top_slot_bit') == false && query.block_property('bridge:is_full_bit') == false"),
                        new Permutation(Component.builder()//The collision box and selection box of this block in the upper state
                                .collisionBox(new CollisionBox(-8, 8, -8, 16, 16, 16))
                                .selectionBox(new SelectionBox(-8, 8, -8, 16, 16, 16))
                                .geometry(new Geometry("geometry.custom_slab")
                                        .boneVisibility("lower", false)
                                        .boneVisibility("upper", true))
                                "query.block_property('bridge:top_slot_bit') == true && query.block_property('bridge:is_full_bit') == false"),
                        new Permutation(Component.builder()//The collision box and selection box of the block in the state of the complete plank
                                .collisionBox(new CollisionBox(-8, 0, -8, 16, 16, 16))
                                .selectionBox(new SelectionBox(-8, 0, -8, 16, 16, 16))
                                .geometry(new Geometry("geometry.custom_slab")
                                        .boneVisibility("lower", true)
                                        .boneVisibility("upper", true))
                                "query.block_property('bridge:is_full_bit') == true")

    public double getHardness() {
        return 5;

    //The friction of the ice on the server side is 0.9
    public double getFrictionFactor() {
        return 0.9;

    public double getResistance() {
        return 5;

    public int getLightLevel() {
        return 15;

    public int getLightFilter() {
        return 0;

    public int getBurnAbility() {
        return 0;

    public int getBurnChance() {
        return 0;

    public int getItemMaxStackSize() {
        return 64;

CustomBlockDefinition API JavaDoc

© PowerNukkitX Dev team