Welcome to the Hangar Open Beta. Please report any issue you encounter on GitHub!
Avatar for webbeck

PluginUpdater-WB is a fully asynchronous plugin manager and updater for Paper-based Minecraft servers.

Report PluginUpdater-WB?

PluginUpdater-WB β€” Full Wiki πŸ“š

Version: 26.1.2-1.0.2 | API: 26.1 | Java: 17+ | Author: WebBeck


Table of Contents

  1. Overview
  2. Requirements & Compatibility
  3. Installation
  4. How It Works
  5. Configuration Reference
  6. Commands Reference
  7. Permissions
  8. Geyser Addon Management
  9. Update Lifecycle
  10. Backups
  11. Version Matching & Filters
  12. Internals & Architecture
  13. Building from Source
  14. Troubleshooting
  15. 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

  1. Download the latest PluginUpdater-WB-*.jar from Modrinth or your distribution source.
  2. Place the jar in your server's plugins/ directory.
  3. Start or restart the server.
  4. Open plugins/PluginUpdater-WB/config.yml. The plugin will have auto-populated an entry for every currently loaded plugin under the plugins: section.
  5. For each plugin you want to track, set enabled: true and fill in the type and source-specific fields (see Source Types).
  6. Run /upd reload to apply the updated config without a full restart.

First-Run Behaviour

On first enable, the plugin:

  • Creates config.yml with global defaults
  • Scans all loaded plugins via Bukkit's PluginManager
  • Writes a disabled stub entry for each plugin it finds
  • Registers the /updater command (alias /upd) with tab completion
  • Initialises an HttpClient with 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

See Geyser Addon Management.


/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.sendMessage is thread-safe.
  • The pendingUpdates map is a ConcurrentHashMap to 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.1 or newer.
  • Confirm you are running Java 17+: java -version.

No updates found even though I know there's a newer version

  • Check that enabled: true is set for the plugin entry in config.yml.
  • Verify current-version matches what's actually installed. If it's set higher than the installed version, the check may skip it.
  • For Modrinth: confirm minecraft-version and server-type-override match what the plugin targets. A plugin may not list a version for your specific loader.
  • For GitHub: the release must have a .jar asset attached. Releases with only source code zips are skipped.
  • Check allowed-release-types. If you have - release only, beta/alpha versions won't show.
  • Run /upd check and 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.