Astral Realms Documentation Help

Item Data

Every ItemInstance carries a typed data bag — a Map<Key, Object> persisted alongside the item in the PDC. Built-in adapters cover XP/level progression and "remember a Material" use cases; plugins can register their own adapters via ItemDataRegistry.

How it works

┌──────────────────────────────────────────────────────────────────┐ │ ItemInstance │ │ ┌────────────┐ ┌──────────────┐ ┌──────────┐ ┌─────────────┐ │ │ │ uniqueId │ │ blueprint │ │ version │ │ data: Map │ │ │ └────────────┘ └──────────────┘ └──────────┘ └─────────────┘ │ └──────────────────────────────────────────────────────────────────┘ │ ItemDataRegistry.findByKey(key) ◄─────────────┘ │ ▼ ItemDataAdapter<T> (serialize / deserialize binary bytes ↔ T) │ ▼ PDC sub-key "astralitems:data" ─► nested PDC
  1. A blueprint declares its default-data block — keys are Key strings (e.g. astralitems:scalable), values are deserialised by the registered adapter.

  2. ItemFactory.make(blueprint) copies the default-data entries into the new ItemInstance's data map.

  3. When the stack is serialised, ItemInstanceDataType iterates the data map and asks each adapter to write its entry into a nested PersistentDataContainer keyed by the adapter's Key.

  4. When the stack is deserialised, the same key drives ItemDataRegistry.findByKey to recover the adapter and read the bytes back into a typed object.

Each piece of data is opaque to the framework — it is only the adapter that knows how to read or write it. New adapters can be added without touching the rest of the plugin.

ItemDataAdapter contract

public interface ItemDataAdapter<T> { Key key(); T deserialize(BinaryMessage message); void serialize(BinaryMessage message, T data); Class<T> type(); }

Method

Purpose

key()

The Key under which the adapter is registered. Used as the PDC sub-key and as the YAML key in default-data.

deserialize(BinaryMessage)

Read a value from the binary stream. Called once per item when the stack is loaded.

serialize(BinaryMessage, T)

Write a value to the binary stream. Called when the item is mutated and saved.

type()

Runtime class for type-safe lookups via ItemInstance.findDataByType.

BinaryMessage is the same primitive stream used by AstralCore's packet protocol — see Infrastructure › Messaging for the helpers (writeInt, writeEnum, writeOptional, writeUtf8, …).

Registering an adapter

ItemDataRegistry is a static @UtilityClass — register early (during plugin enable, before any blueprint that references the key is loaded):

import com.astralrealms.items.registry.ItemDataRegistry; ItemDataRegistry.registerAdapter(new MyCustomDataAdapter());

The registry uses a ConcurrentHashMap, so it is safe to register from any thread — but blueprints loaded before registration will not be able to resolve the key. Register synchronously on enable.

A minimal custom adapter

A "kill counter" that tracks how many entities a sword has killed:

public class KillCountData { private int kills; public KillCountData() { this(0); } public KillCountData(int kills) { this.kills = kills; } public int kills() { return kills; } public void kills(int kills) { this.kills = kills; } } public class KillCountDataAdapter implements ItemDataAdapter<KillCountData> { public static final Key KEY = Key.key("myplugin", "kill_count"); @Override public Key key() { return KEY; } @Override public KillCountData deserialize(BinaryMessage message) { return new KillCountData(message.readInt()); } @Override public void serialize(BinaryMessage message, KillCountData data) { message.writeInt(data.kills()); } @Override public Class<KillCountData> type() { return KillCountData.class; } }

Blueprint reference:

default-data: "myplugin:kill_count": kills: 0

Mutating from a pipeline link (example sketch — registering a custom link is not yet pluggable, but the same pattern applies inside a Bukkit listener):

plugin.items().fromItemStack(stack).ifPresent(instance -> { KillCountData data = instance.findDataByType(KillCountData.class).orElse(new KillCountData()); data.kills(data.kills() + 1); instance.setData(KillCountDataAdapter.KEY, data); plugin.items().updateItemStack(stack, instance); });

Making the data placeholder-accessible

To expose data through the placeholder system, the data class can implement ComplexPlaceholder (namespace() + get(PlaceholderContext)). The framework's built-in adapters do this — see ScalableItemData and StoredMaterialItemData. Implementations are then reachable from YAML as %instance_data_<key>_<subkey>%.

Built-in adapters

ScalableItemData

Key: astralitems:scalable

Holds a level + experience pair. Mutated by the scalable link.

public class ScalableItemData { private int level; // default 1 private int experience; // default 0 }

Placeholders (namespace scalable):

Placeholder

Resolves to

%instance_data_scalable_level%

Current level.

%instance_data_scalable_experience%

Accumulated XP since the last level-up.

%instance_data_scalable%

The data object itself (for [ifequal] requirements, etc.).

Default-data form:

default-data: "astralitems:scalable": level: 1 experience: 0

StoredMaterialItemData

Key: astralitems:stored-material

Remembers one Material. Used by set-stored-material, build-wand, and similar tools that need to "lock" a block type onto the item.

public class StoredMaterialItemData { private Material material; // default AIR }

Placeholders (namespace stored-material):

Placeholder

Resolves to

%instance_data_stored-material%

The stored Material (or AIR if unset).

%instance_data_stored-material_material%

Same as above — explicit subkey form.

%instance_data_stored-material_name%

Translatable component for the material (e.g. block.minecraft.oak_planks), or the none message when unset.

%instance_data_stored-material_sprite%

Adventure ObjectContents.sprite component pointing at the block / item texture — renders the material as an inline icon in chat/lore.

Default-data form:

default-data: "astralitems:stored-material": material: AIR

StoredEntityItemData

Key: astralitems:entity

Stores a captured entity as serialised bytes. Written by store-entity and cleared by release-entity — the backing data for mob-catcher / pet-wand items.

public record StoredEntityItemData(byte[] entityData) { }

Placeholders (namespace stored-entity):

Placeholder

Resolves to

%instance_data_stored-entity%

The data object itself.

%instance_data_stored-entity_empty%

true when no entity is stored.

Unlike the other built-in data types, StoredEntityItemData is written by pipeline links at runtime rather than declared in default-data — there is no meaningful literal for a serialised entity.

See also

Last modified: 25 July 2026