The PDC API allows Lua scripts to read and write persistent data on ItemStacks, entities, and tile-entity blocks using Paper's PersistentDataContainer system.
The API is accessed via the global PdcApi table in Lua scripts.
PdcApi can attach data to the following objects:
ItemStack— data is stored on the item'sItemMetaEntity/Player— data is stored on the entity itselfBlock— if it is a tile entity (e.g.CHEST,FURNACE,SIGN), the block state is used and updated automaticallyTileState— direct tile state snapshots; changes are persisted withBlockState.update()- Any other
PersistentDataHolder
Note: Block support requires the block to be a tile entity. Non-tile blocks (dirt, stone, etc.) have no PDC and will produce a warning.
The type argument for get, set, and has is case-insensitive and supports the following aliases:
| Type | Aliases | Lua Value | Notes |
|---|---|---|---|
byte |
byte, boolean, bool |
number / boolean | Booleans are stored as 0/1 bytes |
short |
short |
number | |
int |
int, integer |
number | |
long |
long |
number | |
float |
float |
number | |
double |
double |
number | |
string |
string |
string | |
bytearray |
bytearray |
table (1-indexed) | Values outside [-128, 127] are clamped |
intarray |
intarray, integerarray |
table (1-indexed) | |
longarray |
longarray |
table (1-indexed) | |
tagcontainer |
tagcontainer, container |
PDC userdata | Obtained from PdcApi:getContainer or PdcApi:get |
tagcontainerarray |
tagcontainerarray, containerarray |
table of PDC userdata | No nil elements allowed |
Keys are Minecraft NamespacedKeys. You can use them in two forms:
namespace:key— e.g.myplugin:counterkey— uses the plugin's namespace automatically
Invalid or empty keys log a warning and return nil.
Checks whether a key of a specific type exists on the holder.
Syntax:
local exists = PdcApi:has(holder, key, type)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)key(string): the PDC keytype(string): the PDC type
Returns:
exists(boolean):trueif the key exists with the specified type,falseornilotherwise
Example:
if PdcApi:has(item, "myplugin:owner", "string") then
DebugApi:log("Item has an owner")
endReads a value from the holder's PDC.
Syntax:
local value = PdcApi:get(holder, key, type)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)key(string): the PDC keytype(string): the PDC type
Returns:
value: the stored value, ornilif the key does not exist or the type does not match. Arrays are returned as Lua tables with 1-based indexing.
Example:
local owner = PdcApi:get(item, "myplugin:owner", "string")
if owner then
DebugApi:log("Owner: " .. owner)
endStores a value in the holder's PDC.
Syntax:
local success = PdcApi:set(holder, key, type, value)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)key(string): the PDC keytype(string): the PDC typevalue: the value to store (must match the type)
Returns:
success(boolean):trueif the value was stored,falseornilotherwise
Example:
PdcApi:set(item, "myplugin:owner", "string", "Steve")
PdcApi:set(item, "myplugin:damage", "int", 15)
PdcApi:set(item, "myplugin:tags", "bytearray", {1, 2, 3})Removes a key from the holder's PDC.
Syntax:
local success = PdcApi:remove(holder, key)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)key(string): the PDC key to remove
Returns:
success(boolean):trueif the mutation was applied,falseornilotherwise
Example:
PdcApi:remove(item, "myplugin:owner")Returns all keys stored on the holder.
Syntax:
local keys = PdcApi:getKeys(holder)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)
Returns:
keys(table): a 1-indexed Lua table of key strings, ornilon error
Example:
local keys = PdcApi:getKeys(item)
for i, key in ipairs(keys) do
DebugApi:log("Key " .. i .. ": " .. key)
endReturns the raw PersistentDataContainer for the holder. This is useful when you want to store a container inside another container.
Syntax:
local container = PdcApi:getContainer(holder)Parameters:
holder(ItemStack,Entity,Player,Block,TileState, orPersistentDataHolder)
Returns:
container(userdata): thePersistentDataContainer, ornilon error
Notes:
- For
ItemStackandBlock, the returned container is from a snapshot of the holder's state. Direct mutations to that container alone will not persist back to the item or block; usePdcApi:setorPdcApi:removeon the original holder instead. - For
Entity/Playerand other livePersistentDataHolders, the returned container is the live container.
Example:
-- Get a container from one item and embed it in another
local container = PdcApi:getContainer(item)
PdcApi:set(otherItem, "myplugin:embedded", "tagcontainer", container)registerCommand("tagitem", "myplugin.tagitem", function(sender, args)
local player = castTo(sender, "org.bukkit.entity.Player")
if player == nil then return end
local item = player:getInventory():getItemInMainHand()
if item == nil or item:getType():isAir() then
DebugApi:error("Hold an item first")
return
end
PdcApi:set(item, "myplugin:owner", "string", player:getName())
DebugApi:log("Item tagged")
end)PdcApi:set(player, "myplugin:kills", "int", 42)
PdcApi:set(player, "myplugin:ratio", "double", 1.25)
local kills = PdcApi:get(player, "myplugin:kills", "int")
DebugApi:log("Kills: " .. kills)local loc = player:getLocation()
loc:setY(loc:getY() - 1)
local block = WorldApi:getBlock(loc)
if block then
PdcApi:set(block, "myplugin:claimed", "boolean", true)
end-- Build a nested container from the item's PDC snapshot and store it inside the item.
-- Note: getContainer(item) returns a snapshot; build the sub-container data
-- and then use PdcApi:set on the original item to persist it.
local subContainer = PdcApi:getContainer(item)
PdcApi:set(subContainer, "sub:key", "string", "nested value")
PdcApi:set(item, "myplugin:sub", "tagcontainer", subContainer)ItemStackPDC operations require a non-air item with validItemMetaBlockPDC operations only work on tile entities and callBlockState.update()automaticallybytevalues andbytearrayelements are clamped to the[-128, 127]range; out-of-range values log a warningshortvalues are clamped to the[-32768, 32767]range- For the
boolean/booltype,setuses Lua truthiness: onlyfalseandnilare stored as0; all other values (including0and empty strings) are stored as1 - Array types with missing indices treat
nilentries as0and log a warning get,set,has, andremovereturnnilon invalid input or unsupported holders