━━━━━━━━━━ Downloads ━━━━━━━━━━
- Latest Release - Download the most recent jar
- All Releases - Previous versions and prerelease test builds
━━━━━━━━━━ 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 ━━━━━━━━━━
- Download the jar from the Downloads section above
- Place it in your server's plugins/ folder
- Start the server — PackMerger generates config.yml, messages_en.yml, and creates the packs/, output/, and cache/ directories under plugins/PackMerger/
- Drop your resource pack .zip files (or unzipped pack folders) into plugins/PackMerger/packs/
- Edit plugins/PackMerger/config.yml to set priority order, upload provider, and distribution settings
- 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+)
• /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 ━━━━━━━━━━
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"
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
- Discover - Scans the packs folder for .zip files and directories containing pack.mcmeta or assets/
- Order - Builds a merge order from the priority config, per-server include/exclude rules, and any unlisted packs (sorted alphabetically at lowest priority)
- Merge - Iterates packs from lowest to highest priority and combines files in memory, applying format-specific merge strategies for JSON files
- Override - Applies any custom pack.mcmeta / pack.png placed directly in the packs folder
- Validate - Checks pack.mcmeta structure, JSON syntax, and missing texture/model references
- Upload - Sends the merged zip to the configured provider (Polymath or self-host)
- 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 ━━━━━━━━━━
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 ━━━━━━━━━━
- GitHub Issues - Bug reports and feature requests
- Source Code - Open source on GitHub
Made by PCX Network | Licensed under MIT
Quick facts
- Edition: Minecraft Java
- File type: .jar
- Minecraft versions listed: 1.21, 26.1, 26.2
- How to install: Install the matching mod loader (Forge, Fabric or NeoForge) for your Minecraft version. → Download the .jar. → Put it in the .minecraft/mods folder and launch that loader profile.
- Where to get it: Opens on Spigot — not every file is mirrored on our own servers.
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.