Skip to main content

Nexal TPA

A comprehensive teleport request plugin for Minecraft servers with warmup, cooldown, cross-server support, and extensive customization options.

Features​

  1. TPA Requests - Players can request to teleport to others or request others teleport to them
  2. Player Selector GUI - Type /tpa with no arguments to open a visual player selector; left-click to send TPA, right-click to send TPAHERE, blocked players shown greyed out; in cross-server mode it lists the players on the other servers too
  3. Warmup System - Configurable delay before teleport executes, cancelled on movement or damage, with particle aura visualization and action bar countdown
  4. Cooldown System - Prevent spam by adding cooldown between requests
  5. Request Timeout - Requests automatically expire after configurable time
  6. Block System - Players can block specific players from sending requests
  7. Toggle System - Players can disable receiving all TPA requests
  8. Combat Protection - Prevent TPA during combat with configurable combat tag duration
  9. Request Management GUI - 5-row visual interface with pagination, deny all, and settings access
  10. Settings GUI - Visual interface to toggle TPA, view status, and manage blocked players
  11. Blocked Players GUI - Paginated interface to view and unblock players
  12. Clickable Chat Messages - Accept/deny buttons appear in chat when receiving a request
  13. Cross-Server Support - /tpa and /tpahere players on other servers of a BungeeCord or Velocity network; on accept the proxy moves the player and they arrive at the other player's location, with the fee refunded if the move fails. Off by default, see Cross-Server Support
  14. World Blacklist - Disable TPA in specific worlds, both for leaving them and for teleporting into them
  15. Cross-World Control - Optionally prevent TPA between different worlds
  16. Configurable Damage Cancel - Choose whether damage cancels teleport warmup
  17. Max Requests Limit - Limit pending incoming requests per player
  18. Comprehensive Messages - All messages, chat buttons, action bars and menu text customizable in messages.yml, with sounds and titles
  19. PlaceholderAPI Integration - Placeholders for pending requests, enabled status, and more
  20. TPA Fees - Optional Vault economy integration to charge per-teleport fees (configurable, disabled by default), waived for players with nexaltpa.bypass.fee
  21. Teleport History - Persistent per-player history of all TPA requests with paginated GUI viewer, recording how each teleport actually ended and the fee actually charged
  22. Dual Storage - JSON and SQL (MySQL) storage backends, selectable via config; several servers can share one SQL database
  23. Import/Export - Move player data between storage types or servers
  24. Admin Menu - /tpa admin opens a menu to change the main settings in-game and to reload, export and import

Technologies​

  • Paper API 1.20.1+ - Minecraft server API (Spigot compatible)
  • Utilizer v0.21.2 - Configuration, messages, items, tasks, GUIs
  • nexal-core - Storage (JSON and SQL), help menu, license check
  • ACF (Aikar Command Framework) - Annotation-based command system
  • PlaceholderAPI - Placeholder integration (optional)
  • Vault - Economy integration for TPA fees (optional)

Commands​

Player Commands​

CommandDescriptionPermission
/tpa [player]Open player selector or send a teleport requestnexaltpa.tpa
/tpahere \<player>Request another player to teleport to younexaltpa.tpahere
/tpaccept [player]Accept incoming TPA requestnexaltpa.accept
/tpdeny [player]Deny incoming TPA requestnexaltpa.deny
/tpacancelCancel your outgoing requestnexaltpa.cancel
/tpatoggleToggle receiving TPA requestsnexaltpa.toggle
/tpblock \<player>Block a player from sending requestsnexaltpa.block
/tpunblock \<player>Unblock a playernexaltpa.unblock
/tpalistView all pending requests in GUInexaltpa.list
/tpasettingsOpen TPA settings menunexaltpa.settings
/tphistoryView your teleport historynexaltpa.history

All commands also work as subcommands of /tpa (e.g., /tpa accept, /tpa settings, /tpa history).

Admin Commands​

CommandDescriptionPermission
/tpa adminOpen the admin menu (shows the admin help in the console)nexaltpa.admin
/tpa admin helpShow the admin commandsnexaltpa.admin
/tpa admin reloadReload config.yml and messages.ymlnexaltpa.admin.reload
/tpa admin exportWrite every player profile to export/tpa_users.jsonnexaltpa.admin.export
/tpa admin importLoad export/tpa_users.json into the current storagenexaltpa.admin.import

Permissions​

