PluginUpdater-WB is a fully asynchronous plugin manager and updater for Paper-based Minecraft servers.
PluginUpdater-WB β Full Wiki π
Version:
26.1.2-1.0.2| API:26.1| Java: 17+ | Author: WebBeck
Table of Contents
- Overview
- Requirements & Compatibility
- Installation
- How It Works
- Configuration Reference
- Commands Reference
- Permissions
- Geyser Addon Management
- Update Lifecycle
- Backups
- Version Matching & Filters
- Internals & Architecture
- Building from Source
- Troubleshooting
- FAQ
1. Overview
PluginUpdater-WB is a fully asynchronous plugin manager and updater for Paper-based Minecraft servers. It tracks plugins across Modrinth, Hangar (PaperMC), GitHub Releases, SpigotMC, and custom direct-download URLs, then handles checking, downloading, backing up, and installing updates β all without blocking your main server thread.
On startup, it scans every loaded plugin and auto-populates config.yml with a stub entry for each one, ready for you to fill in source details. Once configured, a single command checks all plugins at once and another applies every pending update in parallel.
2. Requirements & Compatibility
| Requirement | Minimum |
|---|---|
| Server software | Paper (or fork: Purpur, Folia, etc.) |
| Paper API version | 26.1 |
| Java | 17 |
| Build tool (source) | Maven 3.x |
PluginUpdater-WB uses the Paper API exclusively and will not load on vanilla Spigot or CraftBukkit. It declares api-version: "26.1" in plugin.yml, so Paper will refuse to load it on older builds.
3. Installation
Standard Installation
- Download the latest
PluginUpdater-WB-*.jarfrom Modrinth or your distribution source. - Place the jar in your server's
plugins/directory. - Start or restart the server.
- Open
plugins/PluginUpdater-WB/config.yml. The plugin will have auto-populated an entry for every currently loaded plugin under theplugins:section. - For each plugin you want to track, set
enabled: trueand fill in thetypeand source-specific fields (see Source Types). - Run
/upd reloadto apply the updated config without a full restart.
First-Run Behaviour
On first enable, the plugin:
- Creates
config.ymlwith global defaults - Scans all loaded plugins via Bukkit's
PluginManager - Writes a disabled stub entry for each plugin it finds
- Registers the
/updatercommand (alias/upd) with tab completion - Initialises an
HttpClientwith redirect-following enabled - Registers a JVM shutdown hook to process any pending plugin deletions
4. How It Works
/upd check
β
βΌ
UpdateChecker.runUpdateCheck() β runs async via CompletableFuture
β
βββ for each enabled plugin in config:
β read type (MODRINTH / GITHUB / HANGAR / SPIGOT / CUSTOM)
β query the appropriate API
β compare returned version to current-version
β if newer β store UpdateInfo in pendingUpdates map
β
βββ report results to command sender
/upd run
β
βΌ
UpdateDownloader.applyUpdates() β runs async via CompletableFuture.allOf()
β
βββ for each UpdateInfo in pendingUpdates:
β back up existing jar to plugins/PluginUpdater-WB/backups/
β download new jar to plugins/ (streamed via HttpClient)
β update current-version in config.yml
β
βββ report completion to command sender
All network I/O and file operations run off the main thread. The main thread is only touched when sending chat messages back to the command sender.
5. Configuration Reference
Config file location: plugins/PluginUpdater-WB/config.yml
5.1 Global Settings
# Minecraft version sent to Modrinth when querying compatible versions.
# Leave blank or omit to auto-detect from Bukkit's reported version string.
minecraft-version: '26.1.2'
# Usernames that can run /upd commands without OP or the permission node.
allowed-players:
- 'YourUsernameHere'
# Loader type reported to Modrinth for compatibility filtering.
# "auto" detects from the running server software at runtime.
# Valid values: "auto", "paper", "purpur", "folia", "spigot", "bukkit"
server-type-override: 'paper'
# Default release channel applied globally to all plugins.
# Valid values: "release", "beta", "alpha", "all"
tracking-type: 'all'
| Key | Type | Default | Description |
|---|---|---|---|
minecraft-version |
string | auto-detected | MC version sent to Modrinth |
allowed-players |
list | [] |
Non-OP players with command access |
server-type-override |
string | paper |
Loader type for Modrinth queries |
tracking-type |
string | all |
Global release channel filter |
5.2 Geyser Addons
geyser-addons:
enabled: false # Set to true to enable Geyser management
Geyser: true # Track/update the Geyser plugin
Floodgate: true # Track/update Floodgate
MCXboxBroadcast: true # Track/update MCXboxBroadcast
When enabled: true, the /upd plugin geyser subcommand becomes active. See Geyser Addon Management.
5.3 Plugin Entries
All plugin entries live under the top-level plugins: key. The plugin name (the YAML key) should match the plugin's display name exactly for reliable matching against the loaded plugin list.
plugins:
PluginName:
enabled: true|false # Whether this plugin is tracked
type: MODRINTH|GITHUB|HANGAR|SPIGOT|CUSTOM
# ... source-specific fields (see below)
allowed-release-types: # Overrides global tracking-type for this plugin
- release
- beta
current-version: 1.0.0 # Last known installed version
The current-version field is read during update checks and written back automatically after a successful download. Do not set it to a version you haven't actually installed, or the next check will think you're already up to date.
5.4 Source Types
MODRINTH
Queries https://api.modrinth.com/v2/project/<project-id>/version filtered by minecraft-version and server-type-override.
MyPlugin:
enabled: true
type: MODRINTH
project-id: osLrA9oB # The slug or ID from the Modrinth URL
allowed-release-types:
- release
current-version: 1.0.0
GITHUB
Queries https://api.github.com/repos/<owner>/<repo>/releases and finds the latest matching release.
MyPlugin:
enabled: true
type: GITHUB
github-repo: EssentialsX/Essentials # owner/repository format
allowed-release-types:
- release
current-version: 2.20.1
HANGAR
Queries https://hangar.papermc.io/api/v1/projects/<project-id>/versions.
MyPlugin:
enabled: true
type: HANGAR
project-id: ViaVersion # Project slug from Hangar URL
allowed-release-types:
- release
current-version: 5.0.0
SPIGOT
Queries the Spiget API at https://api.spiget.org/v2/resources/<project-id>/versions/latest. The project ID is the numeric ID from the SpigotMC resource URL (e.g. spigotmc.org/resources/someplugin.19254/ β 19254).
MyPlugin:
enabled: true
type: SPIGOT
project-id: '19254' # Must be the numeric resource ID, quoted as string
allowed-release-types:
- release
current-version: 1.0.0
CUSTOM
Bypasses all version-checking logic. When /upd run (or /upd plugin <name>) is called, it downloads directly from the given URL regardless of version comparison.
MyPlugin:
enabled: true
type: CUSTOM
custom-url: 'https://example.com/downloads/MyPlugin-latest.jar'
current-version: 1.0.0
Use this for plugins hosted on private servers, CI artifact servers, or any source not covered by the other types.
6. Commands Reference
All commands require either the pluginupdater.admin permission, OP status, or the player's name in allowed-players. Commands can be run from the console or in-game.
Main command: /updater β alias: /upd
/upd check
/upd check [plugin]
Runs an asynchronous update check. Queries the configured source APIs for every enabled plugin (or just the one named) and reports how many updates are available. Results are cached in memory as UpdateInfo objects and used by /upd run.
Running /upd check does not download anything β it is read-only.
/upd run
/upd run [plugin]
Downloads and installs all pending updates found by the last /upd check. Each update runs in its own CompletableFuture, so multiple plugins download in parallel. CompletableFuture.allOf() is used to wait for all downloads to finish before reporting completion.
If no check has been run, or no updates were found, the command reports accordingly.
/upd list
/upd list [versions|pending]
Displays a list of tracked plugins. Submode options:
| Submode | Output |
|---|---|
| (none) | All enabled plugins |
versions |
All plugins with their currently installed versions |
pending |
Only plugins with pending updates (from the last check) |
/upd plugin
/upd plugin <pluginName> [check|update|...]
Operates on a single named plugin. Useful for checking or updating one plugin without running a full scan. The plugin name is matched case-insensitively against both the config keys and the loaded plugin list.
For Geyser addons specifically:
/upd plugin geyser
/upd plugin geyser download all
/upd plugin geyser update all
/upd reload
/upd reload
Reloads config.yml from disk. All in-memory config values (minecraft-version, allowed-players, server-type-override, tracking-type, and the plugins map) are refreshed. Pending update results from a previous check are cleared. No restart required.
/upd help & -v
/upd help β Prints a summary of all subcommands
/upd -v β Prints the running plugin version
7. Permissions
| Permission Node | Description | Default |
|---|---|---|
pluginupdater.admin |
Full access to all /upd subcommands |
OP |
There is no granular per-subcommand permission system. Either a player has pluginupdater.admin (or OP, or is in allowed-players) and can run everything, or they cannot run anything.
Granting Access to Non-OP Players
Option A β Permission plugin: Grant pluginupdater.admin through LuckPerms or similar.
Option B β Config allowlist: Add the player's username to allowed-players in config.yml and run /upd reload.
allowed-players:
- 'Steve'
- 'Alex'
8. Geyser Addon Management
PluginUpdater-WB has built-in support for the three main Geyser-ecosystem components: Geyser, Floodgate, and MCXboxBroadcast. These are managed separately from the normal plugins: config section because they are downloaded directly from the Geyser CI server rather than a standard repository.
Enabling
geyser-addons:
enabled: true
Geyser: true
Floodgate: true
MCXboxBroadcast: true
Set enabled: true and toggle each component on or off individually.
Commands
| Command | Action |
|---|---|
/upd plugin geyser |
Show status of all three addons, with clickable chat buttons |
/upd plugin geyser download all |
Download any addons whose jar is missing |
/upd plugin geyser update all |
Force-download the latest version of all enabled addons |
The status list shows [DOWNLOAD MISSING] in chat for any addon whose jar is not present in the plugins folder. Clicking the in-chat button runs the appropriate download command automatically.
9. Update Lifecycle
Understanding the full lifecycle helps when things don't behave as expected.
Server Start
βββ PluginUpdater.onEnable()
βββ Load config.yml
βββ Scan all loaded plugins β auto-populate config stubs
βββ Init HttpClient (redirect=ALWAYS)
βββ Register /updater command + tab completer
βββ Register event listener
βββ Process any pending scheduled deletions (from previous restart)
βββ Register JVM shutdown hook (for cleanup on stop)
/upd check
βββ UpdateChecker.runUpdateCheck() [async]
βββ For each enabled plugin in config:
β Query source API
β Parse version from response
β Run through PluginUpdaterUtils.cleanVersion() to strip loader tags
β Compare to current-version with PluginUpdaterUtils.versionsMatch()
β If mismatch β create UpdateInfo(pluginName, oldVer, newVer, url, fileName)
β Store in PluginUpdater.pendingUpdates (ConcurrentHashMap)
βββ Report count to sender
/upd run
βββ UpdateDownloader.applyUpdates() [async]
βββ For each UpdateInfo in pendingUpdates:
β Create CompletableFuture.runAsync():
β Back up existing jar β plugins/PluginUpdater-WB/backups/
β Stream download to plugins/<fileName>.download.tmp
β Rename tmp β final jar name (atomic swap)
β Update current-version in config.yml
βββ CompletableFuture.allOf(...).thenRun() β report completion
Server Stop (or /upd plugin <name> delete)
βββ Shutdown hook fires
βββ processPendingDeletions()
βββ Delete any jars scheduled for removal
10. Backups
Before every download, the existing plugin jar is copied to:
plugins/PluginUpdater-WB/backups/<PluginName>-<oldVersion>.jar
The backups directory is created automatically if it doesn't exist. Backups are not automatically pruned β manage disk space manually if you update plugins frequently.
To restore a backup, copy the desired jar from backups/ back to plugins/ and restart the server.
11. Version Matching & Filters
Version Cleaning (PluginUpdaterUtils.cleanVersion)
Before comparing versions, the plugin strips loader-specific suffixes to avoid false mismatches. It removes tags matching the pattern:
-(paper|spigot|bukkit|purpur|folia|release|beta|alpha)
It also strips leading v prefixes (e.g. v1.2.3 β 1.2.3) and handles build metadata appended after a +. This means 1.5.0-paper and 1.5.0 are treated as the same version.
Release Type Filters
Each plugin entry has an allowed-release-types list. Valid values: release, beta, alpha. Use all in the global tracking-type to allow any type, or specify per-plugin. The update checker skips versions whose release type doesn't appear in this list.
Global vs Per-Plugin Channel
tracking-type in the global config sets the default. A plugin's own allowed-release-types takes precedence when specified. This lets you track stable releases globally while opting specific plugins into beta channels.
12. Internals & Architecture
| Class | Role |
|---|---|
PluginUpdater |
Main plugin class (JavaPlugin). Wires all components together, holds the pendingUpdates map (ConcurrentHashMap), registers commands and shutdown hook. |
ConfigManager |
Reads and writes config.yml. Provides typed accessors for all global settings and plugin-specific config sections. |
UpdateChecker |
Performs async API queries using java.net.http.HttpClient. Builds the pendingUpdates map with UpdateInfo objects. |
UpdateDownloader |
Performs async file downloads and backup operations. Uses CompletableFuture.allOf() for parallel downloads. Writes the downloaded file to a .download.tmp and renames atomically. |
GeyserManager |
Handles Geyser-ecosystem downloads separately. Sends Adventure component messages with click events for in-chat buttons. |
CommandHandler |
Implements CommandExecutor and TabCompleter. Dispatches all /upd subcommands and delegates to the appropriate manager class. |
UpdateInfo |
Simple value object holding pluginName, oldVersion, newVersion, downloadUrl, fileName, and requiredDependencies. |
PluginUpdaterUtils |
Static utility methods: cleanVersion(), versionsMatch(), and joinArgs() for command argument parsing. |
Threading Model
- All HTTP requests run on threads from the JVM's common fork-join pool via
CompletableFuture.runAsync(). - File I/O (backup copy, streaming download) also runs async.
- Chat messages back to the command sender are dispatched from within the async callbacks β Paper's
CommandSender.sendMessageis thread-safe. - The
pendingUpdatesmap is aConcurrentHashMapto safely handle concurrent reads and writes.
13. Building from Source
git clone <repo-url>
cd PluginUpdater-WB
mvn clean package
The shaded/output jar lands in target/PluginUpdater-WB-<version>.jar.
Maven Dependency
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>26.1.2.build.65-stable</version>
<scope>provided</scope>
</dependency>
</dependencies>
Paper API is provided β it must be on the classpath at runtime (your server provides it) but is not bundled into the jar.
Project Structure
src/
βββ main/
βββ java/me/webbeck/pluginUpdater/
β βββ PluginUpdater.java β main class
β βββ CommandHandler.java
β βββ ConfigManager.java
β βββ GeyserManager.java
β βββ UpdateChecker.java
β βββ UpdateDownloader.java
β βββ UpdateInfo.java
β βββ PluginUpdaterUtils.java
βββ resources/
βββ plugin.yml
βββ config.yml
βββ common-plugins.yml
14. Troubleshooting
Plugin fails to load
- Confirm your server is running Paper (or a Paper fork) at API version
26.1or newer. - Confirm you are running Java 17+:
java -version.
No updates found even though I know there's a newer version
- Check that
enabled: trueis set for the plugin entry inconfig.yml. - Verify
current-versionmatches what's actually installed. If it's set higher than the installed version, the check may skip it. - For Modrinth: confirm
minecraft-versionandserver-type-overridematch what the plugin targets. A plugin may not list a version for your specific loader. - For GitHub: the release must have a
.jarasset attached. Releases with only source code zips are skipped. - Check
allowed-release-types. If you have- releaseonly, beta/alpha versions won't show. - Run
/upd checkand look for any error messages in console β API failures are logged.
Update downloaded but server still runs old version
Plugin jars can't be hot-swapped on a running JVM β the new jar is on disk, but the old class files are already loaded in memory. Restart the server to load the updated jar.
Backup file not found warning
This appears if /upd run is called for a plugin but no existing jar could be located in the plugins/ directory to back up. The download still proceeds. This can happen if the jar was manually deleted or has an unexpected filename.
Config not reloading
Make sure you run /upd reload after editing config.yml. Changes to plugin.yml (command definitions, permissions) require a full server restart since those are read by Paper on startup.
Geyser commands have no effect
Ensure geyser-addons.enabled is set to true in config.yml and that you've run /upd reload. The Geyser subcommand is a no-op when the section is disabled.
15. FAQ
Can I run /upd check on a schedule automatically?
Not natively β there is no built-in scheduled task. You could use an external automation plugin that runs console commands on a timer, or open a feature request on the project repository.
Will it update itself?
Yes. PluginUpdater-WB includes itself in config.yml as a Modrinth-tracked plugin (project-id: osLrA9oB). It follows the same update path as any other plugin.
Is it safe to use on a live production server?
Yes β all operations are async and non-blocking. However, actually applying updates (/upd run) replaces jar files on disk; the new code won't take effect until the next restart. There is no risk of mid-session class replacement.
Can I add plugins that aren't on any of the supported platforms?
Yes β use type: CUSTOM with a direct download URL. The plugin will download from that URL whenever /upd run is called, bypassing any version comparison.
What happens if a download fails partway through?
The downloader writes to a .download.tmp file first. If the download fails or the process is killed mid-stream, the tmp file is left behind but the original jar is untouched (the backup was already made). On the next run, the tmp file will be overwritten.
Does it support required dependencies?
The UpdateInfo model tracks a requiredDependencies list, which is surfaced in the update output. Dependency downloading is reported but not automatically resolved β you would need to manually add the dependency as its own tracked plugin entry.
Why is the version string 26.1.2-1.0.2?
The format is <minecraft-version>-<plugin-version>. The first segment (26.1.2) reflects the Paper/Minecraft version this build targets, and the second segment (1.0.2) is the plugin's own semver version.
For additional help, open an issue on the project repository or visit the GitHub page.