Paper 1.21.5+ and 26.1–26.3#
Prefers Paper's native Player#openVirtualSign path.
NexaGUIAPI combines the established callback-based Sign API with reusable native Paper dialogs. The Sign API keeps its original package and backend selection; Dialog support is capability-checked and remains optional on older supported servers.
Backend behavior depends on the Paper API exposed by the server version.
Keep Paper provided, ProtocolLib optional and NexaGUIAPI relocated inside the final plugin jar. This example uses the checked local source version, 1.2.3; use the version supplied with your library release.
<dependency>
<groupId>net.ritomasss</groupId>
<artifactId>NexaGUIAPI</artifactId>
<version>1.2.3</version>
</dependency>
<relocation>
<pattern>net.ritomasss.nexasignapi</pattern>
<shadedPattern>net.ritomasss.yourplugin.libs.signapi</shadedPattern>
</relocation>Create one API instance in the plugin lifecycle and close it during disable.
private NexaSignApi signApi;
@Override
public void onEnable() {
signApi = NexaSignApi.create(this);
}
@Override
public void onDisable() {
if (signApi != null) signApi.close();
}signApi.open(player, SignInputOptions.builder()
.line(0, "Enter price")
.line(1, "")
.line(2, "-------------")
.line(3, "NexaAuctions")
.build(), result -> {
if (!result.submitted()) return;
String input = result.line(1);
});SignInputOptions describes the visual prompt and the temporary session environment.
line(index, text)Set the four displayed lines. Keep prompts short enough for the client sign UI.
color / glowConfigure dye color and glowing text where the selected backend supports it.
material / sideChoose the temporary sign material and active side for supported backends.
timeout / locationControl expiry and the fallback placement location.
The API reports support so a consumer can disable a feature cleanly instead of failing while opening a sign.
if (!signApi.isSupported()) {
getLogger().warning(signApi.supportReason());
return;
}Preferred on Paper 1.21.5+ and 26.1–26.3; no physical block is placed.
Reflection-based packet path for Paper 1.21.0–1.21.4, with ProtocolLib as a compatibility fallback.
Uses a nearby air block only when virtual backends are unavailable, then restores it for submit, cancel, timeout, quit or close.
Only one active sign session is allowed per player. Completion must be treated as a result state, not as an exception.
Opening and closing are dispatched through player/entity-scoped scheduling where the runtime supports Folia.
Parse the returned text in the callback, then schedule any inventory, economy or world operation with your own server-safe scheduler.
The library makes the sign session dispatch safe, but it cannot make arbitrary consumer code safe. Keep external plugin calls on the thread required by that API.
NexaGUIAPI is intentionally small so auction, shop, claim and admin plugins can own validation and business logic.
Most issues come from backend expectations or a missing cleanup call.
Confirm the server is a supported Paper branch. Do not assume Spigot or an unrecognized fork exposes Paper virtual-sign APIs.
On Paper 1.21.0–1.21.4, NexaGUIAPI first checks its reflective packet backend. ProtocolLib is the compatibility fallback if that backend is unavailable; otherwise a temporary physical sign is used.
Check that the consumer calls close() on disable and does not terminate the JVM/server thread during a callback.
Only one active session is supported. Disable or cancel the existing session before starting another.
Move inventory/economy/world operations to the correct player or server scheduler; the callback is not permission to call Bukkit APIs from any thread.
SignAPI returns text, not domain validation. Parse and validate in the consumer before applying the business action.
NexaGUIAPI is a library, so include both library and consumer context.
Build a vanilla form with a title, rich text or item body, checkbox, stable-id selector, text input, or number slider. Paper and the connecting client must support native dialogs.
DialogDefinition form = DialogDefinition.builder(Component.text("Crate » Preview Template"))
.body(Component.text("Select a preview GUI template."))
.input(DialogInputDefinition.checkbox("enabled", Component.text("Enabled"), true))
.input(DialogInputDefinition.singleOption("preview", Component.text("Preview"), options, "default"))
.action("apply", Component.text("OK"), DialogActionRole.SUBMIT)
.action("back", Component.text("Back"), DialogActionRole.CANCEL)
.build();
guiApi.dialogs().open(player, form, result -> {
if (result.status() == DialogResultStatus.SUBMITTED) {
boolean enabled = result.values().booleanValue("enabled");
String template = result.values().optionValue("preview");
}
});Checkbox, text, single-option selection, and bounded number-range inputs.
Session-scoped buttons return Submit or Cancel results. Back never applies form values.
Sessions expire and clear on replacement, disconnect, consumer disable, or service close.
Paper does not expose an immediate callback for Escape dismissal. An Escape close is cleaned up at timeout or disconnect; no values are applied unless the consumer handles a submitted result. Programmatic cancellation invalidates a session without closing another plugin's currently visible dialog.