Develop Guide
Please note that UltimateShop is not a traditional shop plugin. It can dynamically display products and prices (and even the each single price amount), unlike other shop plugins where one ItemStack corresponds to one price.
Add as dependency
As of March 20, 2026, the latest plugin version number is 4.3.4. If this date is too far away, then you should check the latest plugin version number yourself, as the provided plugin version may be outdated or unavailable.
<repositories>
<repository>
<id>repo-lanink-cn</id>
<url>https://repo.lanink.cn/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>cn.superiormc.ultimateshop</groupId>
<artifactId>plugin</artifactId>
<version>[PLUGIN VERSION]</version>
<scope>provided</scope>
</dependency>
</dependencies>repositories {
maven {
url "https://repo.lanink.cn/repository/maven-public/"
}
}
dependencies {
compileOnly group: 'cn.superiormc.ultimateshop', name: 'plugin', version: '[PLUGIN VERSION]'
}
UltimateShop Developer Integration Guide
This document is for developers who want to integrate with UltimateShop, not for regular server owners configuring shops.
The content here is based on the current 4.3.4 source code and focuses on the most common integration scenarios:
Depending on UltimateShop from your own plugin
Fetching shop items by shop ID / product ID
Querying player buy/sell usage counts and previewing prices
Listening to transaction events
Triggering bulk sell
Opening a shop or triggering quick buy/sell from your plugin
The code snippets below intentionally omit unrelated listener registration, class wrappers, and some basic imports, and only keep the parts directly related to UltimateShop integration.
1. Understand What UltimateShop Exposes
From the source code, the best external entry points are mainly these:
cn.superiormc.ultimateshop.api.ShopHelpercn.superiormc.ultimateshop.api.ItemPreTransactionEventcn.superiormc.ultimateshop.api.ItemFinishTransactionEvent
Besides that, there are also some public classes that are usable but are more internal implementation details:
cn.superiormc.ultimateshop.objects.ObjectShopcn.superiormc.ultimateshop.objects.buttons.ObjectItemcn.superiormc.ultimateshop.gui.inv.ShopGUIcn.superiormc.ultimateshop.methods.Product.BuyProductMethodcn.superiormc.ultimateshop.methods.Product.SellProductMethod
The practical way to think about them is:
Prefer the
apipackage firstUse the
objectspackage for read-only item/shop queriesUse
gui/methodsonly when you need more direct control, knowing they are more likely to change across upgrades
2. Declare the Dependency in Your Plugin
plugin.yml
If your plugin integrates with UltimateShop only when it is present, add at least a softdepend:
If your plugin cannot work without UltimateShop at all, use depend instead.
3. Check at Runtime That UltimateShop Is Present
If your integration logic should only be enabled when UltimateShop exists, this check is best placed in onEnable().
4. Most Common Entry Point: Fetch an Item by ID
The safest way is to fetch the target item directly by shop ID and product ID:
This is the recommended approach because it is deterministic.
Common read-only methods on ObjectItem include:
getShop(): owning shop IDgetProduct(): product IDgetDisplayName(Player): display namegetBuyPrice()/getSellPrice(): price definitionsgetReward(): product reward definitiongetPlayerBuyLimit(Player)/getPlayerSellLimit(Player): per-player limitsgetServerBuyLimit(Player)/getServerSellLimit(Player): global limits
5. Query Current Player Usage Counts
If you want to show something like "how many more times this player can still buy this item" inside your own plugin, use ShopHelper directly:
A typical usage looks like this:
Notes:
-1means unlimitedShopHelperwill create the use-times cache automatically if it does not exist yet
6. Preview Buy Cost
If you only want to display something like "how much this batch of items is worth" in your own GUI or message flow, use the preview methods in ShopHelper.
7. Preview Sell Reward
If you need the raw result objects instead of formatted strings:
These result objects are useful for display, logging, or secondary checks.
8. Listen to Transaction Events
UltimateShop provides two transaction events:
ItemPreTransactionEventItemFinishTransactionEvent
8.1 ItemPreTransactionEvent
This event fires before the actual transaction is finalized. It is suitable for:
statistics
external logging
webhook pushes
side-channel syncing
Example:
This event also gives you two important result objects:
getTakeResult(): what will be taken in this transactiongetGiveResult(): what will be given in this transaction
The common interpretation is:
On buy:
takeResultis usually the price the player pays, andgiveResultis usually the product rewardOn sell:
takeResultis usually the item(s) the player turns in, andgiveResultis usually the sell reward
8.2 ItemFinishTransactionEvent
This event fires after a successful transaction completes. It is suitable for:
success statistics
achievement unlocks
quest/task progress integration
post-success notifications
9. Bulk Sell: ShopHelper.sellAll
If you want your plugin to trigger UltimateShop's built-in bulk selling logic, use:
The third parameter, multiplier, is the sell reward multiplier.
For example:
1.0D: normal value2.0D: double sell reward0.5D: half sell reward
This method already performs the real transaction
This is the most common misuse point.
Internally, sellAll(...) directly runs the sell flow. That means it will:
check matching products
actually remove sold items
actually give sell rewards
execute sell actions
The returned Map<AbstractSingleThing, BigDecimal> is better understood as a summary of the sell result, not something that still needs another manual giveThing(...) call.
Wrong usage:
Correct understanding:
sellAllalready completes the transactionthe return value is only for display, logging, or statistics
10. Manually Execute Give / Take
If you already have a TakeResult or GiveResult and want to execute it yourself, you can use either the result object methods or the helper methods on ShopHelper.
Execute a Take
Execute a Give from a Result Map
Execute a Take from a Result Map
This style is more appropriate when you are building your own custom business flow, not when you are repeating a transaction UltimateShop has already executed.
11. Open a Shop from Your Plugin
Option A: Use the Command, Best Compatibility
If you do not want to depend on internal GUI classes, the safest approach is to dispatch the built-in command:
Based on the current command implementation, the common forms are:
Player opens their own shop:
/shop menu <shop>Console opens a shop for a player:
/shop menu <shop> <player>
The advantages of this approach:
no direct dependency on internal GUI classes
better compatibility if the internal implementation changes later
Option B: Call ShopGUI Directly
If you explicitly want the direct internal path, you can do this:
Parameter meaning:
first
false: whether to bypass menu conditionssecond
false: whether this should be treated as a reopen
Note: ShopGUI is not in the api package. This is an internal-class integration and is more likely to be affected by version changes than the command approach.
12. Trigger Quick Buy / Sell Directly
If you already know the exact shop and product, you can also call the internal trade methods directly.
Direct Buy
These parameters mean:
item: target productplayer: playertrue: whether to force failure messages to displayfalse: whether to skip the actual cost3: transaction amount
Direct Sell
These boolean parameters control:
whether to force failure messages
whether to skip actual cost / settlement
whether to enable max-sell logic
If you simply want your external plugin to perform one normal buy or sell, this is the most direct code path.
Still, this is internal API usage, so it is less stable than using the api package.
13. Practical Example: External GUI Entry with Remaining Usage Display
This is a very common pattern:
your own NPC or menu plugin only provides the entry point
all product logic, pricing, limits, and sell rules are still delegated to UltimateShop
That lets you reuse UltimateShop's existing systems as much as possible.
14. Understanding TakeResult, GiveResult, and AbstractSingleThing
These three types are easiest to understand together:
AbstractThingsrepresents a whole things container, such as the fullproducts,buy-prices, orsell-pricessectionAbstractSingleThingrepresents one single entry inside that containerTakeResult/GiveResultrepresent the final selected single things for one transaction, together with their calculated final amounts
A practical mental model is:
AbstractThings= a group of candidate rulesAbstractSingleThing= one candidate branchTakeResult/GiveResult= the final selected branches for this transaction
14.1 What AbstractSingleThing Is
AbstractSingleThing is the abstract base class for all single things, defined in AbstractSingleThing.java.
Conceptually, it describes one unit entry that can be checked, calculated, given, or taken.
In the current implementation, the common subclasses are:
ObjectSingleProduct: one product entry underproductsObjectSinglePrice: one price entry underbuy-pricesorsell-prices
The most important fields and responsibilities on it are:
type: the single thing type, inferred from configsingleSection: the config section for this one single thingapplyCondition: whether this branch should participate in selectionrequireCondition: whether this branch is allowed to complete after being selectedgiveAction/takeAction: actions that run when it is actually given or takenthings: the parentAbstractThingscontainer
The supported ThingType values are not limited to just "item". The detection logic lives in AbstractSingleThing.java:
HOOK_ITEMMATCH_ITEMCUSTOMHOOK_ECONOMYVANILLA_ECONOMYVANILLA_ITEMFREERESERVE
So the important idea is that AbstractSingleThing is not "one item". It is "one transaction-capable unit".
14.1.1 How Developers Can Tell Which Type a Single Thing Is
UltimateShop does not require you to declare ThingType manually. It infers the type from the config fields.
The detection order is important, because it is effectively "first matching rule wins".
Based on the current source logic, the rules are:
If both
hook-pluginandhook-itemexist, it isHOOK_ITEMIf
match-itemexists andMythicChangeris loaded, it isMATCH_ITEMIf
match-placeholderexists and this is not the free version, it isCUSTOMIf
economy-pluginexists, it isHOOK_ECONOMYIf
economy-typeexists buteconomy-plugindoes not, it isVANILLA_ECONOMYIf
materialoritemexists, it isVANILLA_ITEMIf only
amount-style numeric definition exists and none of the above matched, it isRESERVEIf none of the above matched, it is
FREE
You can use this quick mapping:
hook-plugin+hook-item->HOOK_ITEMmatch-item->MATCH_ITEMmatch-placeholder->CUSTOMeconomy-plugin->HOOK_ECONOMYonly
economy-type->VANILLA_ECONOMYmaterialoritem->VANILLA_ITEMonly
amount->RESERVEnothing meaningful defined ->
FREE
Some common examples:
This is a Vault economy entry:
so to know whether this is a Vault price, you can do this:
14.2 What AbstractSingleThing Does During Transactions
It mainly handles four jobs:
deciding whether it should participate
reading how much of it the player currently has
checking whether the player has enough to pay or enough room to receive it
producing real give/take behavior
The key methods are:
getApplyCondition(...): checksapply-conditionsgetRequireCondition(...): checksrequire-conditionsplayerHasAmount(...): reads owned amountplayerHasEnough(...): checks affordability / availability and can optionally perform the takeplayerCanGive(...): checks whether this thing can be given
This is also why the distinction matters so much:
apply-conditionsdecide whether a branch is selectedrequire-conditionsdecide whether a selected branch is allowed to continue
14.3 What TakeResult Is
TakeResult is the result object for "what this transaction needs to take", defined in TakeResult.java.
Its most important internal structure is:
That means:
key: the selected single thing
value: the final calculated amount / cost for that single thing in this transaction
The key fields are:
resultBoolean: whether the player has enough to pay / turn in these thingsconditionBoolean: whether all selected single things passed theirrequire-conditionsempty: whether the result is empty
The most important distinction is:
resultBooleananswers "is it affordable / available"conditionBooleananswers "is it allowed"
TakeResult.addResultMapElement(...) checks require-conditions while the entries are being added.
Actual execution happens in take(...).
That execution is not just "remove items". It does this:
calls
playerHasEnough(..., true, cost)for each selected single thingthen runs the single thing's
takeAction
So TakeResult is best understood as:
first, "what needs to be taken"
then, "execute the take and its related actions"
14.4 What GiveResult Is
GiveResult is the result object for "what this transaction needs to give", defined in GiveResult.java.
Like TakeResult, it also stores:
That means:
key: the selected single thing
value: the final amount that should be given for that single thing
It also tracks:
conditionBooleanempty
But it does not have a resultBoolean field.
That is because "can this actually be given" is not fixed when the result object is created. It is checked later inside give(...), where inventory capacity and item delivery are evaluated together.
The give(...) flow is:
iterate over
resultMapcall
playerCanGive(...)on eachAbstractSingleThingcollect all
GiveItemStackobjectsif any entry cannot be given, return
falseif all entries can be given, actually give them
So the right way to think about GiveResult is:
it describes "what should be given"
whether it can actually be delivered is only known at
give(...)time
14.5 Where They Sit in the Full Transaction Flow
A buy flow can be understood like this:
ObjectPrices.take(...)calculates the cost and returns aTakeResultObjectProducts.give(...)calculates the reward and returns aGiveResultthe trade logic checks
TakeResult.getResultBoolean()then checks
TakeResult.getConditionBoolean()andGiveResult.getConditionBoolean()then executes
GiveResult.give(...)finally executes
TakeResult.take(...)
The shared container abstraction is AbstractThings.java, which defines:
give(...) -> GiveResulttake(...) -> TakeResult
So the most stable external mental model is:
AbstractThingschooses final branches from a candidate setAbstractSingleThinghandles the logic of one branchTakeResult/GiveResultstore and execute the final selected result
15. Hook Integration: Economy Sources and Item Sources
UltimateShop manages economy hooks and item hooks through HookManager.
Built-in implementations are auto-registered at startup, for example:
Economy: Vault, PlayerPoints, CoinsEngine, UltraEconomy, and more
Items: ItemsAdder, Oraxen, MMOItems, EcoItems, Nexo, CraftEngine, and more
But HookManager also exposes public registration methods:
registerNewEconomyHook(...)registerNewItemHook(...)
That means other developers can register custom economy or item sources from their own plugin.
15.1 Custom Economy Hook
The abstract base class is AbstractEconomyHook.java.
The core methods you need to implement are:
getEconomy(Player, currencyID): read balancetakeEconomy(Player, value, currencyID): withdrawgiveEconomy(Player, value, currencyID): deposit
Optional:
isEnabled(): returnfalseif the underlying provider is not ready
The currencyID parameter comes from the config field economy-type:
Single-currency hooks like Vault will usually ignore currencyID, while multi-currency hooks like CoinsEngine use it to resolve the specific currency.
Minimal example:
Registration:
Important notes:
the registration name must exactly match
economy-pluginin configif you support multiple currencies, treat
economy-typeas yourcurrencyIDthe default
AbstractEconomyHook.checkEconomy(...)implementation checks balance first, then callstakeEconomy(...)when needed
15.2 Custom Item Source Hook
The abstract base class is AbstractItemHook.java.
You need to implement two core methods:
getHookItemByID(Player, itemID): build an item from the configured IDgetIDByItemStack(ItemStack): reverse-resolve an item back into your source ID
This maps to config like:
Minimal example:
Registration:
Important notes:
the registration name must exactly match
hook-pluginin configthe actual
hook-itemformat is entirely defined by your hookif
getIDByItemStack(...)cannot resolve an ID, UltimateShop can still build items from directhook-itemconfig, but reverse item-source detection features will not work
15.3 Good Built-In References
If you want working examples to copy from, the best built-in references are:
Economy hooks:
EconomyVaultHook: single currency, services-manager basedEconomyCoinsEngineHook: multi-currency, explicitly usescurrencyID
Item hooks:
ItemItemsAdderHook: simple ID -> ItemStack and ItemStack -> IDItemMMOItemsHook: composite ID format usingTYPE;;ID
Together, these show the hook style UltimateShop expects.
Last updated