# Add Storage

> Add data persistence to a Minecraft plugin — pick and wire the right store: PersistentDataContainer on items/entities, a YAML data file, or a real database (SQLite/MySQL via a shaded, relocated JDBC driver). Use this whenever the user wants to save/load data, remember values across restarts, persist per-player stats, store custom item/entity tags, keep a database, homes/economy balances, or 'don't lose it when the server restarts' in their Bukkit/Paper plugin. Reads the target stack from .mcplugin/config.yml.

- Skill: `itamarb2010-jpg/add-storage` (Agent Skill)
- Install (CLI): `npx skillmds@latest add itamarb2010-jpg/add-storage`
- Raw SKILL.md: https://api.skillmd.com/api/skills/itamarb2010-jpg/add-storage/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: itamarb2010-jpg (https://skillmd.com/u/itamarb2010-jpg)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/itamarb2010-jpg/add-storage

---


# Add data persistence

Pick the right store for the data and wire it safely (async I/O, main-thread apply, clean
shutdown), matching the project's stack and conventions. Follow the collaborative rule: propose
the design, show the code, then write after the user approves.

## Phase 1: Load context

- Read `.mcplugin/config.yml` for `platform`, `mc_version`, `package`, `main_class`,
  `plugin_name`, `build_tool`, `group_id`. If it's missing, tell the user to run
  `/setup-platform` (and `/scaffold`) first.
- Read the references and pitfalls (relative to this skill; `Glob` as fallback):
  - `../../references/api/persistence-config.md` — PDC, `NamespacedKey`, YAML config APIs
  - `../../references/api/scheduler.md` — async vs. main thread; Folia's schedulers
  - `../../references/pitfalls.md` — the "Persistence", "Threading", and "Memory leaks" sections
- Look at existing source to match style: `Glob` `src/main/java/**/*.java`, read the main class
  and the build file (`pom.xml` / `build.gradle*`) in case a driver + relocation is needed.

## Phase 2: Clarify the data

Ask (AskUserQuestion or plain text) only what you can't infer:
- **What data** — fields and types (e.g. homes = name → world + x/y/z).
- **Scope** — per-player, per-item, per-entity, per-world, or global.
- **Size & shape** — a handful of values vs. thousands of rows; flat vs. relational (does it need
  queries/joins, or just load-all-on-start)?
- **Concurrency** — is it read/written on hot paths, or occasionally?
- **Multi-server** — will more than one server instance share this data? That rules out SQLite
  and a local YAML file and points at MySQL (or another shared DB).
- **Loss tolerance** — must every write be durable immediately, or is periodic/on-quit saving
  acceptable? This decides between write-through and a batched save task.

## Phase 3: Choose the store (explain the pick)

- **`PersistentDataContainer` (+ reused `NamespacedKey`)** — best when the data *belongs to* a
  specific item, entity, or chunk (custom item tags, mob metadata). Survives with the object; no
  file to manage. Prefer this over the deprecated metadata API and NBT string hacks.
- **A YAML data file** (separate from `config.yml`) — good for small/medium structured data
  keyed by `UUID` (homes, small stat maps). Simple, human-readable; load on enable, save on
  change/disable. Not for high-frequency writes or large datasets.
- **A real database — SQLite (embedded) or MySQL (shared)** — for large, relational, or
  multi-server data. The **JDBC driver must be added to the build and SHADED into the jar, then
  RELOCATED** (shade/shadow relocation) to avoid classpath clashes with other plugins. SQLite is
  a single local file; MySQL needs connection details in `config.yml`.

State the recommendation and why, given the scope and size from Phase 2.

## Phase 4: Implement

Generate, in the project's package:
1. **The chosen store**:
   - *PDC:* build `NamespacedKey`s once (reuse them) and read/write typed values on the object.
   - *YAML:* a data-access class wrapping a `YamlConfiguration` file under the data folder.
   - *DB:* the build change (dependency + shade/relocate config), a connection/pool setup, schema
     creation (`CREATE TABLE IF NOT EXISTS ...`), and parameterised queries (never string-concat
     SQL).
2. **Do all disk/DB I/O off the main thread** (async task), then hop back to the main thread to
   apply results to the world/players/inventories — the Bukkit API is not thread-safe. On Folia
   use the appropriate scheduler (`scheduler.md`); `Bukkit.getScheduler()` assumptions don't hold.
3. **Store `UUID`s, never `Player`/`Entity` objects** (holding them leaks memory and pins logged-
   off players). Look the object up from the `UUID` when needed.
4. **Lifecycle**: load/open in `onEnable`; **flush and close in `onDisable`** (save YAML, close DB
   connections/pool) so nothing is lost on shutdown. Cancel any repeating save task there too.

Show the new/edited files (store class, main-class `onEnable`/`onDisable` diffs, and any build
file diff for the driver) and get approval before writing.

## Phase 5: Verify + hand off

- Re-check the pitfalls: all I/O is **async** with a **main-thread hop** to apply; only **`UUID`s**
  are stored (no retained `Player`/`Entity`); `NamespacedKey`s are reused not recreated; SQL is
  parameterised; a DB driver is **shaded and relocated** in the build; and everything is **flushed/
  closed in `onDisable`**.
- Suggest next steps: "`/build` to compile, then `/run-server`; write some data, restart, and
  confirm it reloads."

Do not fabricate APIs for a Minecraft version newer than `../../references/api/VERSION.md`
documents — if unsure a method exists in the target version, say so and verify.

