docs/paper/storage/SKILL.md
# Storage Skill — Paper ## Purpose Reference this skill to choose the right storage mechanism for a Paper plugin. Covers when to use config files, PersistentDataContainer (PDC), or a full database, and links to detailed implementation guides for each. ## When to Use This Skill - Deciding where to persist plugin data - Storing per-entity or per-block metadata - Managing plugin configuration that users can edit - Setting up a database for large-scale or cross-server data ## API Quick Reference
npx skillsauth add MrPippi/MPS docs/paper/storageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill to choose the right storage mechanism for a Paper plugin. Covers when to use config files, PersistentDataContainer (PDC), or a full database, and links to detailed implementation guides for each.
| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| JavaPlugin#saveDefaultConfig() | Write bundled config.yml to disk if not present | Call in onEnable |
| JavaPlugin#getConfig() | Get the in-memory FileConfiguration | Cached after first load |
| JavaPlugin#reloadConfig() | Re-read config.yml from disk | For /reload support |
| JavaPlugin#saveConfig() | Write in-memory config back to disk | After modifying values |
| ConfigurationSection | Section of a YAML config | Nested key grouping |
| PersistentDataContainer | NBT-backed key-value store on entities/blocks | Persists across restarts |
| NamespacedKey | Key for PDC entries | new NamespacedKey(plugin, "key_name") |
| PersistentDataType | Type descriptor for PDC values | STRING, INTEGER, DOUBLE, BYTE_ARRAY, etc. |
| HikariDataSource | HikariCP connection pool | For MySQL / SQLite |
| Data Type | Recommended Storage | Reason |
|-----------|-------------------|--------|
| Plugin configuration (user-editable) | config.yml / FileConfiguration | Human-readable, easy to edit |
| Per-entity or per-block metadata | PersistentDataContainer | Tied to entity lifetime, no separate DB needed |
| Per-player persistent data (small) | PDC on OfflinePlayer | Stored in player data files |
| Per-player persistent data (large / queried) | Database (HikariCP) | Enables cross-server queries, bulk operations |
| Cross-server shared state | Database (HikariCP + MySQL) | Single source of truth across instances |
| Temporary runtime state | Plain Java Map<UUID, T> | Discarded on restart — for caches only |
package com.yourorg.myplugin.storage;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
public class StorageExamples {
private final JavaPlugin plugin;
// --- Config ---
public void loadConfig() {
plugin.saveDefaultConfig(); // write defaults if absent
FileConfiguration config = plugin.getConfig();
String greeting = config.getString("messages.greeting", "Welcome!");
int maxPlayers = config.getInt("limits.max-players", 50);
}
// --- PersistentDataContainer ---
private final NamespacedKey killKey;
public StorageExamples(JavaPlugin plugin) {
this.plugin = plugin;
this.killKey = new NamespacedKey(plugin, "kill_count");
}
public void incrementKills(Player player) {
PersistentDataContainer pdc = player.getPersistentDataContainer();
int current = pdc.getOrDefault(killKey, PersistentDataType.INTEGER, 0);
pdc.set(killKey, PersistentDataType.INTEGER, current + 1);
// No manual save needed — persisted automatically with the player data
}
public int getKills(Player player) {
return player.getPersistentDataContainer()
.getOrDefault(killKey, PersistentDataType.INTEGER, 0);
}
}
Not calling saveConfig() after set(): getConfig().set(key, value) modifies only the in-memory object. Call saveConfig() to persist to disk, or data is lost on restart.
Creating NamespacedKey with plugin-unrelated namespaces: Always use new NamespacedKey(plugin, "name"). Using NamespacedKey.minecraft("name") will create keys in the minecraft: namespace, which may conflict with vanilla data or other plugins.
Reading PDC on async threads: PDC reads/writes are technically possible off the main thread, but the entity must not be modified concurrently. Safest: always access PDC on the main thread.
Using raw Java HashMap for persistent data: In-memory maps are cleared on restart. Use PDC for entity-bound data or a database for anything that must survive restarts.
Blocking the main thread with database queries: All SQL calls must run asynchronously. See database-hikari.md for the async pattern.
PersistentDataType added LIST and TAG_CONTAINER compound types for nested structured data.config.yml + YAML API is unchanged from older Paper versions.development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag