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

Keeps plugins up to date from Modrinth and Hangar, controls them in game, and fetches new ones on request.

Report PlugMaster?

PlugMaster

Tells you which of your plugins have a newer version, fetches it safely, and lets you switch plugins on and off without a restart.


Forty plugins in a folder, and no way to know which three of them shipped a fix last week. So you check by hand, or you don't check at all and find out from a player. Neither is a plan.

PlugMaster reads the folder for you. It works out which jars are published on Modrinth or Hangar, compares what you have against what's out there, and tells you what moved. Fetching the new file is a separate step you ask for, and nothing is ever swapped into a running server.

The second half is the part you reach for when something's already gone wrong at 2am: /pm list, /pm info, /pm disable, /pm reload. Turn the misbehaving plugin off, look at what it depends on, put it back, without kicking anyone.

Drop the jar in and restart. It reports and does nothing else until you tell it to.


Requirements

  • Bukkit, Spigot, Paper, Purpur or Folia, 1.16.5 and up
  • BungeeCord, Waterfall or Velocity for the update half
  • Java 11 or newer
  • Outbound HTTPS to api.modrinth.com and hangar.papermc.io

One jar carries a plugin.yml, a bungee.yml and a velocity-plugin.json and runs on any of them. The only bundled library is bStats; the JSON and YAML parsers are written into the project.


How it recognises your plugins

Modrinth goes by the SHA-1 of the file itself. Nothing to configure and nothing to guess at: the exact bytes you have either exist on Modrinth or they don't. Every installed plugin is looked up in one bulk request rather than one request per plugin, so a folder of sixty costs a single round trip.

That endpoint doesn't filter by release channel, though, so it can answer with a beta on a server that only accepts releases. When that happens the one plugin affected is asked about individually, where the channel does get honoured.

Hangar has no hash to go on, so matching happens in two stages. First the website entry of the plugin.yml, which is dependable when it points at a Hangar project. Failing that, a name search, and the result is only accepted if exactly one project matches. Ambiguous means no match, not a guess. By default the Hangar project owner also has to line up with an author from the plugin.yml.

If Modrinth is down, Hangar is skipped for that pass. Not because it wouldn't work, but because every plugin would count as unmatched, and Hangar matching is one search request each. Sixty requests to another service because a first one had a bad minute is not a trade worth making.

Whatever nothing matches is listed by /pm update list, so it's clear what still needs doing by hand rather than quietly looking up to date.


Why updates are parked instead of installed

No plugin can safely replace a jar underneath a running server. The classloader is still holding the old classes, listeners are registered against objects from it, scheduler tasks are mid-flight, and other plugins may be holding references of their own.

So a download goes to Bukkit's update folder, the one configured as update-folder in bukkit.yml, and the server installs it on its next start. Downloaded now, active after your next restart. BungeeCord and Velocity have no such folder, so there PlugMaster keeps its own and applies the parked files as the proxy shuts down.

If your update-folder is set to an empty value, the server hands back the plugins folder itself, and writing there would overwrite the jar that's currently running. PlugMaster notices that and falls back to plugins/update/.


What gets checked before anything is parked

  • Size, during the transfer. The limit is enforced while bytes are arriving, not measured once the file is already on disk. Content-Length is checked up front too, but headers are allowed to lie, so both.
  • It has to be a zip. Anything under 1 KB or without the PK magic bytes is an error page, not a jar. A Cloudflare interstitial returned with HTTP 200 is a real thing that happens.
  • The checksum. Modrinth publishes SHA-1 per file, Hangar SHA-256. A mismatch means discarded.
  • The plugin inside is the right plugin. The plugin.yml in the downloaded jar has to name the plugin it's meant to replace.

Anything that fails is deleted, not parked, and you're told which check it failed.

Already-parked updates are recognised as such, by hash first and by the version in their own plugin.yml otherwise. So 1.1 sitting in the update folder won't block 1.2 when it appears, and a newer jar you dropped in by hand won't be overwritten by an older one.


Runtime control

/pm enable and /pm disable use nothing but public Bukkit API. Disabling also tells you which loaded plugins list the target as a dependency, so you find out before they start throwing.

/pm unload and /pm reload are the fragile pair, and they're honest about it. There is no public API for removing a plugin from the server. What gets cleaned up: event handlers, scheduler tasks, registered services, the plugin's commands and their aliases, its entries in the manager's own registries, the dependency graph, and finally its classloader, which is closed so the jar is actually released.

Which internals those are depends on the server. Bukkit, Spigot and Paper up to 1.20.4 keep their plugins in SimplePluginManager. Paper 1.20.5 and newer run their own plugin system and leave that class as a shell that forwards everything, so the real registers sit in PaperPluginInstanceManager instead. PlugMaster carries both paths and picks whichever actually holds the plugin. If it finds neither, it refuses and says so, before the plugin has been touched.

Even where it works, a reload is best effort. A plugin that handed references to itself to something else can leave traces behind. For a plugin you've just updated, a restart is still the clean route. This is for the 2am case where a restart is the expensive option.


Installing a plugin you do not have yet

Off by default. downloader.enabled: true turns it on, and plugmaster.download gates it separately from everything else.

/pm search worldedit          list matching plugins with their ids
/pm download worldedit        fetch the newest fitting version into plugins/
/pm load worldedit.jar        start it, or leave it for the next restart

The search only offers plugins that fit: Modrinth is asked for plugins tagged with a loader this server runs and, while strict-game-version is on, for the server's own version. The download goes through the same four checks an update does, and refuses on top of that if the jar turns out to hold a plugin you already have.

It stops at downloading. The new jar sits in plugins/ and does nothing until you load it or restart.

Modrinth only. Hangar has no comparable search-and-fetch path, and guessing at one would mean guessing at which project a name refers to.


Metrics

PlugMaster reports anonymous counts to bStats: server software and version, Java version, player count, and which of PlugMaster's own switches are on. No names, no addresses, nothing about which plugins you run.

Switch it off in plugins/bStats/config.yml with enabled: false - that covers every bStats plugin on the server at once. Bukkit family only; the proxy side reports nothing.


Commands

Everything is /pm, with /plugmaster as the full name.

Command What it does Permission
/pm list every plugin with its status plugmaster.control.info
/pm info <plugin> details about one plugin plugmaster.control.info
/pm enable <plugin> enable a plugin plugmaster.control.enable
/pm disable <plugin> disable a plugin plugmaster.control.disable
/pm load <file.jar> load a jar from plugins/ plugmaster.control.load
/pm unload <plugin> remove a plugin from memory plugmaster.control.unload
/pm reload <plugin> unload and load it again plugmaster.control.reload
/pm update check look for updates plugmaster.update.admin
/pm update install [plugin] download and park updates plugmaster.update.admin
/pm update list plugins that could not be matched plugmaster.update.admin
/pm update reload-config reload config.yml plugmaster.update.admin
/pm search <text> find a plugin on Modrinth plugmaster.download
/pm download <id> fetch one into plugins/ plugmaster.download

plugmaster.* covers all of them. Every permission defaults to op.

Update checks run on a daemon thread of their own, never on the server scheduler, so a slow API can't hold up a tick. That's also what makes the plugin work on Folia without touching a scheduler API.

BungeeCord, Waterfall and Velocity get the update and download half only. Their plugin managers are built differently - Velocity wires its plugins up once at startup through Guice and has no runtime unload at all - and switching individual plugins on and off at runtime on a proxy is unusual enough that the extra reflection wasn't worth carrying.


Configuration

plugins/PlugMaster/config.yml, written on first start:

# PlugMaster
#
# Updates are never swapped in while the server is running. They are downloaded and
# parked, and the server picks them up on its next start.

# Look for updates automatically when the server starts.
check-on-startup: true

# Repeated checking. 0 = off. In hours.
check-interval-hours: 12

# false = only report which updates exist (recommended to begin with).
# true  = download updates straight away and park them for the next start.
auto-download: false

# Which release types are accepted: release, beta, alpha
channel: release

# Only take versions that match the server version. The whole patch series is sent
# along: on 1.21.4 versions for 1.21 through 1.21.3 count too, but never any newer
# than the server's own.
# false = also allow versions without a matching version tag (more hits, more risk)
strict-game-version: true

# Admins holding plugmaster.update.admin are shown pending updates when they log in -
# and immediately, if they are already online when something is found.
notify-admins-on-join: true

# These plugins are ignored entirely (name from plugin.yml or file name).
ignore:
  - PlugMaster

modrinth:
  enabled: true
  # One bulk request for every plugin instead of one request per plugin.
  # The endpoint does not filter by channel - if the channel it returns does not
  # fit, that plugin is asked about individually.
  bulk-check: true

hangar:
  # Hangar (hangar.papermc.io) is PaperMC's official plugin repository, with an open
  # REST API - no scraping, no Cloudflare problem.
  enabled: true
  # Only accept a match when the plugin name AND the project owner line up.
  # false = a name match is enough (more chance of a wrong match).
  require-author-match: true

# In-game plugin downloader: search Modrinth and pull a plugin that is not installed
# yet, straight from the game (/pm search, /pm download).
#
# Off by default, and deliberately so. Updating something you already chose to run is
# one decision; writing a brand new jar into the plugins folder from a search result
# is a bigger one. Turn it on when you want it, and it still only ever downloads -
# the new plugin stays inactive until you load it or restart.
#
# Only Modrinth. Hangar has no comparable search-and-fetch path, and guessing at one
# would mean guessing at which project a name refers to.
downloader:
  enabled: false
  # How many search results to print at once. 1 to 20.
  search-results: 8

# Network
timeout-seconds: 20

# Larger downloads are aborted and discarded during the transfer - not measured only
# once the file already sits on the disk in full.
max-download-megabytes: 200

The first check runs 30 seconds after start, so it isn't competing with everything else a server does while booting. auto-download is off on purpose: left off, PlugMaster only ever reports, and every download is something you asked for.


What has been tested

The platform-independent core has 72 tests: downloads against a real HTTP server, the size limit enforced mid-stream, and rejection of error pages, wrong checksums and foreign jars.

The commands were then run against real servers - Paper 1.21.11 and Paper 1.20.6, headless, driving list, info, unload, load, reload twice over and update check from the console. Both came back with no exceptions, and the reloaded plugin re-ran its own startup each time. The downloader was run the same way: refused while switched off, enabled through /pm update reload-config, then searched, downloaded a real plugin into plugins/ and started it with /pm load.

Velocity 3.5.0 went through the same routine on a live proxy: a real Velocity plugin recognised by its velocity-plugin.json, matched on Modrinth, the update downloaded, parked and genuinely swapped in as the proxy shut down, plus a search and a download of a plugin that was not installed at all.

Known limitations

  • BungeeCord and Waterfall have not been through a live proxy. The update half is shared code that the Bukkit and Velocity sides exercise, but the BungeeCord entry point itself is unproven.
  • The Hangar field names are reconstructed, not verified against a live response. The code is deliberately defensive, so a field that isn't there costs the verification rather than the run, but treat Hangar matching as the less proven of the two sources.
  • Unload and reload reach into server internals, not API, on both kinds of server. A future version can move them; PlugMaster then refuses rather than half unloading something.
  • On Paper the command map may keep an entry. Paper backs it with its own command system, and that view refuses removal in some builds. The unload still goes through - the old command just answers until the next restart, and PlugMaster tells you.
  • Plugins that aren't published on either service can't be matched. That's not a bug, it's the whole reason /pm update list exists.

Source and license

codeberg.org/LucasTHCR/PlugMaster - GPL-3.0-only.

The released jar bundles one library, bStats, which is MIT-licensed and relocated because bStats requires it. Its notice ships inside the jar.

Questions and bug reports: dc.gg/paperstream

Information

Category
Admin Tools
Published
August 27, 2026
License
4Downloads
0Stars

Pinned Versions

  • R
    Paper1.21–1.21.11Waterfall1.21Velocity3.3–3.5

Members

1