PermissionDescriptionDefault
nexaltpa.tpaSend TPA requeststrue
nexaltpa.tpahereSend TPAHERE requeststrue
nexaltpa.acceptAccept requeststrue
nexaltpa.denyDeny requeststrue
nexaltpa.cancelCancel outgoing requeststrue
nexaltpa.toggleToggle TPA on/offtrue
nexaltpa.blockBlock playerstrue
nexaltpa.unblockUnblock playerstrue
nexaltpa.listView request GUItrue
nexaltpa.settingsOpen settings GUItrue
nexaltpa.historyView teleport historytrue
nexaltpa.bypass.cooldownBypass request cooldownop
nexaltpa.bypass.feeTeleport without paying the economy fee (e.g. for donor ranks)false
nexaltpa.adminAdmin menu and all admin commands (includes the three below)op
nexaltpa.admin.reloadReload configop
nexaltpa.admin.exportExport dataop
nexaltpa.admin.importImport dataop

Placeholders​

Requires PlaceholderAPI.

PlaceholderDescription
%nexaltpa_pending_requests%Number of pending incoming requests
%nexaltpa_tpa_enabled%Yes/No if TPA is enabled for player
%nexaltpa_tpa_enabled_status%Colored enabled/disabled status
%nexaltpa_blocked_count%Number of blocked players
%nexaltpa_has_outgoing%Yes/No if player has outgoing request
%nexaltpa_history_count%Number of history entries

Configuration​

config.yml​

# Request timeout in seconds
request-timeout: 60

# Warmup delay in seconds before teleport (0 = instant)
warmup-delay: 3

# Cooldown between sending requests in seconds
cooldown: 30

# Allow TPA while in combat
allow-in-combat: false

# Combat tag duration in seconds
combat-tag-duration: 10

# Particle aura during teleport warmup
warmup-particles:
enabled: true
# e.g. PORTAL, END_ROD, FLAME, ENCHANT, DUST.
# The names from before 1.20.5 (ENCHANTMENT_TABLE, REDSTONE) work too.
type: PORTAL
count: 15
radius: 0.8
update-interval: 2

# Worlds where TPA is disabled
disabled-worlds: []

# Maximum pending incoming requests per player (0 = unlimited)
max-requests-per-player: 10

# Whether cross-world TPA is allowed
allow-cross-world: true

# Whether taking damage cancels teleport warmup
warmup-cancel-on-damage: true

# Clickable accept/deny buttons in chat when receiving a request
clickable-messages:
enabled: true

# Cross-server TPA on a BungeeCord or Velocity network (README: Cross-Server Support).
# Install the plugin on every backend server and give them all the same settings below.
# Velocity needs bungee-plugin-message-channel = true in velocity.toml (the default).
# Messages travel over a player's connection: a server with nobody online can neither send
# nor receive requests.
cross-server:
# Turn on cross-server TPA. Leave off on a single server.
enabled: false
# Shared secret that signs every message between the servers, so nobody can forge one.
# Use the same long random value (at least 16 characters) on every server, and keep it private.
# Cross-server TPA refuses to start while this is empty or still this default.
secret: "change-this-to-a-long-random-secret"
# Seconds a message stays valid. Older ones are refused, so a recorded message cannot be
# sent again later. Raise it only if the servers' clocks differ by more than this.
max-message-age: 30

# Economy settings (requires Vault)
economy:
# Enable TPA fees
enabled: false
# Cost per TPA request (charged to the sender on teleport)
tpa-fee: 100.0
# How fees are shown in messages, menus and history. %amount% is the number
price-format: "$%amount%"
# The number, as a Java DecimalFormat pattern: , groups thousands, .00 gives two decimals
number-format: "#,##0.00"
# Leave out the decimals of whole amounts: $1,000 rather than $1,000.00 (but $1,000.50)
hide-zero-decimals: true

# History settings
history:
# Maximum history entries stored per player
max-entries-per-player: 50

# Storage settings
storage:
# Storage type: json or sql. On a network, point every server at the same SQL database
# to share the TPA toggle, block list and history (JSON keeps them per server).
type: json

# SQL settings (used when type is "sql")
sql:
host: localhost
port: 3306
database: minecraft
username: root
password: ""

messages.yml​

