Skip to main content

Shops

Shop menus with fixed, dynamic or barter prices, stock, limits, taxes and three currency types.

Freev1.12Economy & crafting
RequiresTypeWriter0.9GUI0.17+VaultoptionalPlaceholderAPIoptionalDiscordoptional

Overview​

Shops lets players buy and sell items from an inventory menu that you build in the TypeWriter web panel. A shop is one Shop Definition entry: it holds the list of items on sale, their prices, the currency, the limits, the messages and the menu layout. A player opens it with /tw shop <id> or from an Open Shop action fired by a quest, an NPC or a button of another menu.

Each item is priced in one of three ways: a fixed price, a dynamic price that follows the stock, or barter, where the item is paid for with other items instead of money. Purchases and sales go through Vault, a PlaceholderAPI economy or a built-in points balance.

The menu itself is drawn with the GUI extension. You place markers in a layout where products appear (SHOP_ITEM) and tagged buttons for paging, closing and quantities, and Shops fills them in for each player.

Features​

  • Fixed, dynamic and item prices — a flat price per unit, a price that rises as stock falls, or a list of items to hand over.
  • Barter — priceMode ITEMS sells an article against one or several items, with a shop-wide default price and the {item_price} lore token.
  • Three currencies — VAULT, PLACEHOLDER (balance read from a PlaceholderAPI placeholder, changed with console commands) and a built-in POINTS balance.
  • Click actions and quantities — buy or sell 1, a stack, the maximum, everything, a custom amount from a dialog, or a fixed amount such as 10 in a sub-menu.
  • Stock, limits and resets — stock per item, per-player and global limits, shop-wide buy and sell caps, and resets on a cron, interval, daily, weekly or monthly schedule.
  • Taxes and promotions — a global tax, per-item tax overrides, and timed percentage discounts.
  • Menu in the panel — layout pool, sub-menu, filler item, price lore and default name and lore formats.
  • Tab of a shared menu — a shop can become a view of a larger GUI chassis through baseMenuId.
  • Admin commands — list, inspect, open, reset, edit stock and read transaction history.
  • Discord announcements — transactions are announced as an event that the Discord extension relays to a webhook.

Installation​

  1. Install the GUI extension (version 0.17 or newer) and TypeWriter on a Paper or Folia server.
  2. Optional, for the VAULT currency: install Vault and an economy plugin that registers with it.
  3. Optional, for placeholders in titles and lore, and for the PLACEHOLDER currency: install PlaceholderAPI.
  4. Download the Shops JAR from the GitHub releases (the button in the summary panel above), put it in plugins/Typewriter/extensions/ and restart the server.
  5. In the web panel, create one Shop Artifact entry and name it shop_artifact. Shops reads and writes its stock, limits and points through that entry.

Getting started​

Build a small weapon shop paid with Vault money:

  1. On a static page, add a Shop Artifact entry named shop_artifact.
  2. Add a Shop Definition entry. Set title to <gold>Weapons, rows to 3 and currency to VAULT.
  3. In items, add an item. Choose a diamond sword in item, set name to <aqua>Sword, priceMode to FIXED, fixedBuyPrice to 500 and fixedSellPrice to 100. Leave primaryAction on BUY_1 and secondaryAction on SELL_1.
  4. In layoutPool, add a simple layout with the id main, and write main in mainLayoutId. Add an item to it with buttonPrefix shop_button:, buttonType SHOP_ITEM, x 1, y 1, count 7 and direction right. This marker row is where products appear.
  5. Add three more items to the layout with buttonPrefix shop_button: and buttonType set to PREV_PAGE, NEXT_PAGE and CLOSE, placed on the bottom row. Give each an item and a displayName.
  6. Set fillerItem to a gray stained glass pane so unused slots are filled.
  7. Publish, then run /tw shop admin list in game to read the shop id, and open it with /tw shop <id>. To open it from a quest or an NPC instead, add an Open Shop action pointing at the shop.

Left click buys one sword, right click sells one, and shift + left click opens the buy sub-menu if you configured one.

Items shown, items sold

A shop shows as many products per page as it has SHOP_ITEM marker slots, and the paging buttons move through the rest. If no marker exists, the menu opens with no product at all.

