Configuration
This page documents the keys of config.yml. EmakiStorage's configuration is split across three files, and the capacity domain model has a page of its own:
| Topic | Page |
|---|---|
The gui/storage_gui.yml template plus the gui and display sections | GUI and Display |
The capacity section, four capacity sources, three-level stackLimit, shrinking and overflow | Capacity |
The unlock_costs.yml paid tiers | Unlock Costs |
Run /estorage reload afterwards to reload config, language and GUI templates.
config.yml Top Level
version: "1.0.6"
language: "zh_CN"
release_default_data: true
op_bypass: false
debug: false| Field | Type | Default | Description |
|---|---|---|---|
version | string | 1.0.6 | Default resource version marker, maintained by resource sync. Same value as AppConfig.CURRENT_VERSION. |
language | string | zh_CN | Uses lang/<language>.yml. |
release_default_data | boolean | true | Whether to release the bundled unlock_costs.yml when it is missing. |
op_bypass | boolean | false | When true, OPs are treated as holding emakistorage.admin. |
debug | boolean | false | Parsed into the config object, but no branch in the current implementation reads it; manage debugging through /estorage debug. |
unlock
unlock:
overflow_policy: lock_readonly
purchase_enabled: true
cost_file: "unlock_costs.yml"| Field | Type | Default | Description |
|---|---|---|---|
overflow_policy | enum | lock_readonly | How to handle occupancy that exceeds a lowered capacity; the four values are documented in Capacity. |
purchase_enabled | boolean | true | Whether in-GUI paid expansion is available. Also requires the emakistorage.unlock.purchase permission. |
cost_file | string | unlock_costs.yml | File name of the paid tier configuration, documented key by key in Unlock Costs. |
behavior
behavior:
overflow_on_withdraw: return
withdraw_amounts:
left: 1
right: 16
shift_left: 32
shift_right: 64
withdraw_prompt:
enabled: true
mode: "auto"
dialog:
title: "dialog.withdraw.title"
can_close_with_escape: true
pause: false
body:
- "dialog.withdraw.body"
inputs:
- type: "text"
key: "amount"
label: "dialog.withdraw.label"
max_length: 32
buttons:
- label: "dialog.confirm"
deposit_filter:
mode: blacklist
entries: []
allow_unique_items: true
multi_slot_stacking: false
batch_max_ops: 200
default_sort: amount_desc
player_sort_enabled: true| Field | Type | Default | Description |
|---|---|---|---|
overflow_on_withdraw | enum | return | What happens to the part the inventory refuses: return credits it back to storage, drop drops it on the ground. |
withdraw_amounts.left | long | 1 | Left-click withdrawal amount. |
withdraw_amounts.right | long | 16 | Right-click withdrawal amount. |
withdraw_amounts.shift_left | long | 32 | Shift + left-click withdrawal amount. |
withdraw_amounts.shift_right | long | 64 | Shift + right-click withdrawal amount. |
withdraw_prompt.enabled | boolean | true | Whether middle-click enables custom-amount input. |
withdraw_prompt.mode | enum | auto | Input method: auto uses the dialog when available and falls back to chat input otherwise; dialog forces the dialog and disables the interaction when unavailable; chat forces chat input. |
withdraw_prompt.dialog | section | see example | Dialog configuration; the key set is described in Dialog Configuration Block. The input with key: amount must be kept. |
deposit_filter.mode | enum | blacklist | Deposit filter mode: blacklist / whitelist / off. |
deposit_filter.entries | list | [] | List of CoreLib ItemSource tokens. |
deposit_filter.matcher | section | none | General item matcher selecting items by component, PDC, or lore. Combined with entries using OR: a hit on either counts as a hit, and mode then decides allow or block. See Item Matcher. |
allow_unique_items | boolean | true | Whether items carrying a per-instance marker may be deposited. |
multi_slot_stacking | boolean | false | Whether one item type spills into a new slot once its slot is full. When false, the remainder is refused. See Capacity. |
batch_max_ops | int | 200 | Maximum operations allowed in one applyBatchAsync, clamped to 1–2000. Exceeding it returns batch_too_large for the whole batch without executing anything, see API and Integration. |
default_sort | enum | amount_desc | Default sort: material_asc / material_desc / name_asc / name_desc / amount_asc / amount_desc. |
player_sort_enabled | boolean | true | Whether players may change the sort in the GUI. When false, default_sort is enforced. |
When deposit_filter.mode is whitelist and entries is empty, nothing can be deposited. The unique-marker test is structural: it checks whether the item's own PersistentDataContainer is non-empty rather than reading its display name or lore.
These keys decide the amount each click moves and whether it is allowed at all; the full click semantics table, the rejected click kinds and the search-mode restriction live in GUI and Display.
search
search:
enabled: true
operators:
name: "@"
lore: "#"
id: "$"
exclude: "!"
input:
mode: "auto"
timeout: 30
cancel_keywords: ["取消", "cancel"]
dialog:
title: "dialog.search.title"
can_close_with_escape: true
pause: false
body:
- "dialog.search.body"
inputs:
- type: "text"
key: "query"
label: "dialog.search.label"
max_length: 64
buttons:
- label: "dialog.confirm"| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether search is available. |
operators.name | string | @ | Matches the display name only. |
operators.lore | string | # | Matches lore only. |
operators.id | string | $ | Matches the material key or ItemSource id only. |
operators.exclude | string | ! | Exclusion: a match rejects the entry. |
input.mode | enum | auto | Input method; same values as behavior.withdraw_prompt.mode. |
input.timeout | int | 30 | Chat input timeout in seconds. |
input.cancel_keywords | list | [取消, cancel] | Keywords that cancel the chat input. |
input.dialog | section | see example | Dialog configuration. The input with key: query must be kept. |
Dialog Configuration Block
behavior.withdraw_prompt.dialog and search.input.dialog use exactly the same key set as the EmakiCoreLib dialog definition files (EmakiCoreLib/dialogs/*.yml), including type, external_title, after_action, columns, exit_button, plus per-input width, initial and label_visible. The only difference is that the id is supplied by the plugin and must not be written in the configuration.
The title, the text of body entries, input labels and button label / tooltip are resolved as language keys first: a key such as dialog.withdraw.title is read from lang/*.yml, and a missing key renders literally, so MiniMessage text can be written directly instead. The search dialog body supports the four operator placeholders %name%, %lore%, %id% and %exclude%.
Dialogs require client 1.21.6 or newer and EmakiCoreLib's dialog.enabled set to true. If inputs lacks the key the feature needs (amount for withdrawal, query for search) or carries no buttons, the dialog is treated as unavailable: mode: auto falls back to chat input, while mode: dialog disables the interaction and reports dialog.unavailable.
Operator prefixes are configurable so they can avoid clashing with item names. Search syntax:
- Whitespace separates terms and every term must match (AND).
- A term may carry one scope prefix and one exclusion prefix in either order, so
!@diamondand@!diamondare equivalent. - Without a scope prefix a term matches the combined text, which is built from display name plus lore plus identifier, so an unscoped search can also hit a material key.
- All matching is substring matching against text that was already lower-cased and stripped of formatting.
No regular-expression search is provided, not even behind an admin flag: a hostile pattern can stall the server through catastrophic backtracking, and substring matching covers the actual use case.
auto_pickup
auto_pickup:
enabled: true
mode: on_pickup
radius: 8
max_radius: 16
scan_interval_ticks: 10
default_enabled: false
notify_cooldown_ms: 3000| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch for auto-pickup. Once off the feature is entirely unavailable and the player command is refused. |
mode | enum | on_pickup | Working mode: on_pickup only collects when the vanilla EntityPickupItemEvent fires (no extra cost); radius additionally starts a scheduled scan that collects every dropped item within the configured radius (stronger, but it costs performance). |
radius | int | 8 | radius mode only. Scan radius in blocks, server-wide. Clamped between 1 and max_radius. Ignored in on_pickup mode. |
max_radius | int | 16 | radius mode only. Clamp ceiling for radius, minimum 1. |
scan_interval_ticks | int | 10 | radius mode only. Server-wide scan interval in ticks, minimum 1. 20 ticks = 1 second, so 10 scans twice per second. Lower reacts faster but costs more CPU. Ignored in on_pickup mode. |
default_enabled | boolean | false | Default per-player state when a player first joins. true means on by default and players turn it off with /estorage autopickup off; false means off by default and players turn it on with /estorage autopickup on. |
notify_cooldown_ms | long | 3000 | Notification cooldown after a successful pickup, in milliseconds. Further pickups inside the cooldown send no message, avoiding chat spam. 0 notifies every time. |
Players manage their personal switch with /estorage autopickup (requires emakistorage.autopickup, default true). There is no per-player radius: radius is a single server-wide value and can only be changed in the configuration.
mode: radius scans every online player every scan_interval_ticks: with 100 players online and scan_interval_ticks: 20 that is five all-player scans per second, so tune the interval to the server or use on_pickup mode.
persistence
persistence:
autosave_interval_seconds: 300
drain_timeout_seconds: 10| Field | Type | Default | Description |
|---|---|---|---|
autosave_interval_seconds | int | 300 | Autosave interval in seconds. 0 saves only when the GUI closes and on logout. |
drain_timeout_seconds | int | 10 | Seconds to wait for file writes to drain when the plugin disables. |
Multiple amount changes within one interval are merged into a single write. Every save is a full rewrite of storage.dat: write a sibling .tmp, re-read and validate the record count, then replace atomically. A failed write leaves the previous file untouched and deletes the tmp.
logging
logging:
enabled: true
retention_days: 30
sources: []| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether ordinary flow records are written. |
retention_days | int | 30 | Log retention in days. <= 0 disables purging. |
sources | list | [] | Record only these surfaces: gui / command / api / action. Empty means all. |
The log layout is one directory per player and one file per day: logs/<uuid>/<yyyy-MM-dd>.log. Expired files are deleted per day; once every file for a player has expired, their directory is removed as well.
The three admin operation kinds ADMIN_SET, ADMIN_GIVE and ADMIN_CLEAR are always recorded, ignoring both enabled and sources. An admin disabling logging to save disk is no reason to lose the audit trail of admin changes.
The flow log is write-only: business logic never reads these files, which removes the need for an index, compaction, schema versioning or cross-file transactions. A failed write only logs a warning and never fails the storage transaction that produced it. Records queued for the same player are flushed as one append, so a 36-slot bulk deposit produces one file operation rather than 36.
Related Pages
- GUI and Display: the
guianddisplaysections, thegui/storage_gui.ymltemplate and click semantics. - Capacity: the
capacitysection, the three-levelstackLimitand the fouroverflow_policyvalues. - Unlock Costs: the tiers, pricing and payment ordering of
unlock_costs.yml. - Commands and Permissions: the scope of
/estorage reloadand the permission tiers. - CoreLib Actions: changing slots and
stackLimitfrom an action chain. - API and Integration: how
StorageCapacityfields map onto configuration keys.