New: see how many SpigotMC page viewers end up running your plugin →

Obfuscated plugins

Obfuscating your plugin is fine. Upload the mapping of each version and errors, stack traces, performance and reports show your real class and method names again.

Why a mapping

Obfuscators like ProGuard rename your classes and methods (ArenaManager.join becomes e.a), so an error from a server reads at com.example.arena.e.a(SourceFile:516). ProGuard writes a mapping file with every renaming; with it, PluginAnalytics turns the names back, the same way ProGuard's own retrace does:

at com.example.arena.e.a(SourceFile:516)
        ↓
at com.example.arena.mob.MobManager.onDeath(MobManager.java:516)

Each version has its own mapping, because the obfuscated names change between builds. The version must match the version in your plugin.yml, which is what servers report.

Keep the SDK readable

Relocate the SDK as usual, and tell ProGuard to leave it alone. It's small, and its own names are part of what you see in the dashboard:

# proguard.pro
-keep class your.plugin.libs.analytics.** { *; }

# Bukkit calls event handlers by reflection (you probably have this already)
-keepclassmembers,allowobfuscation class * {
    @org.bukkit.event.EventHandler <methods>;
}

Keep your plugin's main class too (the one in plugin.yml), as you already must for Bukkit to load it. Keeping line numbers makes stack traces exact: -keepattributes SourceFile,LineNumberTable.

Turn it on

In the dashboard, open Settings → Obfuscation and switch on My plugin is obfuscated. The card shows your plugin's upload token and the build task below. Mappings always come from your build, so every release brings its own and none is forgotten. The list there shows which versions have one; only the plugin's owner can manage them.

Upload it from your build

Every plugin has an upload token from the start: copy it from Settings → Obfuscation into ~/.gradle/gradle.properties, outside your repository:

pluginanalytics.token=pam_…

Then a task sends the mapping after ProGuard. It does nothing when there's no token, so builds on other machines keep working:

// at the top of build.gradle.kts
import java.net.HttpURLConnection
import java.net.URI

val uploadMapping by tasks.registering {
    dependsOn("proguard") // your ProGuard task
    doLast {
        val token = providers.gradleProperty("pluginanalytics.token").orNull ?: return@doLast
        val mapping = file("build/proguard/mapping.txt")
        val url = URI("https://pluginanalytics.dev/api/v1/mappings?version=${project.version}").toURL()
        val c = url.openConnection() as HttpURLConnection
        c.requestMethod = "POST"
        c.doOutput = true
        c.setRequestProperty("Authorization", "Bearer $token")
        c.outputStream.use { it.write(mapping.readBytes()) }
        check(c.responseCode == 200) { "Mapping upload failed: " + c.responseCode }
    }
}

From anything else (Maven, a CI job), it's one request:

curl -X POST -H "Authorization: Bearer $PLUGINANALYTICS_TOKEN" --data-binary @mapping.txt "https://pluginanalytics.dev/api/v1/mappings?version=1.4.0"

The token can only upload mappings for that plugin, and only the owner sees it. Replace the token in the same card if it leaks: the old one stops working at once.

What gets translated

WhereWhat
ErrorsStack traces (classes, methods and line numbers), exception types and class names in messages.
PerformanceMethods found by sampling and your listeners' class names. For obfuscated code the SDK adds the line of each sample, which tells apart the many methods ProGuard names alike (SDK 1.2.0 or newer).
ReportsThe types of the errors attached to a report.

Mappings are applied when you open the dashboard, so uploading one later also fixes data that already arrived for that version. Data from versions without a mapping is shown as it was sent.

Other obfuscators

Any tool that writes ProGuard's mapping format works, R8 included. Tools with their own formats (Allatori, Zelix KlassMaster, Stringer) aren't supported; leave line numbers on so you can still find the code by hand.