English | 日本語
A guide for server operators and OPs. It covers installation, configuration, permission management, creating admin shops, auditing and migration. For player-facing operations, see the Player Guide (Basics) and the Player Guide (Advanced).
- Paper 1.21.8 or later (development is verified on 1.21.11)
- Java 21
- Vault: required. A Vault-compatible economy plugin (e.g. EssentialsX Economy) is needed separately.
- BedrockDialog: required. A Paper plugin distributed on Modrinth. Add Geyser + Floodgate as well if you want Bedrock support.
- PlaceholderAPI: optional. Install it to use the placeholders.
- Put
ModernVillagerShop-*.jarintoplugins/. - Put Vault and BedrockDialog there too (and PlaceholderAPI if you want it).
- Start the server;
config.yml,lang/messages_ja.ymlandlang/messages_en.ymlare extracted underplugins/ModernVillagerShop/. - Confirm a Vault-compatible economy plugin is up and running.
The main options are excerpted below. See src/main/resources/config.yml for the full defaults.
locale: ja_JP # default locale
fallbackLocale: en_US # fallback for missing keysLanguage files live at plugins/ModernVillagerShop/lang/messages_<locale>.yml. Keys must be present in both languages.
storage:
type: sqlite # sqlite | mysql
sqlite:
file: shops.db # relative to the plugin data folder
mysql:
host: localhost
port: 3306
database: vshop
username: root
password: ""
properties: "useUnicode=true&characterEncoding=utf8&useSSL=false"
poolSize: 8- SQLite guarantees consistency by serializing writers. Convenient, but limited in scalability.
- MySQL is designed around row locks (
SELECT ... FOR UPDATE) and READ COMMITTED or stricter. Suited to multi-server and larger deployments. - To switch backends later, see 8. Migration.
economy:
feeRate: 0.05 # fee rate for player shop SELL
feeRateAdmin: 0.05 # fee rate for admin shops
priceMin: 1
priceMax: 1000000
amountMax: 2304
fractionDigits: 2 # decimal digits
roundingMode: HALF_UP
priceDriftTolerance: 0.01 # tolerated drift after the price is frozen
currencyFormat: "<amount> <currency>"
priceProvider:
enabled: true # master on/off for the dynamic price SPI- Collected fees are removed from the server economy (they are not paid into any account).
- Setting
priceProvider.enabled: falsemakes admin shops fall back to static unit prices too. Use it to isolate problems or when a pricing extension is down.
shop:
maxShopsPerPlayer: -1 # -1 for unlimited
openDistance: 6.0 # right-click open distance
minDistance: 0.5 # reject placement this close to an existing shop
defaultLimitScope: PER_PLAYER
villagerNameFormat: "<shop_name> <gray>[<primary>]</gray>"
villagerNameFormatAdmin: "<shop_name>"
closeWithInventory: REFUSE # DISCARD | DROP | REFUSEmaxShopsPerPlayercounts only shops held with the PRIMARY role. Shops you merely co-own are not counted.closeWithInventorybehavior:DISCARD: discard the stock and deleteDROP: drop the stock at the shop location and deleteREFUSE: refuse deletion while stock remains (default, safest)
playerCache:
maxEntries: 5000 # entries beyond this are evicted by last_seen ascending
defaultSort: LAST_SEEN_DESC # LAST_SEEN_DESC | NAME_ASC
textureTtl: 7d # skin re-fetch TTLThis cache backs the player-picker UI (adding co-owners, choosing a PRIMARY transfer target, --player arguments, and so on). It is upserted on login, on logout, and when co-owners are looked up.
items:
blacklist:
- SHULKER_BOX
- WHITE_SHULKER_BOX
# ... every shulker color, BUNDLE, etc.- Specified by Bukkit Material name.
- Shulker boxes and bundles are included by default. Treat items that can hold other items carefully — they open unintended duplication and extraction paths.
- The plugin enforces no built-in blacklist of its own. Add and remove entries to match your policy.
ui.chest.icons.* lets you override the material, display name, lore and custom model data of every navigation icon in the chest UI (next/prev page, close, filter, sort, back, empty slot, unavailable, unknown player head). Combine it with a resource pack to match your server's look.
paper-plugin.yml defines the following role-like groupings. Granting them through a permission plugin such as LuckPerms is convenient.
modernvillagershop.player(default:true): the pack a regular player needs. Includesuse,egg,list,search,stats,history,open.nearby,edit.*,coowner.manage,coowner.transfer.modernvillagershop.admin(default:op): the admin pack. Includesadmin.egg,admin.edit,admin.export,admin.import,edit.others,coowner.manage.others,coowner.transfer.others,history.others,open.any,migrate,reload.
The notable ones:
| Permission | Purpose |
|---|---|
modernvillagershop.use |
Open a shop UI (buy / deliver) |
modernvillagershop.egg |
Use a player shop spawn egg |
modernvillagershop.admin.egg |
Use an admin shop spawn egg |
modernvillagershop.admin.edit |
Edit admin shops |
modernvillagershop.edit.* |
The individual edit operations on your own shop (move / rename / profession / suspend / delete / delete.refund) |
modernvillagershop.edit.others |
Edit someone else's shop (ignores role) |
modernvillagershop.coowner.manage.others |
Manage co-owners of any shop |
modernvillagershop.coowner.transfer.others |
Force a PRIMARY transfer on any shop (e.g. for players who left) |
modernvillagershop.history.others |
View other players' and other shops' trade history |
modernvillagershop.open.nearby / open.any / open.<shopId> |
Distance conditions for opening a shop UI. open.any wins; a per-shopId node is also possible |
modernvillagershop.migrate |
Storage migration |
modernvillagershop.reload |
Configuration reload |
- Role (PRIMARY / MANAGER / STAFF) is set per shop and decides what someone can do in that shop.
- Permissions (
modernvillagershop.*) decide whether the feature is available at all. - Example: a player without
modernvillagershop.edit.renamecannot rename their own shop even as PRIMARY. *.othersnodes are admin overrides that ignore role and apply to any shop. Grant them only to staff.
An admin shop has no owner and infinite stock, which makes it useful as a public store or NPC exchange.
-
As a user with the
modernvillagershop.admin.eggpermission:/vshop egg <target player> adminThe
adminegg type internally hasinf-equivalent capacity (paged 45 slots at a time). -
Hold the spawn egg and place it.
-
Register listing slots through the normal edit flow (
/vshop edit, orEdit itemsafter right-clicking the villager). Editing an admin shop requiresmodernvillagershop.admin.edit.
- Infinite stock: SELL is always available, and items delivered through BUY are absorbed (discarded) by the server.
- No payee: SELL revenue is handled by the system (nobody is paid).
- No co-owners: attempting it returns an
admin-shoperror. - Fees:
economy.feeRateAdminapplies, so you can tune it separately from player shops. - Dynamic pricing: the only shop type whose prices the
PriceProviderSPI can override (see 10. Dynamic price API).
Admin shop listing slots can be exported to and imported from YAML — useful for cloning an existing admin shop to another server, or editing a large number of slots externally.
Run these while looking at the target admin shop's villager within 8 blocks.
/vshop admin export <file name>
/vshop admin import <file name>
- Permissions:
modernvillagershop.admin.export/modernvillagershop.admin.import - Files are placed under
plugins/ModernVillagerShop/exports/. - export: if the file already exists it errors instead of overwriting. Use a different name or remove the existing file.
- import: deletes and replaces all existing slots. A backup is taken automatically beforehand and its path is reported in the message.
Practical uses: keep exported YAML under review in pull requests, or build it into a staging-to-production workflow.
| Command | Description | Key permission |
|---|---|---|
/vshop help |
Show help | — |
/vshop list [page] |
List shops | modernvillagershop.list |
/vshop open <shopId> |
Open a shop UI | open.nearby / open.any / open.<shopId> |
/vshop search <item> [page] |
Search by item name | modernvillagershop.search |
/vshop stats <shopId> |
Show statistics | modernvillagershop.stats |
/vshop history [shopId] [page] [--flags] |
Trade history | history / history.others |
/vshop edit [shopId] |
Edit menu | edit / edit.others |
/vshop coowner <shopId> |
Co-owner management UI | coowner.manage / .others |
/vshop transfer <shopId> <player> |
Transfer PRIMARY | coowner.transfer / .others |
/vshop egg <player> <lines|inf|admin> |
Give a spawn egg | egg / admin.egg |
/vshop admin export <file> |
Export admin shop slots to YAML | admin.export |
/vshop admin import <file> |
Import admin shop slots from YAML | admin.import |
/vshop migrate <from> <to> |
Storage migration | migrate |
/vshop reload |
Reload config, language files and messages | reload |
--from / --to on /vshop history accept YYYY-MM-DD or YYYY-MM-DDTHH:mm[:ss], interpreted in the server's default time zone.
- Trade history is persisted in the database. Users with
modernvillagershop.history.otherscan inspect other players and other shops via/vshop history <shopId>or--player <name>. - Every history record stores
basePrice(the slot's static unit price),finalPrice(the price actually traded) andresolvedBy(the list of PriceProvider ids applied). Use these to validate dynamic pricing. - Querying the database directly is more flexible than analyzing logs:
SELECTfromshop_transactionsinshops.db(SQLite) or in your configureddatabase(MySQL).
/vshop reload
- Reloads
config.ymland the language files. - Database connections are NOT rebuilt. Changing storage settings requires a server restart.
- Handy for verifying language file edits at runtime.
Data can be migrated between SQLite and MySQL in either direction.
/vshop migrate <from> <to>
Example: /vshop migrate sqlite mysql
- Permission:
modernvillagershop.migrate - Trading is paused during the migration.
- Steps:
- Initialize the schema (target)
- Bulk-copy every table
- Consistency check
- On failure the target data is rolled back and the source is left untouched — the design errs on the safe side.
Recommended flow:
- Announce maintenance and stop meaningful trading.
- Back up
config.yml'sstoragesection before switching it to the target configuration. - Verify the target MySQL connection details (and reflect them in
config.yml). - Run
/vshop migrate <from> <to>. - After the completion message, switch
storage.typeinconfig.ymland restart the server. - Once verified, discard the old data (e.g.
shops.db).
With placeholderapi.enabled: true (the default), these are available:
%mvshop_shop_count_<player>%: shops the player owns (PRIMARY only)%mvshop_shop_name_<shopId>%: shop name%mvshop_shop_owner_<shopId>%: owner name%mvshop_total_sales_<player>%: the player's cumulative sales%mvshop_total_purchases_<player>%: the player's cumulative purchases
Use them in scoreboards, chat prefixes, DeluxeMenus and so on.
An SPI is provided for other plugins to change admin shop prices dynamically (player shops are out of scope).
- Pipeline-shaped: multiple providers are applied in ascending
order. The static unit price is treated internally asorder = 0. - Each provider receives a
PriceContextand the previous stage'sPriceResult, and returns a price, a reason text and a cache TTL. PriceResult#reasonis appended to the end of the slot lore in the chest UI (plain text on Bedrock).- On a completed trade,
basePrice/finalPrice/resolvedBy(the applied provider ids) are recorded in the history.
- PriceSnapshot: the price is frozen the moment the purchase confirmation dialog opens and used until settlement.
- Drift tolerance: if the price re-resolved at settlement deviates beyond
economy.priceDriftTolerance, the trade is canceled automatically. - Rejection logic belongs in
ShopPreTransactionEvent. Providers are responsible for pricing only (stopping a trade from a provider is discouraged).
- Providers are assumed to run synchronously. Do not include blocking I/O (a contract, not an enforcement).
- On exception the provider is skipped and the previous stage's result is used (the trade is not stopped). A warning is logged.
- Use
PriceResult#ttlto declare the render cache lifetime; it suppresses repeated recalculation while the chest UI is drawn. - Master kill switch:
economy.priceProvider.enabled: false. Useful as a fail-safe during incidents.
See spec.md §12.3 for the detailed interface.
Bukkit events are provided for other plugins:
ShopCreateEvent/ShopDeleteEventShopPreTransactionEvent(cancellable, for rejecting trades)ShopTransactionEvent(after completion)ShopSlotChangeEvent(slot added / edited / removed)
The public API is retrieved through ServicesManager:
ModernVillagerShopAPI api = Bukkit.getServicesManager()
.load(ModernVillagerShopAPI.class);Register a PriceProvider with api.priceRegistry().register(plugin, provider). The API follows semantic versioning to maintain compatibility.
| Symptom | What to check |
|---|---|
| Disabled with an error on startup | Are Vault and BedrockDialog loaded? Java 21? Paper 1.21.8+? |
| Vault Economy not found | Is a Vault-compatible economy plugin installed alongside? Add EssentialsX Economy or similar. |
| A villager doesn't become a shop, or disappears | Is the chunk loaded? Shops are stored in the database and respawned by UUID check on chunk load (nothing happens while unloaded). |
| Inconsistencies after migrating to MySQL | Is the isolation level READ COMMITTED or stricter? Check that nothing disables SELECT ... FOR UPDATE. |
| Formatting disappears on Bedrock | By design. BedrockDialog flattens MiniMessage formatting to plain text, so write messages that don't depend on decoration. |
Dialog onClose doesn't fire |
Unsupported on Bedrock. This is why the current design relies on explicit cancel buttons. |
| Trade notifications don't arrive | Is notification turned off in the target player's player_preferences? The STAFF role is excluded by design. |
/vshop egg rejects the admin type |
Does the executor have modernvillagershop.admin.egg? |
/vshop admin export reports "no-target-villager" |
No shop villager within 8 blocks of your line of sight. Look straight at it and retry. |
Old data still shows after /vshop migrate |
You may not have switched storage.type in config.yml and restarted the server. |
- spec.md: the v1 specification (core design) (Japanese)
- dialog.md: Paper Dialog API + BedrockDialog notes
- adventure.md: Adventure / MiniMessage syntax
- modern-commands.md: Paper Brigadier command API
- Player Guide (Basics) / Player Guide (Advanced)