All messages are fully customizable with:

  • MiniMessage colors and gradients (<gradient:#00D2D3:#54A0FF>, \<white>); legacy & codes also work
  • Sounds (Minecraft sound names)
  • Titles and subtitles with timing, and action bars (actionbar:)
  • Placeholders (listed in a comment above each message)

Messages, and menu and chat button text (gui, request-buttons), added in an update are used with their default text until you add them to your file. Fee amounts (%amount%, %balance%) already include the currency from economy.price-format, so a $ written before them in an older file is left out.

Key message keys:

  • request-sent - Request sent successfully
  • request-received - Incoming request notification
  • request-accepted - Request accepted messages
  • request-denied - Request denied messages
  • request-expired - Request timeout messages
  • warmup-started - Teleport warmup countdown
  • warmup-cancelled - Warmup cancelled (moved/damaged)
  • teleport-complete - Successful teleport
  • cooldown-active - Cooldown remaining
  • combat-tagged - Cannot TPA in combat
  • target-max-requests - Target has too many pending requests
  • cross-world-disabled - Cross-world TPA not allowed
  • request-received-cross-server, request-received-here-cross-server, request-accepted-cross-server - Requests between servers
  • cross-server-connecting, cross-server-no-response, cross-server-not-ready, cross-server-destination-offline, cross-server-move-failed - Moving between servers, and when the other server does not answer or the move fails
  • fee-refunded - The fee of a cross-server teleport that did not happen was given back
  • world-disabled, world-disabled-target - A blacklisted world is involved
  • request-buttons - The clickable [✔ Accept] / [✘ Deny] line under a request, and its hover texts
  • warmup-countdown, teleport-complete-actionbar - Action bars during and after the warmup
  • gui - Titles, item names and lore of every player menu, the accent glass colours (gui.accent) and the button click sound (gui.click-sound)
  • And many more...

How It Works​

Basic TPA Flow​

  1. Request: Player A uses /tpa PlayerB
  2. Notification: Player B receives a notification with clickable [✔ Accept]/[✘ Deny] buttons
  3. Accept: Player B clicks [✔ Accept] or uses /tpaccept
  4. Warmup: Player A enters warmup period (configurable, default 3 seconds)
  5. Teleport: After warmup, Player A teleports to Player B's location

TPAHERE Flow​

  1. Request: Player A uses /tpahere PlayerB
  2. Accept: Player B accepts
  3. Result: Player B teleports to Player A (reversed direction)

Request Management​

  • Timeout: Requests expire after 60 seconds (configurable)
  • Cooldown: Players must wait 30 seconds between requests (configurable)
  • Max Requests: Limit pending incoming requests per player (default 10)
  • Block List: Blocked players cannot send you requests
  • Toggle: Completely disable receiving requests with /tpatoggle

Warmup System​

  • Configurable Delay: Set warmup time in config.yml (0 = instant)
  • Movement Cancel: Moving cancels the teleport
  • Damage Cancel: Taking damage cancels the teleport (configurable with warmup-cancel-on-damage)
  • Visual Feedback: Countdown messages and warnings
  • Action Bar Countdown: Displays "Teleporting in Xs..." on the action bar each second during warmup
  • Particle Aura: Spiraling particle ring around the player during warmup with a burst effect on completion (configurable type, radius, and count)

Every player menu has cyan glass on its corners (the rest is left empty), your own head at the top with an overview, and a click sound on its buttons; the buttons that do something glow. Item lore follows ● Label » Value, with ▶ Left Click hints and ✔/✘ for states.

Request GUI​

Access with /tpalist to see all pending requests in a 5-row visual interface:

  • Player Heads: Shows requester's head with their name and world
  • Request Type: Shows TPA or TPAHERE with color coding
  • Time Remaining: Live countdown to expiration
  • Click to Accept: Left-click any request to accept (the same checks as /tpaccept apply)
  • Click to Deny: Right-click any request to deny
  • Pagination: Navigate through requests with arrow buttons
  • Deny All: Deny all requests on the current page at once
  • Settings: Quick access to TPA settings from the request GUI
  • Auto-Refresh: Updates every second

Settings GUI​

Access with /tpasettings to manage your TPA preferences:

  • Toggle TPA: Enable or disable receiving TPA requests (green when on, red when off)
  • Blocked Players: View and manage your blocked players list
  • Status: Your head at the top shows your TPA status, pending requests and blocked count

Blocked Players GUI​

Accessed from the Settings GUI to manage blocked players:

  • Player Heads: Shows blocked players with their names
  • Click to Unblock: Click any head to unblock that player
  • Pagination: Navigate through blocked players with arrow buttons

Combat Protection​

  • Combat Tag: Taking damage tags you in combat
  • Duration: Configurable combat tag duration (default 10 seconds)
  • TPA Block: Cannot send TPA requests while combat tagged, from a command or the player selector
  • On Accept: The player who would teleport must not be combat tagged when the request is accepted; the request stays pending until they can
  • Optional: Can be disabled with allow-in-combat: true

Teleport History​

/tphistory lists each request with its type, direction, result, fee (or None) and how long ago it was:

ResultMeaning
✔ AcceptedAccepted and teleported
Accepted, teleport cancelledAccepted, but the warmup was cancelled (movement, damage, leaving)
Accepted, fee not paidAccepted, but the sender could not pay the fee
Accepted, could not move serversCross-server only: the move to the other server failed, and the fee was refunded
Denied, Expired, CancelledThe request was denied, timed out or withdrawn

Economy Fees​

With economy.enabled: true and Vault installed, the request sender pays economy.tpa-fee when the teleport happens, whoever teleports. If they cannot pay, nobody is teleported and both players are told. Players with nexaltpa.bypass.fee never pay.

Fees are shown the same way everywhere (messages, menus, history) using economy.price-format, economy.number-format and economy.hide-zero-decimals: by default 100 reads $100, 1000 reads $1,000 and 1000.5 reads $1,000.50.

Admin Menu​

/tpa admin (permission nexaltpa.admin) opens:

  • Configuration: toggles (combat, damage cancel, particles, cross-world, clickable buttons, economy), numbers (warmup, cooldown, timeout, combat tag, max requests, fee) and the warmup particle. Changes are saved to config.yml straight away.
  • Reload, Export Data and Import Data (shift-click): the same as the admin commands.

Cross-Server Support​

Off by default; with it off, nothing changes on a single server. With it on, /tpa and /tpahere reach players on the other servers of a BungeeCord or Velocity network. It uses the proxy's BungeeCord plugin messaging channel, so no proxy plugin is needed.

Setup

  1. Install NexalTPA on every backend server (not on the proxy).
  2. Set up the network as usual: on BungeeCord, settings.bungeecord: true in each backend's spigot.yml; on Velocity, keep bungee-plugin-message-channel = true in velocity.toml (the default).
  3. Make a long random secret, for example with openssl rand -hex 32. On every backend server set cross-server.secret to that same value and cross-server.enabled: true.
  4. Restart the servers (or /tpa admin reload). The console says Cross-server TPA enabled, and once a player is online, this server is "\<name>" on the proxy.
  5. Keep the servers' clocks in sync (NTP). Messages older than cross-server.max-message-age seconds (default 30) are refused.

How it works

  • /tpa \<name>, /tpahere \<name>, their tab completion and the player selector (/tpa with no name, with the player's server in the lore) include players on the other servers (the list is refreshed every 5 seconds). A player on this server with exactly that name comes first.
  • The sender's server checks the sender (combat, world, pending request, cooldown, their block list); the target's server checks the target (TPA toggle, block list, request limit, world) and shows the request with its [✔ Accept] / [✘ Deny] buttons. Accept, deny, cancel, the requests menu and timeouts work as on one server.
  • On accept, the player who teleports warms up on their own server (the same countdown, particles and movement or damage cancel), and the request sender pays the fee. Then the proxy moves them to the other server, where a pending-teleport record puts them at the other player's location. For a TPA, right before the move the sender's server asks the proxy whether the target is still on their server; if they left during the warmup, the sender is told and stays, without paying.
  • If the move fails, the fee is refunded, once. A move fails when the player never leaves their own server in the 15 seconds after the switch began (the other server is down or refused them; the proxies only switch a player once the other server let them in), when they never arrive within 30 seconds, or when the other player left before they arrived (they are told and stay at that server's spawn). The last two are reported by the destination. A player who left their server is never treated as failed, even if they come straight back; and a fee is never refunded after a successful arrival, or because a report got lost.
  • The servers' reports to each other (a player on their way, how a teleport ended, a player's data changed) wait until the other server has someone online, and are sent again every 5 seconds until it confirms them, for up to 10 minutes. Each sending is signed afresh and is acted on only once.
  • Both players' teleport history says how it ended: accepted, teleport cancelled (warmup), fee not paid, or could not move servers. The two servers tell each other.
  • disabled-worlds applies on each server; allow-cross-world does not apply between servers.

Player data across the network: the TPA toggle, block list, cooldown and history follow a player from server to server only when every server uses the same SQL database (storage.type: sql). Each change writes just that field in one short locked transaction, so servers sharing the database never overwrite each other; the server that made the change then tells the others to read that player again, and a server also reads a player again when they join. With JSON storage each server keeps its own data.json, so these settings stay per server.

Security: every message between the servers is signed with HMAC-SHA256 using the shared secret, and carries a timestamp, a random nonce and the name of the server it is for. A forged, changed, replayed, too old or misaddressed message is ignored, with a console warning (at most once a minute). Cross-server mode refuses to start while the secret is empty, shorter than 16 characters or still the default. A message about a request is only believed from the server that request is with, so one server cannot report another's teleport as failed or announce a player.

What the proxy itself answers (this server's name, who is online on which server) is not signed: it is trusted because only the proxy can send it. Keep the backends reachable through the proxy only (modern forwarding on Velocity, a firewall or bungeecord: true with closed ports on BungeeCord), or a player connecting straight to a backend could pose as the proxy.

Limitations

  • Plugin messages travel over a player's connection. A server with nobody online can neither send nor receive them. A server also learns its own name from the proxy only once the first player is online, so cross-server requests work a second or two after that. An answer to a server that has emptied in the meantime is lost, and the request simply times out.
  • Reports waiting for a server that stays empty for more than 10 minutes are dropped, and so are the waiting ones of a server that restarts. A dropped failure report means the fee stays charged (never a refund that should not happen); a dropped "player changed" notice is covered by the reload when that player joins.
  • A target who leaves in the split second between that last check and the move, as the only player on their server, leaves nobody to receive the sender's arrival notice: the sender then arrives at that server's spawn and the fee stays charged.

Data Storage​

NexalTPA supports dual storage backends, selectable via storage.type in config.yml.

JSON Storage (Default)​

Data is stored in plugins/NexalTPA/data.json:

&#123;
"tpa_users": &#123;
"player-uuid": &#123;
"id": "player-uuid",
"blocked_players": "[\"uuid1\",\"uuid2\"]",
"tpa_enabled": true,
"last_request_time": 1234567890,
"history": "[]"
&#125;
&#125;
&#125;

Changes are saved as they happen, off the main thread; a profile is only stored once something in it changes.

SQL Storage (MySQL)​

Set storage.type: sql in config.yml and configure the connection details under storage.sql. If the connection fails at startup, the plugin logs the error and disables itself.

Several servers may share one database (for example the backends of a network): every change writes only the field it changes, under a row lock, so no server overwrites another's changes. See Player data across the network.

Database Schema​

Table tpa_users (the same fields are the keys of each JSON entry):

ColumnTypeDescription
idVARCHAR(36)Player UUID (primary key)
blocked_playersTEXTJSON array of blocked player UUIDs
tpa_enabledBOOLEANWhether the player receives requests
last_request_timeBIGINTWhen the player last sent a request (for the cooldown)
historyMEDIUMTEXTJSON array of history entries

Import/Export​

/tpa admin export  - Write every player profile to plugins/NexalTPA/export/tpa_users.json
/tpa admin import - Load that file into the current storage (replaces profiles of the same players)

Switching from JSON to SQL: /tpa admin export, set storage.type: sql, restart, then /tpa admin import. The file can also be copied to another server's export folder to move player data.

World Blacklist​

Disable TPA in specific worlds:

disabled-worlds:
- world_nether
- arena_world
- event_world

A request is refused, when it is sent and again when it is accepted, if either player is in one of these worlds: nobody can teleport out of them or into them.

Installation​

  1. Download the latest NexalTPA.jar
  2. Place in /plugins folder
  3. Start/restart server
  4. Configure config.yml and messages.yml
  5. Reload with /tpa admin reload

Dependencies​

Required:

  • Paper 1.20.1+ (or compatible fork)

Optional:

  • PlaceholderAPI (for placeholders)
  • Vault (for TPA fees / economy integration)
  • BungeeCord or Velocity (for cross-server TPA; no proxy plugin needed)

Building from Source​

# From repository root
mvn clean package

# Or using npm scripts
npm run build
# Select "nexal-tpa" from menu

# Output: plugins/nexal-tpa/target/NexalTPA-\<version>.jar

Support​

For issues, feature requests, or questions:

  • Check the configuration files
  • Review messages for customization options
  • Ensure all dependencies are met
  • Verify permissions are set correctly