> ## Documentation Index
> Fetch the complete documentation index at: https://andcze.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Minecraft plugin

> Installing and safely configuring the plugin that fulfils orders on a game server.

The plugin makes an outbound TLS connection to ItemShop and receives paid orders in real time. You do not open a port on the game server. If it was offline, outstanding deliveries are replayed after reconnecting.

## Requirements

* Java 17 or newer
* Paper/Spigot 1.19+, Folia, BungeeCord, or Velocity 3.x
* a server created in the ItemShop panel
* write access to `plugins/ItemShop/` for configuration and the fulfilment journal

## Install from the panel

<Steps>
  <Step title="Download the correct JAR">
    Open **Plugin** in the shop panel and download the universal `ItemShop-1.0.0.jar`. Put it in one platform's `plugins/` directory; do not install both a platform-specific JAR and the universal JAR.
  </Step>

  <Step title="Generate configuration">
    Select a server, click **Generate plugin key**, then download `config.yml`. The panel creates a shop-bound `plugin` key with the minimum `orders:write` capability.
  </Step>

  <Step title="Start the plugin">
    Put the file at `plugins/ItemShop/config.yml` and start the server. `/itemshop` shows connection status and `/itemshop reload` safely reloads configuration.
  </Step>
</Steps>

<Warning>
  `apiKey` is a secret shown once. Do not publish or commit `config.yml`, and do not paste it into support tickets. Do not delete `fulfillment-journal.json`: without it an interrupted delivery cannot be reconciled safely.
</Warning>

## Configuration format

The panel generator emits every field used by the plugin:

```yaml config.yml theme={null}
apiUrl: "https://itemshop.dev"
apiKey: "isk_<64 hex characters>"
serverId: "<24 hex characters>"

joinDeliveryDelaySeconds: 2
blockedCommands:
  - op
  - deop
  - stop
  - restart
  - reload
  - whitelist
blockedServers:
  - auth
  - login

playersUpdateIntervalSeconds: 30
sendPlayerNames: true
reconnectMinMs: 2000
reconnectMaxMs: 30000
outboxRetrySeconds: 15
debug: false
```

| Field                               | Meaning                                                                      |
| ----------------------------------- | ---------------------------------------------------------------------------- |
| `apiUrl`                            | Public app or API origin, with **no** `/api/v2`, path, credentials, or query |
| `apiKey`                            | A `plugin` key in the format `isk_` plus 64 hex characters                   |
| `serverId`                          | Exactly one panel server ID; the key and server must belong to the same shop |
| `joinDeliveryDelaySeconds`          | Delivery delay after the player fully joins                                  |
| `blockedCommands`                   | First command words the plugin will never execute                            |
| `blockedServers`                    | Proxy modes where delivery waits, such as `auth` or `login`                  |
| `playersUpdateIntervalSeconds`      | Player-count and optional nickname-list update interval                      |
| `sendPlayerNames`                   | Disabling nicknames does not disable the aggregate player count              |
| `reconnectMinMs` / `reconnectMaxMs` | Bounds for exponential connection retries                                    |
| `outboxRetrySeconds`                | Retry interval for the acknowledgement only, after commands ran              |
| `debug`                             | Verbose diagnostics; enable temporarily                                      |

Production `apiUrl` must use `https://`. A separate API domain works only if the same origin serves the Socket.IO `/plugin` namespace.

## Fulfilment safety

<Steps>
  <Step title="Push and atomic claim">
    Every connected instance may see `order:deliver`, but exactly one wins the atomic `order:claim`. The others do not execute commands.
  </Step>

  <Step title="Durable journal before commands">
    Before the first command, the plugin writes and fsyncs `EXECUTING`. After the last command it writes `AWAITING_ACK`, while commands themselves run on the platform's required thread.
  </Step>

  <Step title="Acknowledge or review">
    After a restart, `AWAITING_ACK` can be acknowledged without running commands again. A recovered `EXECUTING` state is ambiguous, so automatic replay is blocked and the owner must choose **complete** or **retry** after checking the game server.
  </Step>
</Steps>

Arbitrary console commands cannot provide strict exactly-once semantics. In the ambiguous crash window, the system chooses safe at-most-once behaviour and asks for an explicit decision instead of risking a duplicate grant.

## Test before selling

1. Create a product with a harmless test command.
2. Place a 100%-discount order so no payment provider is involved.
3. Check `/itemshop`, server logs, and the `paid` → `processing` → `completed` transition.
4. Repeat with the player offline, after the player joins, and after restarting the plugin before delivery.
5. Separately run a real provider sandbox test: checkout → signed webhook → delivery → ACK.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The plugin reports incomplete configuration">
    Check the exact camelCase names: `apiUrl`, `apiKey`, and `serverId`. The key starts with `isk_`, the server ID is 24 hex characters, and `apiUrl` must not end in `/api/v2`.
  </Accordion>

  <Accordion title="The connection is rejected as unauthorized">
    Generate a new key from **Plugin**. It must have type `plugin`, capability `orders:write`, be active, and be bound to the shop that owns `serverId`.
  </Accordion>

  <Accordion title="An order is waiting for the player">
    Check nickname spelling, the online-player requirement, and `blockedServers`. Joining or switching modes pulls outstanding deliveries after `joinDeliveryDelaySeconds`.
  </Accordion>

  <Accordion title="An order needs manual review">
    The plugin recovered `EXECUTING` after a crash, so at least one command may have run. Check the player's/server's state first, then choose **complete** or **retry** in the panel.
  </Accordion>

  <Accordion title="A configuration change has no effect">
    Use `/itemshop reload` or a full restart. Do not use the platform-wide Minecraft `/reload`, which can leave libraries and listeners in an inconsistent state.
  </Accordion>
</AccordionGroup>

See the [plugin fulfilment protocol](/en/guides/fulfillment-plugin) for event and state details.
