Subsystems

Everything hangs off the handle returned by Keystone.bootstrap(this). Config, messages and registries are created on demand; the scheduler, item adapter, service registry and GUI protection are available from bootstrap. GUI protection is eager because opening even one unprotected menu is an item-duplication risk.

This page is a map of the library. Use the focused references for complete setup, method choices and failure modes:

AreaUse it forDetailed guide
Managed config and textReloadable YAML, immutable settings and MiniMessageConfiguration and messages
CommandsRouting, permission gates and completionCommand framework
SchedulingBukkit/Folia entity, region, global and async workScheduling and Folia
GUIs and registriesInventory interfaces and addon-owned extension pointsGUIs and registries
StorageSQLite, MySQL and schema migrationsDatabase storage
MetricsOptional bStats, telemetry and custom sinksMetrics and telemetry

Messages

MessageService messages = keystone.messages();      // reads messages.yml
messages.send(player, "item-renamed", MessageService.value("name", playerTypedText));

Placeholders are resolvers, not string replacement, and that is the reason the class exists. Splicing a value into raw text and then parsing the result makes the value indistinguishable from markup the author wrote, so a player renaming an item to <click:run_command:/x>hi gets a working click event in the confirmation message. Adventure's resolver API substitutes during parsing, where a value's content can never be reinterpreted.

Config

ManagedConfig config = keystone.config("config.yml");
Snapshot<Settings> settings = new Snapshot<>(parse(config));

ManagedConfig merges the copy shipped in your jar with the one on disk, so a new key added in an update appears without an admin having to delete their file.

Snapshot<T> holds an immutable value behind a volatile reference: readers see the old value or the new one, never a half-applied reload. That is what makes config safe to read from any thread, and it is required on Folia.

LoadReport accumulates problems instead of throwing on the first, so an admin who made three mistakes learns about all three now rather than on three consecutive restarts.

Commands

new RootCommand(messages, "usage")
    .register(SimpleSubcommand.of("give", this::give)
        .permission("yourplugin.give")
        .usage("give <item> [player]")
        .completer((sender, args) -> RootCommand.matching(itemIds(), args.get(0, ""))))
    .bind(this, "yourcommand");

Permission and player-only checks are declared per subcommand and enforced once by the tree, rather than repeated at the top of each handler where one omission is a silent hole. Tab completion hides subcommands the sender cannot use.

Scheduling

KeystoneScheduler scheduler = keystone.scheduler();
scheduler.atEntity(player, () -> ...);            // the thread owning that player
scheduler.atLocation(loc, () -> block.setType(...));

One interface, a Bukkit backend and a Folia backend chosen by probe. Using it instead of BukkitRunnable is what makes a plugin Folia-capable. Off Folia the region-aware methods collapse onto the main thread, so the same code is correct on both.

Registries

OwnedRegistry<MyHandler> handlers = keystone.registry("handler");
handlers.register(otherPlugin, id, handler);

Entries know who owns them, ids must match the owner's namespace, and everything a plugin registered is dropped when that plugin disables. That is wired centrally, so no consumer has to remember the listener. This is what both HoloPanels' providers and Sigil's ability types are built on.

GUIs

GuiMenu and PaginatedMenu, on the plain Bukkit inventory API so they work down to 1.18.

Menus are identified by inventory holder rather than by title, and every click is cancelled before any slot lookup, which is what stops shift-click and hotbar swaps pulling icons out of slots that have no button.

Platform

keystone.platform().describe();                  // for bug reports
keystone.items().supportsItemModel();

PlatformSubcommand is a ready-made diagnostics subcommand you can mount in your own command tree. It is what /sigil platform is.

KeystoneHandle.shutdown() runs registered cleanup in reverse order, continues after one cleanup throws, and is idempotent. Add subsystem-owned resources with onShutdown(...) as soon as they are created so a partial startup still has one teardown path.