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:
| Area | Use it for | Detailed guide |
|---|---|---|
| Managed config and text | Reloadable YAML, immutable settings and MiniMessage | Configuration and messages |
| Commands | Routing, permission gates and completion | Command framework |
| Scheduling | Bukkit/Folia entity, region, global and async work | Scheduling and Folia |
| GUIs and registries | Inventory interfaces and addon-owned extension points | GUIs and registries |
| Storage | SQLite, MySQL and schema migrations | Database storage |
| Metrics | Optional bStats, telemetry and custom sinks | Metrics 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.