Shop Configuration
Every .yml/.yaml file under <plugin-data-folder>/shops/ is one shop. On enable, and again on /shop reload, PaperShopService#load() calls configurationManager().loadFolder(dataPath.resolve("shops"), ShopConfiguration.class), which walks the folder, deserializes each file into one ShopConfiguration, and indexes it by its own id field (not the filename) in an in-memory map. AstralShop ships one example, shops/wood.yml.
Example
Top-level fields
Field | Type | Description |
|---|---|---|
| String | The shop's identifier. Used as the map key shops are stored under and resolved by (e.g. |
| ComponentWrapper | The shop menu's title. A MiniMessage/legacy-formatted string, placeholder-aware like any other |
|
| The inventory slot indices this shop's items occupy. Exposed to the shop's menu blueprint as |
|
| The shop's items, keyed by item id. See items entries. |
ShopConfiguration also implements ComplexPlaceholder under the shop namespace, chaining id, title, slots, and items (the latter as an item-provider list). See Placeholders.
items entries
Each key under items: becomes one ShopItem; the YAML map key itself (oak_wood above) is captured as the item's id (ShopItemTypeSerializer reads node.key()). Fields, with their ShopItemTypeSerializer defaults:
Field | Type | Default | Description |
|---|---|---|---|
| double |
| Price to buy one unit, before per-player modifiers. Charged as |
| double |
| Payout for selling one unit, before modifiers. Paid as |
| ItemStackWrapper | — (required) | The item's appearance, and the exact |
| boolean |
| If |
| boolean |
| If |
| boolean |
| If |
| AstralCore action list | none | Run on a successful buy, in place of the item grant when |
| AstralCore action list | none | Run on a successful sell, in addition to the normal payout and item removal. |
display
display is an ItemStackWrapper block — deserialized by AstralCore's ItemStackWrapperTypeSerializer, the same serializer (and therefore the exact same block shape) used for a menu item's item-stack. See Menu Items — item-stack for the full field reference; the shipped wood.yml only sets material, name, and lore.
Accepted top-level keys are material, copy-from, name, lore, lore-modifiers, append-lore, amount, enchantments, and item-flags, plus the 19 registered item-component keys (armor-trim, attributes, durability, equippable, food, glow, hidden-components, hide-tooltip, instrument, item-model, leather-armor-color, model-data, potion-effect, potion-color, repair-cost, skull, tool, tooltip, tropical-fish-bucket). material is required unless copy-from is set — omitting both fails the load with SerializationException: "Material is required if copy-from is not provided".
display was previously deserialized by the plain ItemStackTypeSerializer, and three of that serializer's conveniences are gone:
It can no longer be written as a bare scalar string (
display: "OAK_LOG") — the node must be a map.materialis resolved withMaterial.valueOf(raw.toUpperCase()), not through the Minecraft registry, so a namespaced key such asminecraft:oak_logis rejected withInvalid material: minecraft:oak_log.Custom item-provider namespaces (
hdb-<id>,ce-<id>) are not accepted either.
material must therefore be a plain Bukkit Material enum name (case-insensitive) or a %placeholder% resolving to a Material.
buy-actions and sell-actions
Both are AstralCore action lists — "[action-id] args" strings run through PaperActionFactory, exactly like any other action list in the ecosystem. See Actions for the full catalogue of action ids (e.g. [console], [message], [give-item]).
Buy: if
give-itemistrue,buy-actionsis never run — the configured item is granted directly instead. Ifgive-itemisfalse, the item is not granted andbuy-actionsruns in its place.Sell:
sell-actionsalways runs after a successful sale (payment deposited, items removed from the seller's inventory) — it isn't a substitute for anything and can be omitted entirely.
Both lists run inside the EconomyService withdraw/deposit completion callback. PaperShopService does not reschedule them onto the main thread, so they execute on whatever thread the economy future completes on, alongside the inventory add/remove and the ITEM_BOUGHT/ITEM_SOLD messages. Action authors must not assume a main-thread context.
Category (shop id on items)
ShopItemTypeSerializer reads the enclosing shop's id back (node.parent().parent().node("id")) and stores it on every ShopItem as shopId. ModifierService uses this as the "category" a ShopCategoryModifier targets, letting a per-player price modifier apply to every item in a shop rather than one specific item. See Per-player price modifiers.
Menu rendering
/shop open <player> <shop> (and the [buy-shop-item]/[sell-shop-item] action flows) render a shop through MenuService#openShopMenu, which opens AstralCore's MenuContainer menu id shop (container.computeAndOpen(player, "shop", Map.of("shop", configuration))) — the clicked-or-opened ShopConfiguration is handed to the menu blueprint as its shop parameter, resolvable via %shop_*%/%item_*% placeholders.
AstralShop's own menus/ folder ships empty — it does not include a menus/shop.yml blueprint. A menu blueprint with id shop must be authored under the server's menus/ folder (Menu YAML format, see Menu Items) for /shop open to render anything; without one, opening a shop fails with IllegalArgumentException: Menu blueprint with id 'shop' not found.
Reloading
/shop reload (permission shop.command.reload) calls AstralShop#loadConfiguration(), which re-runs, in order:
shops.load()— reloads every file undershops/.menus.load()— reloads every menu blueprint undermenus/.loadEnum("messages.yml", ShopMessages.class)— reloadsmessages.yml.
See Commands for the full /shop command reference.
No config.yml
AstralShop has no config.yml — the shop files under shops/ and messages.yml are the only shipped configuration.