PackMerger icon

PackMerger -----

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




[​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 ━━━━━━━━━━

  • Automatic Plugin-Pack Discovery - Detects packs generated by other installed plugins (Oraxen, Nexo, ItemsAdder, ModelEngine, EliteMobs, FreeMinecraftModels) and pulls each into the merge automatically — no manual exporting or copying. Unlike file-overwrite mergers, these are deep-merged through the format-aware strategies, so overlapping item models and arrays combine instead of clobbering each other.
  • Priority-Based Merging - Control which pack's files win on conflict via a simple ordered list.
  • Format-Aware Deep JSON Merging - Dedicated strategies per Minecraft format (models, blockstates, items, equipment, atlases, font, sounds, language, pack.mcmeta) so nothing is silently lost: overrides/sources/providers/overlays.entries/filter.block arrays concat-dedup by identity, lang files key-union, and same-predicate custom_model_data collisions are flagged at merge time (no more "my knife turned into a pistol").
  • Merge Provenance + /pm inspect - For every output file, records which pack won and every contributor. Surface via /pm inspect (summary, per-pack, collisions, exportable report) and the plugin API.
  • Validation + Rollback - Checks pack.mcmeta structure, JSON syntax, and missing texture/model references after every merge; if a merge trips errors, the previous pack stays live for players instead of shipping broken output.
  • pack_format Guardrail - Warns (or errors) when the merged pack's pack_format doesn't match the running server version. Honors supported_formats ranges and tracks the latest Minecraft versions.
  • Orphan Asset Detection - Finds unreferenced textures/sounds so you can trim bloated packs.
  • In-Game Priority Reordering - /pm priority up|down|top|bottom|set <pack> edits config and re-merges live.
  • Profiles / Presets - Flip whole pack compositions atomically via /pm profile switch <name> for seasonal events or A/B tests.
  • Remote Pack Sources - Declare pack URLs in config; PackMerger downloads + caches them with ETag / Last-Modified and bearer/basic auth using ${ENV_VAR} substitution. /pm fetch to refresh.
  • Flexible Hosting - Built-in HTTP host (no third-party service; optional auto port), a self-hosted Polymath instance, or any S3-compatible store (AWS S3, Cloudflare R2, Backblaze B2) with content-addressed keys + retention and public or presigned URLs.
  • Bedrock / Geyser Conversion (opt-in, experimental) - Converts the merged pack's custom item icons into a Bedrock pack + Geyser custom-item mappings so Bedrock players see the same custom items, with optional auto-deploy into Geyser's folders. Targets 2D item icons (1.21.4+ item-definition format); 3D geometry not yet converted.
  • Network-Wide Distribution (optional) - A companion Velocity proxy plugin offers the merged pack to players from the proxy instead of each backend sending its own; backends can push live URL/hash updates to it over a plugin channel.
  • Auto-Distribution on Join - The pack is sent to players automatically when they connect, with player-cache tracking that skips re-sending when the SHA-1 already matches.
  • Per-Server Composition - Multi-server networks can include/exclude packs per backend.
  • Hot Reload - Watches the packs folder and auto-merges on changes with configurable debounce.
  • Custom Overrides - Drop a pack.mcmeta or pack.png into the packs folder to override the merged metadata/icon.
  • Plugin API + Bukkit Events - PackMergedEvent, PackUploadedEvent, PackValidationFailedEvent, and more — other plugins react without log-scraping.
  • Brigadier Commands - Full tab completion via Paper's modern command API; all player-facing text is externalized in messages_en.yml.
  • Lean Jar - Ships ~360 KB; heavy optional dependencies (the S3 SDK) are downloaded on first enable and verified by SHA-256.

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

  • Paper 26.1+ (built against Paper API 26.1.2)
  • Java 25+
  • Tested on Paper and Folia-compatible forks

━━━━━━━━━━ 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 ━━━━━━━━━━

/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 ━━━━━━━━━━

  • packmerger.admin - Access to all /packmerger commands (default: op)
  • packmerger.notify - Receive chat notifications when a new pack is available (default: true)

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

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:

  • Models (assets/<ns>/models/*.json) - Deep merge objects; overrides arrays concat and dedup by predicate (this is what makes QualityArmory CustomModelData merges survive). When two packs collide on the same predicate a warning is logged so the silent drop is visible to operators.
  • Blockstates (assets/<ns>/blockstates/*.json) - Variants merge per selector key; multipart cases concat and dedup by when clause
  • Atlases (assets/<ns>/atlases/*.json) - sources arrays concat with structural dedup (1.19.3+)
  • Item Definitions (assets/<ns>/items/*.json) - Key-level deep merge (1.21.4+)
  • Equipment (assets/<ns>/equipment/*.json) - Key-level deep merge per slot (1.21.2+)
  • Sounds (assets/<ns>/sounds.json) - Per-event sound arrays concat so all variants coexist
  • Font (assets/<ns>/font/*.json) - providers arrays concat so custom glyph packs compose
  • Language (assets/<ns>/lang/*.json) - Translation keys union across packs; higher priority wins on collision so plugins layering localizations into en_us.json no longer lose each other's strings
  • pack.mcmeta - Deep-merge on the metadata block; overlays.entries[] concat-dedup by directory (v1.0.4) and filter.block[] concat-dedup by namespace|path (v1.0.5)
  • Everything else (textures, audio, shaders) - Simple overwrite: higher priority wins

━━━━━━━━━━ 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 ━━━━━━━━━━

  • 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

  • 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

  • "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

  • 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

  • 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

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

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

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

Resource Information
Author:
----------
Total Downloads: 71
First Release: Feb 18, 2026
Last Update: Jun 19, 2026
Category: ---------------
All-Time Rating:
1 ratings
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings