HomeJavaModsPackMerger
PackMerger

[​IMG]

Merge, Host, and Distribute Multiple Resource Packs on Paper 26.1+

[​IMG]

[​IMG] [​IMG] [​IMG] [​IMG]

━━━━━━━━━━ Downloads ━━━━━━━━━━


━━━━━━━━━━ Overview ━━━━━━━━━━

PackMerger is a Paper plugin that merges multiple Minecraft resource packs into a single pack, hosts it via a built-in HTTP server or Polymath, and automatically distributes it to players on join. Designed for servers and networks that need to combine packs from different sources — UI overhauls, mechanics plugins like QualityArmory, custom model packs — into one seamless download.

Instead of forcing players to manually install or merge packs, drop your packs into a folder, configure priority, and PackMerger handles the rest: intelligent JSON merging, SHA-1 hash tracking, per-server composition, and hot reload during development.

━━━━━━━━━━ Features ━━━━━━━━━━


━━━━━━━━━━ Requirements ━━━━━━━━━━


━━━━━━━━━━ Installation ━━━━━━━━━━

  1. Download the jar from the Downloads section above
  2. Place it in your server's plugins/ folder
  3. Start the server — PackMerger generates config.yml, messages_en.yml, and creates the packs/, output/, and cache/ directories under plugins/PackMerger/
  4. Drop your resource pack .zip files (or unzipped pack folders) into plugins/PackMerger/packs/
  5. Edit plugins/PackMerger/config.yml to set priority order, upload provider, and distribution settings
  6. Run /packmerger reload or restart the server

━━━━━━━━━━ Commands ━━━━━━━━━━

Spoiler: Click to view all commands
/pm reload — Reload config, re-init upload provider, re-fetch on-reload remote packs, trigger a merge
/pm validate — Run validation on the current merged pack (JSON, missing refs, orphans, pack_format drift)
/pm status — Server name, provider, last merge time, pack URL, SHA-1, file size, discovered packs
/pm apply [player] — Force-send the current pack to all online players or one (supports @a selectors)
/pm inspect — Summary of the last merge (1.1.0+)
/pm inspect <pack> — Per-pack detail: files won + files contributed but lost on (1.1.0+)
/pm inspect collisions — List every output path touched by 2+ packs (1.1.0+)
/pm inspect export — Write the full merge report to output/last-merge-report.txt (1.1.0+)
/pm priority list — Show current priority order (1.1.0+)
/pm priority up|down|top|bottom <pack> — Move a pack in priority (1.1.0+)
/pm priority set <pack> <n> — Place at absolute position (1.1.0+)
/pm profile list — List defined profiles (1.1.0+)
/pm profile switch <name> — Activate a profile and re-merge (1.1.0+)
/pm fetch [alias] — Re-download remote packs, all or one (1.1.0+)

━━━━━━━━━━ Permissions ━━━━━━━━━━


━━━━━━━━━━ Configuration ━━━━━━━━━━

Spoiler: config.yml Example
Code (Text):
# Identifies this server for per-server pack configs. Auto-detected if empty.
server-name: ""

# Merge order — first entry has highest priority.
priority:
  - "main-pack.zip"
  - "ui-overhaul.zip"
  - "custom-models.zip"

# Per-server pack composition for multi-server networks.
server-packs:
  lobby:
   additional:
     - "lobby-textures.zip"
   exclude: []
  survival:
   additional:
     - "survival-textures.zip"
   exclude:
     - "ui-pack.zip"

merge:
  auto-merge-on-startup: true
  optimization:
   strip-junk-files: true       # Remove .DS_Store, Thumbs.db, __MACOSX, .git, etc.
   compression-level: 6         # ZIP compression (0-9)
   size-warning-mb: 100         # Warn if output exceeds this size (0 = disabled)
  hot-reload:
   enabled: true
   debounce-seconds: 5

upload:
  auto-upload: true
  provider: "self-host"          # "self-host" or "polymath"
  self-host:
   port: 8080
   public-url: ""               # Auto-detected from server.properties
   rate-limit: 50               # Max concurrent downloads (0 = unlimited)
  polymath:
   server: "https://your-polymath-host:5000"
   secret: "your-custom-secret"
   id: "my-server"

distribution:
  enabled: true
  required: false                # Kick players who decline
  prompt-message: ""             # Custom MiniMessage prompt (empty = Minecraft default)
  use-add-resource-pack: false   # true = add alongside existing; false = replace
  join-delay-ticks: 20
  cache:
   enabled: true                # Skip re-sending to players with matching SHA-1
  on-new-pack:
   action: "notify"             # "none", "notify", or "resend"
   notify-message: "<yellow>[PackMerger]</yellow> <gray>A new resource pack is available. Rejoin or use F3+T to reload.</gray>"

log-level: "info"                # "debug", "info", "warning", or "error"

━━━━━━━━━━ How It Works ━━━━━━━━━━

Merge → Upload → Distribute Pipeline

  1. Discover - Scans the packs folder for .zip files and directories containing pack.mcmeta or assets/
  2. Order - Builds a merge order from the priority config, per-server include/exclude rules, and any unlisted packs (sorted alphabetically at lowest priority)
  3. Merge - Iterates packs from lowest to highest priority and combines files in memory, applying format-specific merge strategies for JSON files
  4. Override - Applies any custom pack.mcmeta / pack.png placed directly in the packs folder
  5. Validate - Checks pack.mcmeta structure, JSON syntax, and missing texture/model references
  6. Upload - Sends the merged zip to the configured provider (Polymath or self-host)
  7. Distribute - If the SHA-1 changed, notifies online players per the on-new-pack action; new joiners receive it automatically (skipped if cached)

━━━━━━━━━━ JSON Merge Strategies ━━━━━━━━━━

PackMerger uses a different merge strategy for each Minecraft resource-pack JSON format, because naive deep-merge silently drops array data:


━━━━━━━━━━ Upload Providers ━━━━━━━━━━

Self-Host (Default)
Runs a built-in HTTP server; players download from http://<your-ip>:<port>/pack. Make sure the port is open in your firewall. Includes a concurrent download rate limiter.

Polymath
Uploads to a self-hosted Polymath instance (a lightweight Python server originally built for Oraxen). You must self-host your own Polymath if you choose this provider.

S3 / R2 / Backblaze B2 (v1.1.0+)
Upload to any S3-compatible object store. One config works for AWS S3, Cloudflare R2, and Backblaze B2; only the endpoint URL changes. Content-addressed keys with retention keep storage bounded while letting client HTTP caches work cleanly. Private-bucket setups get short-lived presigned URLs automatically. Credentials use ${ENV_VAR} substitution so secrets stay out of config.yml.

━━━━━━━━━━ Per-Server Packs ━━━━━━━━━━

On a multi-server network, each backend can have its own merged pack by defining server-packs entries. Each server's additional packs are merged at lowest priority below the global list, and exclude packs are skipped entirely. The output file is named <server-name>-merged-pack.zip to avoid collisions.

━━━━━━━━━━ Troubleshooting ━━━━━━━━━━

Spoiler: Merged pack not sending to players
  • Check that distribution.enabled is true
  • Verify a merge has completed: run /pm status and check Pack URL is not "N/A"
  • Check the console for upload errors — if the upload failed, no URL is available
  • Ensure upload.auto-upload is true

Spoiler: Self-host port already in use
  • Another process is using the configured port (default 8080)
  • Change upload.self-host.port to an unused port
  • Use netstat -tlnp | grep 8080 (Linux) or netstat -an | findstr 8080 (Windows) to find the conflicting process

Spoiler: Pack validation warnings
  • "Invalid JSON" - a JSON file has syntax errors; open it in a validator
  • "Missing texture" - a model references a texture that doesn't exist in the merged pack
  • "Missing model" - a blockstate references a model that doesn't exist
  • Validation warnings don't prevent the pack from sending — they flag potential visual issues

Spoiler: Players re-downloading every join (cache issue)
  • Ensure distribution.cache.enabled is true
  • The cache only updates after the client reports SUCCESSFULLY_LOADED
  • If using use-add-resource-pack: true, Minecraft may cache differently than replace mode
  • Delete plugins/PackMerger/cache/player-cache.json to reset
  • Set log-level: "debug" to see per-player send/skip logs

Spoiler: Pack too large / download timeouts
  • Check output size with /pm status
  • Increase merge.optimization.compression-level (up to 9)
  • Remove unnecessary packs or textures
  • For very large packs (200+ MB) consider a CDN or Polymath for faster downloads

Spoiler: QualityArmory / CustomModelData missing after merge
As of v1.0.3 this is fixed — item-model overrides arrays from multiple packs are now concat-deduped by predicate. If you're still seeing missing textures:
  • Update to v1.0.3 or newer
  • Confirm the QualityArmory pack zip actually contains its assets/qualityarmory/ namespace — unzip the merged output and check
  • Run /pm validate to surface missing-model / missing-texture references
  • On older versions, workaround: place QualityArmory as highest priority, or avoid letting other packs redefine the same assets/minecraft/models/item/*.json files

Spoiler: Players still see the old pack after a merge — was there a rollback? (v1.1.0+)
Since v1.1.0, validation errors trigger rollback by default: the new merge is discarded and the previous pack stays live. Check the console for [VALIDATION] entries mentioning rollback, or watch for PackValidationFailedEvent if you have a custom listener.
  • Fix the validation errors in the offending pack and /pm reload
  • To ship despite errors (not recommended), set validation.rollback-on-errors: false
  • First-run case: if no previous pack exists, the broken pack ships anyway so the initial merge doesn't stall forever

Spoiler: \"CustomModelData collision\" warning in the console
Since v1.0.5, when two packs both define an item-model override with the same predicate (e.g. both claim custom_model_data: 1000001 on iron_sword), PackMerger logs a warning naming the file and the offending predicate. This is informational — the pack still ships. It surfaces silent drops that previously only showed up in-game when the wrong item rendered.
  • The higher-priority pack's override wins; the lower-priority entry is dropped
  • The warning lists the file path and predicate so you can reassign one side's CustomModelData to a different number
  • If you don't want to change the pack, pick the one that should win and make sure it is listed above the other in priority

━━━━━━━━━━ Support ━━━━━━━━━━



Made by PCX Network | Licensed under MIT

Plugin details

Read from the plugin's own plugin.yml.

Quick facts

Install steps are the general flow for this file type — How to install Minecraft Java mods & modpacks walks through it step by step.

PackMerger is a free Minecraft Java mod. Compatible with Minecraft 1.21, 26.1, 26.2. Downloaded 211 times (via Spigot). Download it and open it directly in the game.

Explore more