How it works​

On open, the shop checks its access criteria, applies a pending reset, then builds the menu for that player. A click sends the action to the transaction handler, which checks the limits, computes the price with promotions and tax, takes the payment, hands over the item and records the stock, the limit counters and the history.

Layout and buttons​

A shop menu is a simple layout in the layoutPool of the Shop Definition, selected by mainLayoutId. Items of the layout carry a buttonPrefix of shop_button: and a buttonType:

buttonTypeRole
SHOP_ITEMA slot where a product is placed. Repeat it with count, direction, gap and repeatY. The prefix may be left empty for this marker.
NEXT_PAGE, PREV_PAGEMove through the pages of products.
INFOSends the lines of infoMessage.
CLOSECloses the menu.
BACKReturns to the previous menu, for example from a sub-menu.
BUY_1, SELL_1Buy or sell one unit. In a sub-menu, BUY_1:10 and SELL_1:10 trade 10 units; any number works.
BUY_STACK, SELL_STACKTrade a full stack of the item.
BUY_MAX, SELL_ALLBuy as many as possible, or sell everything matching.
BUY_CUSTOM, SELL_CUSTOMAsk the player for an amount in a dialog.
BUY_SUBMENU, SELL_SUBMENUOpen the sub-menu for the item.

Buttons other than SHOP_ITEM, NEXT_PAGE, PREV_PAGE, INFO, CLOSE and BACK act on the item the player selected, so they belong in the sub-menu layout. Always set the buttonPrefix to shop_button:: the default prefix belongs to another extension and the button would stay inert.

Create a second simple layout in the pool, put the quantity buttons in it and write its id in subMenuLayoutId. The title comes from subMenuTitle, where {item_name} is the name of the item. Items open it with the BUY_SUBMENU and SELL_SUBMENU actions, which are the default shift click.

Click actions​

Each item has three action slots:

FieldDefaultDefault click
primaryActionBUY_1Left click
secondaryActionSELL_1Right click
shiftActionBUY_SUBMENUShift + left click

The possible actions are NONE, BUY_1, SELL_1, BUY_STACK, SELL_STACK, BUY_MAX, SELL_ALL, BUY_SUBMENU, SELL_SUBMENU, BUY_CUSTOM and SELL_CUSTOM. The shop-wide interactionConfig decides which click triggers each action (buy 1, sell 1, buy stack, sell all, sub-menu, custom amount, navigation). Each click type can run only one action per item, so with the default configuration several actions mapped to a left click replace each other: change interactionConfig if you use more than three actions.

buyMaxEnabled switches BUY_MAX off for an item.

Amount dialog​

BUY_CUSTOM and SELL_CUSTOM open a dialog with one text field. Customize it with buyAmountPrompt, sellAmountPrompt, amountConfirmButton, amountInputLabel, amountInputPlaceholder, amountInputWidth and amountInputMaxLength. The answer must be a positive whole number; otherwise invalidAmountMessage is sent. The normal price, limit and balance checks run when the amount is confirmed.

Item display​

The lines of priceLore are appended to each product, after the item's own lore and its lore. defaultNameFormat and defaultLoreFormat fill in for items with no name or lore of their own. In these fields and in an item's name and lore, the following tokens are replaced; a line asking for a price that does not exist is dropped.

TokenReplaced by
{item}The item's own name.
{buy}, {sell}Formatted unit price. Dropped when the item cannot be bought or sold, when the price is zero, and on barter items.
{item_price}The price in items, one line per requested item such as - 12 × Copper Ingot (wording set by itemPriceLineFormat). Dropped on items that are not paid in items.
{stock}, {stock_max}Current and maximum stock.
{currency}The currencySymbol.

priceFormat shapes the displayed price: {amount} is the number with two decimals and {currency} the symbol.

Prices and economy​

Price modes. FIXED uses fixedBuyPrice and fixedSellPrice. DYNAMIC uses dynamicPricing: the price starts at price (and sellPrice for sales) when the stock is full and moves toward max as stock falls, in steps of step of stockMax, clamped by min and max and rounded to rounding. ITEMS is barter.

