Managing Plugins
Managing Plugins
Complete guide to the Installed tab — viewing, filtering, updating, enabling/disabling, uninstalling, upload detection, ZIP export, and all possible errors.
Installed Plugins List
Two sources:
- Browser-installed — Full tracking (provider, version, plugin_id, download URL, update detection)
- Manually uploaded — Auto-detected by scanner, registered as "Manual / Upload" with limited tracking
Columns
| Column | Description |
|---|---|
| Icon | Provider logo (empty for uploads) |
| Name | From JAR plugin.yml or filename (version regex removed) |
| Status | Active, Disabled, or Unknown |
| Provider | SpigotMC, CurseForge, Modrinth, Hangar, GeyserMC, or Upload |
| Version | Installed version number |
| Installed At | First installation timestamp |
| Latest Version | Latest available on platform (if tracked) |
Upload Detection
scanAndRegisterUploadedPlugins() runs automatically:
- Compares JAR files on disk (
/plugins/) with database entries - New files: Registered with provider
'upload'. Opens JAR to extractplugin.ymlfor name, or parses version from filename regex-(\d+\.\d+(?:\.\d+)?) - Deleted files: Removed from database (cleanup)
- Concurrency protection: Atomic lock with 10-second TTL
- Limitation: Upload plugins have no auto-update detection
Display Name Resolution
| Provider | Name Source |
|---|---|
| GeyserMC | Capitalize project name |
| PaperMC | Slug part of owner/slug |
| CurseForge | Slug if version-prefixed |
| Others | JAR plugin.yml → filename fallback |
Filtering
| Filter | Shows |
|---|---|
| Show All | All plugins |
| Active | Only enabled plugins |
| Disabled | Only disabled plugins (with -bak suffix) |
| Updates Available | Only plugins with newer platform versions |
Plugins with updates float to the top, then alphabetical.
Filter Preservation
The plugin uses resetTableKeepingFilters() — saves filter state before Filament resets, restores after. This prevents losing the "has_updates" filter when navigating.
Updating Plugins
How Update Detection Works
- Plugin queries source platform by stored plugin_id and provider
- Update metadata 24 hours cached per plugin
'upload'provider always skipped- String comparison for version IDs (not semantic versioning)
Special: Update-While-Disabled
If a plugin is disabled (has -bak suffix) when an update is installed:
- The
is_disabledstate is remembered before the update - After update, the plugin is re-disabled automatically
- Your disabled state is preserved across updates
Console Widget
- Syncs plugins on load
- Update check with 15-minute cache
- Warning badge + redirect URL to Plugin Browser
- Same pattern as Game Mods widget
Enable / Disable Plugins
Mechanism
File rename via read+write+delete (for Daemon API compatibility):
| Action | Steps | Example |
|---|---|---|
| Disable | 1. Read file 2. Write as {name}-bak 3. Delete original |
EssentialsX-2.20.jar → EssentialsX-2.20.jar-bak |
| Enable | 1. Read -bak file 2. Write as original name 3. Delete -bak |
EssentialsX-2.20.jar-bak → EssentialsX-2.20.jar |
Note: Three Daemon API calls per toggle. Server may need to be stopped first.
Notifications
| Action | Success | Error |
|---|---|---|
| Disable | "Plugin disabled: [Name]" | "Disable failed" + error |
| Enable | "Plugin enabled: [Name]" | "Enable failed" + error |
Uninstalling Plugins
Process
- Click Uninstall → confirm warning
- JAR file deleted from
/plugins/ - Database entry removed
- Notification: "Plugin uninstalled"
- Restart server to fully unload
What's NOT Removed
| Not removed | Example | Cleanup method |
|---|---|---|
| Config files | plugins/Essentials/config.yml |
File manager |
| Data files | plugins/LuckPerms/luckperms-*.db |
File manager |
| Permissions | Permission nodes in LuckPerms etc. | Permission plugin UI |
| World data | Custom structures, blocks | Not reversible |
Open Plugins Folder
Jumps to /plugins/ in the file manager. Useful for:
- Checking actual files on disk
- Manual uploads
- Cleaning up config directories after uninstall
- Verifying
-bakextensions on disabled plugins
ZIP Export
Process
- Click Download All as ZIP
- Notification: "ZIP download started"
- Server compresses all files in
/plugins/ - JWT-signed URL generated (expires after 30 minutes)
- Download starts automatically
Contains: All plugins (active + disabled)
Complete Error Reference
"Failed to read installed plugins"
/plugins/directory doesn't exist → start server once- Directory not readable → check Daemon permissions
- Disk full
- Daemon connection lost
"Failed to disable/enable plugin"
- Permissions: Read + write + delete required on
/plugins/ - File locked: Running server locks files → stop server first
- Concurrent access: Backup or file manager accessing simultaneously
- File not found: Externally renamed or deleted
- Daemon error: Any of the 3 steps (read/write/delete) can fail
"Failed to delete old versions"
- Old file locked by running server → stop server
- File permission issue
- File already manually removed
Updates not showing
- Update metadata 24 hours cached → clear cache
'upload'plugins always skipped- Version comparison is string-based
- Provider may not have propagated update yet
"Uninstall failed"
- File already manually deleted
- File locked by server process → stop and retry
- Daemon connection issue
Plugin crashes server after install
- Check console output / crash report
- Disable the plugin (don't delete!) + restart to confirm
- Are all dependencies installed?
- Check plugin compatibility with your MC version/server software
- Try an older stable version
- Check Java version (17 for MC 1.18+, 21 for MC 1.21+)
"Cache clear failed"
- Error notification shows
$e->getMessage() - SafeCacheService
flush()couldn't remove keys storage/framework/cache/must exist and be writable (0755)
"Cache health check failed"
- Warning logged by PluginManager
- Cache infrastructure may need repair
php artisan cache:clearas full reset
Provider Internal Functions Reference
This section documents the internal PHP code for each provider integration, including API parameters, response schemas, rate limits, and response processing logic.
SpigotMC / Spiget Provider — Complete Reference
API Endpoint Parameters — GET /v2/search/resources/{query}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string (path) | Yes | — | Search term (URL-encoded, in path) |
size |
int | No | 10 | Results per page (max 100) |
page |
int | No | 1 | 1-based page number |
sort |
string | No | — | Sort field, prefix - for descending (e.g. -downloads) |
fields |
string | No | — | Comma-separated field names to include |
API Endpoint Parameters — GET /v2/resources (browse without query)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
size |
int | No | 10 | Results per page |
page |
int | No | 1 | Page number |
sort |
string | No | -downloads |
Default sorted by most downloads |
Response Schema — Search Results
| Field | Type | Description |
|---|---|---|
[].id |
int | SpigotMC resource ID |
[].name |
string | Resource name |
[].tag |
string | Short description / tag line |
[].downloads |
int | Total download count |
[].rating.average |
float | Average user rating (0–5) |
[].icon.url |
string | Relative icon path (prepend Spiget CDN) |
[].testedVersions |
string[] | MC versions listed as compatible |
[].premium |
bool | True if paid resource |
[].external |
bool | True if hosted outside SpigotMC |
[].updateDate |
int | Unix timestamp of last update |
[].releaseDate |
int | Unix timestamp of initial release |
Header X-Total |
int | Total number of matching resources (for pagination) |
Rate Limits
| Tier | Limit | Notes |
|---|---|---|
| Unauthenticated | ~600 req/10min per IP | Spiget uses sliding window |
| When exceeded | HTTP 429 | No Retry-After header, wait ~60s |
// Search SpigotMC resources — with total count from header
$response = Http::timeout($timeout)
->get("https://api.spiget.org/v2/search/resources/{$query}", [
'size' => $perPage,
'page' => $page,
'sort' => '-downloads',
]);
// Total count is in the X-Total response header (not in JSON body!)
$total = (int) $response->header('X-Total');
// Response processing — how resources are transformed into plugin cards
$resources = $response->json();
$plugins = collect($resources)->map(function ($r) {
return [
'id' => $r['id'],
'name' => $r['name'],
'summary' => $r['tag'] ?? '',
'author' => '', // Spiget requires separate /resources/{id}/author call
'downloads' => $r['downloads'] ?? 0,
'icon_url' => !empty($r['icon']['url'])
? "https://www.spigotmc.org/{$r['icon']['url']}"
: null,
'updated_at' => date('c', $r['updateDate'] ?? 0),
'versions' => $r['testedVersions'] ?? [],
'provider' => 'spiget',
'has_direct_download' => !($r['premium'] ?? false) && !($r['external'] ?? false),
];
});
// Download availability check — HEAD request before attempting download
$check = Http::head("https://api.spiget.org/v2/resources/{$id}/download");
if ($check->status() !== 200) {
// Content-Type check: JAR = direct download, HTML = premium/external page
$contentType = $check->header('Content-Type');
if (str_contains($contentType, 'text/html')) {
$plugin['has_direct_download'] = false;
// Shows "External resource" or "Premium" badge
}
}
// Version list retrieval with release date sorting
$versions = Http::timeout($timeout)
->get("https://api.spiget.org/v2/resources/{$id}/versions", [
'size' => 20,
'sort' => '-releaseDate',
])->json();
// Each version: { id, uuid, name, releaseDate, downloads, rating }
// No file hash available from Spiget API
Spiget-specific behaviours:
- Resources with
premium: truecannot be downloaded via API — requires SpigotMC purchase - External resources (
external: true) are hosted outside SpigotMC — follow link manually - Icons use
data/resource_icons/{id}/{id}.jpgformat from Spiget CDN - Version filtering works by checking
testedVersionsincludes your MC version - No dependency resolution — Spiget API does not expose dependency data
- Author info requires separate
GET /resources/{id}/authorcall — plugin does this lazily
CurseForge Provider (Plugins) — Complete Reference
Uses the same CurseForge API as Game Mods but with classId=5 (Bukkit Plugins). The classId is the critical difference.
API Endpoint Parameters — GET /v1/mods/search
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
gameId |
int | Yes | 432 | Always Minecraft for plugins |
classId |
int | Yes | 5 | Bukkit Plugins (NOT 9137 which is Mods!) |
searchFilter |
string | No | — | Full-text search |
sortField |
int | No | 2 | 1=Featured (with query), 2=Popularity (without) |
sortOrder |
string | No | desc |
Sort direction |
gameVersion |
string | No | — | MC version filter |
pageSize |
int | No | 20 | Max 50 |
index |
int | No | 0 | Pagination offset |
Response Schema — Same as Game Mods CurseForge (see Game Mods documentation), key differences:
classId= 5 in all resultscategories[].classIdmatches plugin categories, not mod categorieslatestFilesIndexes[]may include Bukkit/Spigot/Paper compatibility markers
// Plugin search — classId=5 is the critical difference from Mods (9137)
$response = Http::withHeaders(['x-api-key' => $apiKey])
->timeout($timeout)
->get('https://api.curseforge.com/v1/mods/search', [
'gameId' => 432, // Minecraft
'classId' => 5, // ⚠ Bukkit Plugins — NOT 9137 (Mods)!
'searchFilter' => $query,
'sortField' => $query ? 1 : 2, // Dynamic sort
'sortOrder' => 'desc',
'gameVersion' => $mcVersion,
'pageSize' => $perPage,
'index' => ($page - 1) * $perPage,
]);
// Response processing — same structure as Mods, classId differs
$results = $response->json('data', []);
$plugins = collect($results)->map(function ($mod) {
return [
'id' => $mod['id'],
'name' => $mod['name'],
'slug' => $mod['slug'],
'summary' => $mod['summary'],
'author' => $mod['authors'][0]['name'] ?? 'Unknown',
'downloads' => $mod['downloadCount'],
'icon_url' => $mod['logo']['url'] ?? null,
'provider' => 'curseforge',
];
});
// Recursive dependency resolution — same pattern as Game Mods
foreach ($file['dependencies'] as $dep) {
if ($dep['relationType'] === 3) { // Required dependency
$depPlugin = $this->fetchMod($dep['modId']);
$this->installPlugin($depPlugin); // Recursive
}
}
// Description enrichment — CurseForge may return empty summary
if (empty($mod['summary'])) {
$html = Http::withHeaders(['x-api-key' => $apiKey])
->get("https://api.curseforge.com/v1/mods/{$mod['id']}/description")
->body();
// HTML stripped to plain text for display, max 200 chars
}
CurseForge Plugin Provider Error Map
| Error / Log Message | Cause | Impact | Solution |
|---|---|---|---|
classId=9137 results showing |
Code using mod classId | Wrong content type | Ensure classId=5 for plugins |
| Empty description | Plugin has no CurseForge summary | Missing info | Enrichment API call fills this |
| Dependency loop | Circular relationType=3 |
Install hangs | Report on plugin page |
downloadUrl null |
Author restricted download | Manual download needed | Download from CurseForge website |
Modrinth Provider (Plugins) — Complete Reference
API Endpoint Parameters — GET /v2/search
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | No | — | Search terms |
facets |
string (JSON) | Yes | see below | Must include project_type:plugin |
limit |
int | No | 20 | Max 100 |
offset |
int | No | 0 | Pagination offset |
Plugin-Specific Facets
// The plugin builds these facets specifically for server plugins
$facets = [
['project_type:plugin'], // Must be a plugin, not mod
['categories:paper', 'categories:spigot', // Server software compatibility
'categories:bukkit', 'categories:purpur'], // OR-combined within inner array
];
if ($mcVersion) $facets[] = ["versions:$mcVersion"];
if ($loader) $facets[] = ["categories:$loader"]; // e.g., categories:paper
$params['facets'] = json_encode($facets);
Response Schema — Same as Game Mods Modrinth (see Game Mods documentation), key differences:
hits[].project_type="plugin"(not"mod")hits[].categoriesincludes server types likebukkit,spigot,paper,purpurhits[].loaderslists supported server platforms
// Response processing — identical to mods, project_type differs
$hits = $response->json('hits', []);
$plugins = collect($hits)->map(function ($hit) {
return [
'id' => $hit['project_id'],
'name' => $hit['title'],
'slug' => $hit['slug'],
'summary' => $hit['description'],
'author' => $hit['author'],
'downloads' => $hit['downloads'],
'icon_url' => $hit['icon_url'] ?? null,
'versions' => $hit['versions'] ?? [],
'provider' => 'modrinth',
];
});
Modrinth Plugin Provider Error Map
| Error / Log Message | Cause | Impact | Solution |
|---|---|---|---|
No results with project_type:plugin |
Plugin not tagged correctly on Modrinth | Missing plugins | Try searching on other providers |
| SHA-1 mismatch | Download corrupted | Integrity check fails | Clear cache, retry download |
Wrong project_type results |
Facets misconfigured | Mods mixed with plugins | Ensure project_type:plugin facet |
PaperMC / Hangar Provider — Complete Reference
API Endpoint Parameters — GET /v1/projects
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q |
string | No | — | Search query |
limit |
int | No | 25 | Results per page (max 25) |
offset |
int | No | 0 | Pagination offset |
sort |
string | No | -stars |
Sort: stars, downloads, views, newest, updated, recent (prefix - for desc) |
API Endpoint Parameters — GET /v1/projects/{slug}/versions
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slug |
string (path) | Yes | — | Project slug (e.g. ViaVersion) |
limit |
int | No | 10 | Versions per page |
offset |
int | No | 0 | Pagination offset |
platform |
string | No | — | Filter: PAPER, VELOCITY, WATERFALL |
Response Schema — Projects Search
| Field | Type | Description |
|---|---|---|
result[].namespace.owner |
string | Project owner username |
result[].namespace.slug |
string | Project URL slug |
result[].name |
string | Display name |
result[].description |
string | Short description |
result[].stats.downloads |
int | Total downloads |
result[].stats.stars |
int | Star count |
result[].stats.watchers |
int | Watcher count |
result[].avatarUrl |
string | Project icon URL |
result[].lastUpdated |
string | ISO 8601 timestamp |
pagination.count |
int | Total results |
pagination.limit |
int | Page size |
pagination.offset |
int | Current offset |
Response Schema — Versions
| Field | Type | Description |
|---|---|---|
result[].name |
string | Version display name |
result[].createdAt |
string | ISO 8601 publish date |
result[].downloads.PAPER[].fileInfo.name |
string | JAR filename |
result[].downloads.PAPER[].fileInfo.sizeBytes |
int | File size |
result[].downloads.PAPER[].externalUrl |
string|null | External download URL (if hosted externally) |
result[].platformDependencies.PAPER |
string[] | MC version range (e.g. ["1.8-1.21"]) |
result[].pluginDependencies.PAPER[].name |
string | Required plugin names |
// Project search — with pagination
$response = Http::timeout($timeout)
->get('https://hangar.papermc.io/api/v1/projects', [
'q' => $query,
'limit' => $perPage,
'offset' => ($page - 1) * $perPage,
]);
// Response processing
$results = $response->json('result', []);
$plugins = collect($results)->map(function ($project) {
return [
'id' => $project['namespace']['slug'], // owner/slug not needed for Hangar
'name' => $project['name'],
'summary' => $project['description'],
'author' => $project['namespace']['owner'],
'downloads' => $project['stats']['downloads'] ?? 0,
'icon_url' => $project['avatarUrl'] ?? null,
'updated_at' => $project['lastUpdated'],
'provider' => 'hangar',
];
});
// Version listing with platform priority
$versions = Http::timeout($timeout)
->get("https://hangar.papermc.io/api/v1/projects/{$slug}/versions", [
'limit' => 20,
])->json('result', []);
// Platform priority: PAPER → VELOCITY → WATERFALL
// Each version has platformDependencies showing MC version ranges
foreach ($versions as $v) {
$mcVersions = $v['platformDependencies']['PAPER'] ?? [];
// Returns e.g. ["1.8-1.21"] — means 1.8 through 1.21 supported
// External URL check — some plugins link to GitHub/Jenkins
if (!empty($v['downloads']['PAPER'][0]['externalUrl'])) {
$plugin['requires_manual_download'] = true;
}
}
Hangar-specific behaviours:
- Version-agnostic: Does NOT filter by specific MC version — shows all versions for all platforms
- Projects use
namespace.owner/namespace.slugformat (e.g.,ViaVersion/ViaVersion) externalUrlin downloads → plugin flagged asrequires_manual_download- Stats include
downloads,stars,watchers— used for sort ranking - Plugin dependencies listed in
pluginDependencies.PAPER[].name— informational only (not auto-resolved) - MC version filter is hidden in UI for Hangar since it doesn't support server-side filtering
Hangar Provider Error Map
| Error / Log Message | Cause | Impact | Solution |
|---|---|---|---|
| No results | Query too specific or no Hangar plugins match | Empty page | Use shorter search terms |
| "Version not found" | Project delisted from Hangar | Download fails | Project was removed by author |
| Wrong platform version | Version for VELOCITY not PAPER | Incompatible JAR | Check platformDependencies before download |
| External URL redirect | Plugin hosted on GitHub/Jenkins | Can't auto-install | Must download manually from external URL |
| CDN timeout | Hangar download servers slow | Download fails | Increase timeout, retry later |
GeyserMC Provider — Complete Reference
API Endpoint Parameters — GET /v2/projects
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| (none) | — | — | — | Returns all project names (always 6) |
API Endpoint Parameters — GET /v2/projects/{project}/versions/latest/builds/latest
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
project |
string (path) | Yes | — | One of the 6 project names |
The 6 GeyserMC Projects
| Project | Description | Primary Download Key |
|---|---|---|
geyser |
Bedrock-to-Java bridge | spigot |
floodgate |
Bedrock account linking | spigot |
hurricane |
Performance enhancement | spigot |
geyserconnect |
Multi-server connector | standalone |
thirdpartycosmetics |
Bedrock cosmetics support | spigot |
emoteoffhand |
Emote offhand fix | spigot |
Response Schema — Latest Build
| Field | Type | Description |
|---|---|---|
build |
int | Build number |
version |
string | Version string (e.g. 2.4.0) |
downloads.{platform}.name |
string | JAR filename (e.g. Geyser-Spigot.jar) |
downloads.{platform}.sha256 |
string | SHA-256 hash for integrity |
changes[].commit |
string | Commit hash |
changes[].summary |
string | Commit message |
// List all GeyserMC projects (always returns the same 6)
$projects = Http::timeout($timeout)
->get('https://download.geysermc.org/v2/projects')
->json();
// ["geyser","floodgate","hurricane","geyserconnect","thirdpartycosmetics","emoteoffhand"]
// Get latest build with platform-specific downloads
$build = Http::timeout($timeout)
->get("https://download.geysermc.org/v2/projects/{$project}/versions/latest/builds/latest")
->json();
// Response processing — version ID is constructed from version + build
$versionId = $build['version'] . '-' . $build['build']; // e.g. "2.4.0-670"
// Download key resolution — platform depends on project
$downloadKey = match ($project) {
'geyserconnect' => 'standalone',
default => 'spigot', // Paper/Spigot servers use 'spigot' key
};
$jarName = $build['downloads'][$downloadKey]['name']; // "Geyser-Spigot.jar"
$sha256 = $build['downloads'][$downloadKey]['sha256']; // Integrity hash
$url = "https://download.geysermc.org/v2/projects/{$project}/versions/{$build['version']}/builds/{$build['build']}/downloads/{$downloadKey}";
// GeyserMC special download — bypasses pull(), uses cURL directly
// This is because GeyserMC URLs require specific User-Agent handling
$tmpFile = tempnam(sys_get_temp_dir(), 'geyser_');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => false,
CURLOPT_FILE => fopen($tmpFile, 'w'),
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true,
]);
curl_exec($ch);
curl_close($ch);
// File then written via daemon putContent()
$this->fileRepository->putContent("/plugins/{$jarName}", file_get_contents($tmpFile));
unlink($tmpFile);
GeyserMC-specific behaviours:
- No search — always shows all 6 projects in a fixed list
- Version-agnostic — always shows the latest build, no MC version filtering
- SHA-256 checksums available for every download — used for integrity verification
- Download URLs:
https://download.geysermc.org/v2/projects/{project}/versions/{ver}/builds/{build}/downloads/{platform} - Available platforms vary per project:
spigot,bungee,standalone,velocity,fabric - 24-hour metadata cache for build info
- Uses Base64 SVG island icon for all projects (hardcoded, not from API)
- Hardcoded download counts displayed (not fetched live)
- MC version filter is hidden in UI for GeyserMC (irrelevant)
GeyserMC Provider Error Map
| Error / Log Message | Cause | Impact | Solution |
|---|---|---|---|
| Only 6 projects shown | Expected — GeyserMC has exactly 6 | Not an error | Normal operation |
| "Build not found" | GeyserMC build server issue | Can't determine latest version | Retry later, check download.geysermc.org |
| Wrong platform JAR | Selected wrong downloadKey |
Incompatible JAR installed | Choose spigot for Spigot/Paper servers |
| SHA-256 mismatch | Download corrupted in transit | Integrity check fails | Re-download, check network stability |
| Old version installed | 24-hour metadata cache | Shows outdated build | Clear cache for immediate update check |
| cURL download failed | Temp file write error or timeout | No JAR downloaded | Check temp directory write permissions, increase timeout |
GeyserMC download failed: {message} |
Any error in download pipeline | Install fails | Check server logs for specific error |
Search Retry Logic
If the initial search with version filter returns 0 results, the plugin automatically retries without the version filter. This helps when a specific MC version isn't indexed yet on a provider.
// Automatic fallback search — applies to all searchable providers
$results = $this->search($query, $mcVersion, $loader);
if (empty($results) && $mcVersion) {
// Retry without version filter — version may not be indexed yet
$results = $this->search($query, null, $loader);
// User sees all versions, can manually check compatibility
}
Cross-Provider Patterns
// Disable/Enable — write to -bak name, then delete original (daemon compatibility)
// Uses 3 steps: read → write new name → delete old
// NOT a simple rename — pterodactyl daemon doesn't support rename atomically
$content = $this->fileRepository->getContent("/plugins/{$filename}");
$this->fileRepository->putContent("/plugins/{$filename}-bak", $content);
$this->fileRepository->delete("/plugins/{$filename}");
// Update-while-disabled — remembers disabled state across updates
$wasDisabled = $plugin->is_disabled;
$this->updatePlugin($plugin); // Installs new version
if ($wasDisabled) {
$this->disablePlugin($plugin); // Re-disable after update
}
Complete Notification Reference
| Event | Type | Title | Body |
|---|---|---|---|
| Version detected | Success | Detection | "Minecraft version detected: X.X.X" |
| Version not detected | Warning | Detection | "Could not detect Minecraft version" |
| Install started | Info | Installing | "Plugin installation started" |
| Install success | Success | Installed | "Successfully installed plugin" (logged) |
| Install failed | Danger | Failed | "Installation failed" + error detail |
| Dependencies | Info | Dependencies | "Installing X additional plugins…" |
| Plugin disabled | Success | Disabled | "Plugin disabled: [name]" |
| Plugin enabled | Success | Enabled | "Plugin enabled: [name]" |
| Disable failed | Danger | Failed | "Disable failed" + error |
| Enable failed | Danger | Failed | "Enable failed" + error |
| Plugin uninstalled | Success | Removed | "Plugin uninstalled" |
| Uninstall failed | Danger | Failed | error detail |
| Cache cleared | Success | Cache | "Cache cleared" + scan results |
| Cache error | Danger | Error | "Error clearing cache" + $e->getMessage() |
| ZIP started | Info | Export | "ZIP download started" |
| ZIP failed | Danger | Export | "ZIP download failed" |
| CurseForge key missing | Warning | Config | "CurseForge API key not set" |
| External resource | Warning | Download | "External resource — download manually" |
| Premium resource | Warning | Download | "Premium resource — purchase required" |
| Filename detect fail | Warning | Install | "Failed to detect filename" (logged) |
| JAR metadata fail | Warning | Install | "Failed to extract plugin name from JAR" (logged) |
HTTP Error Code Reference (All Providers)
| HTTP Status | Source | Meaning | Action |
|---|---|---|---|
| 200 | Any | Success | — |
| 400 | CurseForge/Spiget | Bad request | Check game ID, class ID, query format |
| 401 | CurseForge | API key invalid | Replace key at console.curseforge.com |
| 403 | CurseForge | Key lacks permissions | Regenerate key |
| 403 | Plugin | Feature flag plugins missing |
Add ["plugins"] to Egg Features |
| 404 | Any provider | Resource not found | ID/slug wrong or resource removed |
| 404 | Spiget | Version not found | Resource may have been deleted by author |
| 429 | CurseForge | Rate limit | Wait 60s, increase cache duration |
| 429 | Spiget | Rate limit | Reduce frequency, wait 60s |
| 500 | Any | Server error | Retry later |
| 502/503 | Any | Gateway/maintenance | Provider offline |
| 0 / timeout | Any | Connection timeout | Increase MODS_REQUEST_TIMEOUT, check DNS/firewall |
Provider-Specific Error Table
SpigotMC / Spiget Errors
| Error / Symptom | Cause | Solution |
|---|---|---|
| No download button | Premium resource | Purchase on SpigotMC website first |
| "External resource" label | Plugin hosted externally | Follow link to external site |
| No versions listed | Very old or inactive resource | Use File Manager for manual upload |
| Slow search | Spiget API under load | Increase timeout, retry |
| Missing icon | No icon uploaded for resource | Cosmetic — plugin works fine |
| HEAD check fails | Spiget CDN timeout | Retry, check connectivity |
Hangar Errors
| Error / Symptom | Cause | Solution |
|---|---|---|
| No results | Search query too specific | Use shorter/broader terms |
| "Version not found" | Hangar project removed | Project may have been delisted |
| Wrong platform | Version for wrong server type | Check platformDependencies (PAPER vs WATERFALL) |
| Download fails | CDN timeout | Retry, increase timeout |
| Empty stats | New project | No downloads recorded yet |
GeyserMC Errors
| Error / Symptom | Cause | Solution |
|---|---|---|
| Only 6 projects shown | Expected — GeyserMC has exactly 6 | Not an error |
| "Build not found" | GeyserMC build server issue | Retry later |
| Wrong platform JAR | Selected wrong download platform | Choose spigot for Spigot/Paper servers |
| SHA-256 mismatch | Download corrupted | Re-download, check connection |
| Old version installed | Version cache is 24h | Clear cache for immediate update check |