Astral Realms Documentation Help

Custom Functions

You can register new function types that become available to every menu, dialog, item name/lore, action argument, and requirement value via the inline $name(...) syntax. See Functions for the YAML-side reference.

Step 1 — Implement PaperFunction

Functions are records implementing PaperFunction. The record components are the call's arguments, wrapped in PlaceholderWrapper<T> so each is resolved lazily inside the call's placeholder context.

import com.astralrealms.core.paper.model.function.PaperFunction; import com.astralrealms.core.paper.model.function.PaperFunctionContext; import com.astralrealms.core.placeholder.wrapper.PlaceholderWrapper; import com.astralrealms.core.platform.executable.exception.ExecutableRunException; import org.jetbrains.annotations.Nullable; public record MultiplyFunction( PlaceholderWrapper<Number> a, PlaceholderWrapper<Number> b, @Nullable PlaceholderWrapper<Number> cap ) implements PaperFunction { @Override public Object run(PaperFunctionContext context) throws ExecutableRunException { double result = context.parseWrapper(a).doubleValue() * context.parseWrapper(b).doubleValue(); if (cap != null) result = Math.min(result, context.parseWrapper(cap).doubleValue()); return result; } }

Argument resolution

Component type

Behaviour

PlaceholderWrapper<Integer>

Argument resolved and parsed as int.

PlaceholderWrapper<Number>

Resolved as a generic Number (int, long, double…).

PlaceholderWrapper<String>

Resolved as a string.

PlaceholderWrapper<Boolean>

"true"/"false".

@Nullable PlaceholderWrapper<T>

Optional — missing/blank arguments become null.

Always resolve the wrapper through the context (context.parseWrapper(wrapper)) — never call wrapper.get() directly, since only the context knows the player and placeholder parser for the running invocation.

Return value

In most contexts the returned Object is converted to a string via String.valueOf(result) and substituted into the surrounding placeholder string. When a standalone $fn(...) call is the entire value of a text/component field (menu and item names/lore, titles, dialog labels — anywhere ComponentWrapper runs), the raw return value is integrated type-aware instead: an Adventure ComponentLike is spliced in directly, a StyleBuilderApplicable is applied as styling, a single-element List is unwrapped, and a nested PlaceholderWrapper is resolved one level; anything else falls back to String.valueOf. This lets a custom function return rich text rather than its toString(). Returning null still produces the literal "null", so prefer throwing ExecutableRunException for error cases.

Step 2 — Register the Function

Call this during your plugin's onEnable:

AstralPaperAPI.registerFunction("multiply", MultiplyFunction.class);

Use registerFunctionGlobally to expose the function to every plugin on the server (used by built-ins like add, round, min). Use the per-plugin registerFunction when the function only makes sense inside your own menus.

The first argument is the function name used in YAML. It may match [a-zA-Z0-9_-]+ — letters, digits, underscores, and hyphens — so dash-named functions (e.g. the built-ins format-number, format-date, and random-int) are callable inline via $name(...).

Step 3 — Use in YAML

items: total: item-stack: material: GOLD_INGOT name: "<gold>Total: $multiply(%variables_qty%, %parameters_price%)" lore: - "<gray>Capped: $multiply(%variables_qty%, %parameters_price%, 64000)"

The arguments %variables_qty%, %parameters_price%, and the literal 64000 are wrapped into PlaceholderWrapper<Number> components, resolved against the menu's placeholder context, and passed to MultiplyFunction.run.

Nullable Arguments

Mark optional components with @Nullable. The wrapper itself becomes null when the caller omits the argument, so the check is on the wrapper, not on the resolved value:

@Nullable PlaceholderWrapper<String> mode
String mode = this.mode == null ? "default" : context.parseWrapper(this.mode);

Error Handling

Throw ExecutableRunException for any user-visible problem (bad numeric input, invalid pattern, missing required state). The caller sees the function call replaced with UNPARSED_FUNCTION and an InvalidWrapException is logged with the failing call's literal text — making it easy to trace which YAML produced the failure.

if (number == null) throw new ExecutableRunException("multiply: first argument cannot be null");

Built-in Examples

The Paper module ships these implementations as a reference — read them when in doubt about idiomatic function design:

  • AddFunction, SubtractFunction — required + required + optional cap/floor.

  • IncrementFunction, DecrementFunction — single required value + optional bound, preserves number type.

  • MinFunction, MaxFunction — null-tolerant binary numeric ops.

  • RoundFunction — required value + optional decimal precision.

  • FormatNumberFunction — required value + optional DecimalFormat pattern (default 0.00).

  • FormatDateFunction — required epoch millis + optional mode keyword or DateTimeFormatter pattern.

All live under com.astralrealms.core.paper.model.function.impl and are registered globally in AstralCore.onEnable.

Last modified: 25 July 2026