Barter. With priceMode ITEMS, list the cost in the item's itemPrice, or once for the whole shop in defaultItemPrice. Each line is an item and an amount per unit bought. Several lines are all required at once, and buying several units multiplies every line. An article's own list replaces the shop's list, it is never added to it. An item with no price anywhere is refused and logged, never given away. The payment is checked on the whole inventory before anything is removed, space for the bought item is checked first, and catalogue items are matched by identity rather than by material only. BUY_MAX buys as many units as the inventory can pay. A barter item cannot be sold back for money. Missing items are reported with cannotAffordItemsMessage, where {missing} lists them and {amount} is the quantity.

Currencies.

currencyBehavior
VAULTUses the economy registered with Vault. Needs the Vault plugin and a provider.
PLACEHOLDERReads the balance from balancePlaceholder and changes it by running addCommand and removeCommand from the console, where {player} and {amount} are replaced. All three must be set. Numbers such as 1,250.5 and 12.5k are understood.
POINTSA balance kept by Shops in its artifact. It starts at 0 and grows when players sell items; the release has no command to grant points.

Taxes. taxRate is a percentage added to purchases and subtracted from sales. With taxMode PER_ITEM, an item's taxRateOverride (0 or more) replaces it for that item; -1 keeps the shop rate.

Promotions. A Shop Promotion discounts the buy price and the sell reward of a shop by a percentage, for all items or the listed targetItemIndices (position in items, from 0).

Stock, limits and resets​

  • dynamicPricing.stockMax sets the stock of an item. It starts full, goes down when players buy and up when they sell. 0 means no stock tracking. With perPlayer on, every player has their own stock of each item; a reset refills everyone's. While a shop is open, defaultRefreshTicks (and an item's refreshTicks) make it redraw when the stock shown changes, so a player sees purchases made by others. An item's guiSlot pins it at a fixed slot, and sellAllEnabled switches off its sell-all action. Details in Shop Definition.
  • playerLimit and globalLimit cap how many units of one item a player, or everybody together, can buy per reset cycle. globalPlayerLimit, globalBuyLimit and globalSellLimit cap the whole shop; 0 means unlimited. limitReachedMessage is sent when a cap is hit.
  • reset restores stock and clears the limit counters. Policies are none, cron (an expression such as 0 0 * * *), interval (seconds), daily, weekly (day 1 is Monday) and monthly. Times use the server time zone. The first check of a cycle happens when the shop is opened, and a check also runs every minute.
  • criteria restricts access to the shop and sends criteriaFailMessage when they do not match. An item whose own criteria do not match is shown as lockedItem and cannot be bought.

Commands​

Subcommands of /typewriter, usually typed as /tw.

CommandDescription
/tw shop <shop>Open a shop for yourself. Requires typewriter.shop.open.
/tw shop adminList the admin commands. Requires typewriter.shop.admin.
/tw shop admin listList loaded shops with their id, name, item count and pool count.
/tw shop admin info <shop>Show a shop definition and its items with their index.
/tw shop admin open <shop> [target]Open a shop for a player.
/tw shop admin refresh [target]Redraw the open shop of a player.
/tw shop admin close [target]Close the open shop of a player.
/tw shop admin reset <shop>Reset stock and limits now.
/tw shop admin stock <shop> <item> getShow the stock of the item at that index.
/tw shop admin stock <shop> <item> set <amount>Set the stock. Also add and remove.
/tw shop admin history [target]Show the last 10 transactions of a player since the last restart.

<shop> is the entry id, completed with the Tab key. <item> is the position of the item in the shop items, starting at 0.

Editable texts​

No text shown in game is fixed in the code. Three kinds of text can be changed without rebuilding anything.

What a player reads while trading is a field of the Shop Definition: purchaseMessage, sellMessage, cannotAffordMessage, limitReachedMessage, priceLore and the others described above.

Price wordings, trend icons and pool lines are fields of the entry they belong to. Their default is the text the extension always showed.

FieldEntryDefaultUsed for
trendUpIconShop Definition<red>▲</red>The trend_icon placeholder of an item whose price went up since the last purchase or sale.
trendDownIconShop Definition<green>▼</green>The same placeholder when the price went down.
trendStableIconShop Definition<gray>●</gray>The same placeholder when the price did not move, or when no earlier price is known.
itemPriceLineFormatShop Definition- {amount} × {name}One line of the {item_price} lore token, for each item a price in items asks for. {amount} is the quantity, {name} the item name.
unknownItemNameShop Definitionunknown itemThe name of a price item whose own name cannot be read, in {item_price} and in the missing items list.
missingItemFormatShop Definition{amount} × {name}One entry of the {missing} list of cannotAffordItemsMessage. {amount} is the quantity that is lacking.
missingItemSeparatorShop Definition, What goes between two entries of the {missing} list.
defaultItemNameShop DefinitionItemThe {item_name} of subMenuTitle for an item that has no name.
noPriceTextShop Definition—Shown instead of a number where a price in items has none: the buy_price and sell_price placeholders, and {price} in the inventory full message of such an item.
cooldownFormatShop Definition{d}d:{h}h:{m}m:{s}sThe pattern of the cooldown placeholder when the placeholder gives none. {d} days, {h}, {m}, {s} padded to 2 digits.
limitedLoreShop Pool<yellow>Limited: <red>{limit}The last lore line of a limited item of the pool. {limit} is the quantity available.
unlimitedLoreShop Pool<yellow>UnlimitedThe last lore line of an item of the pool that has no limit.

Use %typewriter_shop:<shop>:<n>:trend_icon% in the lore of an item to show the icon. %typewriter_shop:<shop>:<n>:trend% returns up, down, stable or unknown, and these four words are fixed because conditions compare against them.

What the /tw shop commands answer, the /tw shop admin commands included, is a TypeWriter snippet. Open plugins/Typewriter/snippets.yml, find the key under content.shop, change the value and run /tw reload. The extension registers all its snippets with their default text when it starts, so they appear in the file after the first start. Keep the defaults and nothing changes.

Answers are MiniMessage and are sent without the Typewriter prefix, so the colour of an answer is part of its text. A {name} in a text is replaced by the value named in the table; a placeholder that does not exist stays as written, and a value is never searched for placeholders again. A value that comes from your pages (a shop name, a player name, an inventory title) is shown as written: its tags are never interpreted. The server console log stays in English.

Snippet (under content.shop.)DefaultPlaceholders
help<gold>Shop commands:</gold> <yellow>/tw shop <id></yellow> to open a shop, <yellow>/tw shop admin</yellow> for operator tools.none
unknown_shop_errorUnknown shop: {shop} (plain text){shop} (what was typed)
admin.helpthe list of admin commands, one per linenone
admin.unknown_shop<red>Unknown shop: {shop}{shop}
admin.list_empty<yellow>No shop definitions are currently loaded.none
admin.list_header<gold>Loaded shops ({count}):{count}
admin.list_line<white>- {id} | {name} | items={items} | pools={pools}{id}, {name}, {items}, {pools}
admin.opened<green>Opened {shop} for {player}.{shop}, {player}
admin.refreshed<green>Refreshed the shop session for {player}.{player}
admin.closed<green>Closed the shop session for {player}.{player}
admin.reset<green>Reset runtime stock and limits for {shop}.{shop}
admin.history_header<gold>Recent transactions for {player} ({count}):{player}, {count}
admin.history_empty<yellow>No transactions recorded in the current runtime.none
admin.history_line<white>{time} | {type} | {shop}[{index}] | amount={amount} | price={price}{time}, {type}, {shop}, {index}, {amount}, {price}
admin.info_title<gold>=== Shop {shop} ==={shop}
admin.info_name<white>Name: {name}{name}
admin.info_inventory_title<white>Title: {title}{title}
admin.info_currency<white>Currency: {currency} | rows={rows}{currency}, {rows}
admin.info_items<white>Items: {items} direct | pools={pools} | access criteria={criteria}{items}, {pools}, {criteria}
admin.info_reset<white>Reset policy: {policy}{policy}
admin.info_item<white>[{index}] {name} | buy={buy} sell={sell} | playerLimit={playerLimit} globalLimit={globalLimit} | item criteria={criteria}{index}, {name}, {buy}, {sell}, {playerLimit}, {globalLimit}, {criteria}
admin.info_item_unnamed<unnamed> (plain text)none; it is the {name} of an item with no name
admin.stock_unlimited<yellow>{shop}[{index}] uses unlimited stock.{shop}, {index}
admin.stock_value<green>{shop}[{index}] stock: {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.stock_out_of_range<red>Stock must stay between 0 and {max}.{max}
admin.stock_updated<green>{shop}[{index}] stock set to {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.unknown_item<red>Unknown direct item index {index} in {shop}.{index}, {shop}

unknown_shop_error is a command error, shown in red by the game as the player types: it is plain text, without MiniMessage.

Permissions​

PermissionGrants
typewriter.shop.open/tw shop <shop>. Granted to everyone by default.
typewriter.shop.adminEvery /tw shop admin command. Operators only by default.

Placeholders​

PlaceholderAPI placeholders, with the typewriter expansion. <shop> is the shop entry name or id and <n> the position of the item in items, from 0.

PlaceholderReturns
%typewriter_shop:<shop>:name%Name of the entry.
%typewriter_shop:<shop>:entryid%Id of the entry.
%typewriter_shop:<shop>:cooldown%Time until the next reset, as 0d:00h:00m:00s by default (the cooldownFormat field of the shop; earlier releases used j for days). Add a format after an underscore, for example cooldown_{h}h{m}m, using {d}, {h}, {m} and {s}.
%typewriter_shop:<shop>:<n>:buy_price%Buy price of the item; the noPriceText of the shop (— by default) on barter items.
%typewriter_shop:<shop>:<n>:sell_price%Sell price of the item; the noPriceText of the shop (— by default) on barter items.
%typewriter_shop:<shop>:<n>:stock%Current stock.
%typewriter_shop:<shop>:<n>:stock_max%Maximum stock.
%typewriter_shop:<shop>:<n>:has_promotion%true when a promotion applies to the item.

Integration​

  • GUI — Shops opens its menus through the GUI session service, so gui:back, sounds, extended chassis and baseMenuId behave as in any other menu. Set baseMenuId to a shared Open GUI shell, add views, and the shop becomes a tab: contentX, contentY, contentColumns and contentRows place the products inside the shell.
  • Discord — after each purchase or sale Shops fires a ShopTransactionEvent. When the Discord extension is installed and the shop sets notificationWebhookId, a Shop Notification entry formats the announcement.
  • Sounds — buySound, sellSound and deniedSound play on the matching outcome.

Entries​

6 entries, generated from the extension source.

Actions1
Artifacts1
Manifests4

Troubleshooting​

The menu opens but shows no product. The layout has no SHOP_ITEM marker, mainLayoutId does not match a layout id in layoutPool, or the layout is not a simple layout. Products only appear on marker slots.

A button does nothing. Its buttonPrefix is not shop_button:, or its buttonType is misspelled (types are upper case). A paging or close button needs the prefix too; only SHOP_ITEM tolerates an empty prefix.

The shop says "No economy available". With VAULT, Vault or its economy provider is missing. With PLACEHOLDER, one of balancePlaceholder, addCommand or removeCommand is empty. With a barter item, the message also appears when no itemPrice and no defaultItemPrice is set, and the console logs the shop id and the item number.

Stock and limits are lost on restart, or never saved. There must be a Shop Artifact entry whose id or name is shop_artifact.

An item cannot be sold. Its sell price is zero (barter items always are), sellEnabled is off, or the player does not hold enough matching items. Only items with an identical item type and data count.

The console warns that an article cannot be shown, or was moved. An item with a guiSlot holds a SHOP_ITEM marker. When pinned items hold every marker, the other items are placed on the free cells of the content area; when there is no free cell left, the item is not shown. Add SHOP_ITEM markers or free a slot.

Items from itemPools appear but cannot be bought. Only the entries of items are handled by the buy and sell actions in this release; pool items are displayed after them.

Limits never reset. reset is none. Choose a policy or use /tw shop admin reset <shop>.

/tw shop <id> says the shop is unknown. The command takes the entry id, not the title. Use /tw shop admin list.

  • GUI — layouts, sessions and the marker mechanism used by shop menus.
  • Discord — announces shop transactions on a webhook.
  • Quest Codex — uses the same chassis idea for quest menus.