This is a hands-on guide to adding a license key check to a Minecraft plugin. By the end, your plugin will refuse to enable without a valid key. Every check will be cryptographically signed, so it can't be faked, and server limits will stop a key being shared around.
We'll use MC License, because that's what we build. The steps work on Paper, Spigot and Folia.
What you'll need
- An existing Paper or Spigot plugin project (Gradle or Maven)
- A free MC License account
- About ten minutes
Step 1: Create the plugin in MC License
In the dashboard, create a new plugin. If you sell on BuiltByBit, Voxel Shop or Tebex, link the listing now so keys are issued automatically later.
Open the plugin and look at the URL. The 8-character string at the end, something like 3gd7u9r4, is your plugin ID. You'll need it in step 3.
Step 2: Add the library
Gradle (Kotlin DSL)
plugins {
java
id("com.gradleup.shadow") version "9.6.1"
}
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.flyte.gg/releases")
}
dependencies {
// your Paper or Spigot API dependency stays as it is (compileOnly)
implementation("org.mclicense:library:1.5.1")
}
tasks.shadowJar {
relocate("org.mclicense", "com.example.myplugin.libs.mclicense")
}
Build with ./gradlew shadowJar and ship the jar from build/libs that ends in -all.
Maven
<repositories>
<repository>
<id>flyte-repository-releases</id>
<url>https://repo.flyte.gg/releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.mclicense</groupId>
<artifactId>library</artifactId>
<version>1.5.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<relocations>
<relocation>
<pattern>org.mclicense</pattern>
<shadedPattern>com.example.myplugin.libs.mclicense</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Why relocate? If two plugins on the same server both shade the library under its original package, they can end up sharing classes. Moving it into your own package keeps your copy to yourself. Replace com.example.myplugin with your plugin's package.
Or: copy a single class
If shading gives you trouble, copy MCLicense.java (or MCLicense.kt) from the mcl-library-one-class repo into your project instead. No shading needed. Just remember to check that repo for updates now and then.
Step 3: Check the license in onEnable
Put the check at the very top of onEnable(), before you register anything:
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
if (!MCLicense.validateKey(this, "3gd7u9r4")) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
// Licensed: carry on with normal startup
getServer().getPluginManager().registerEvents(new MyListener(), this);
}
}
That single call:
- Creates
plugins/MyPlugin/mclicense.txton first start, and reads the key from it. - Uses a key injected by BuiltByBit or Voxel Shop instead, if there is one, and saves it to the file.
- Sends the key to MC License with a fresh random nonce.
- Verifies the RSA signature and the nonce on the response, so a faked or replayed "valid" is rejected.
- Starts a lightweight background heartbeat, which powers concurrent server limits and the analytics on your dashboard.
- Logs a clear success or failure message to the console.
It returns true only when everything checks out.
Step 4: Test it with a key
- Build the plugin and start a test server. The console will say the license key is empty and tell you where to put it.
- In the dashboard, create a license for your plugin. Set a user (anything that identifies the buyer), a max server count and an expiry, or Never.
- Paste the key into
mclicense.txtand restart. You should see the validation succeed. - Try breaking it: disable the license in the dashboard and restart. The plugin should refuse to enable.
When a check fails, the console shows the reason. The statuses you'll see most are not_found (wrong key or plugin ID), expired, max_ips (too many servers), blacklisted and disabled. The full list is in the security docs.
Step 5: Think about startup time
The check happens during onEnable(), and the library gives up after 5 seconds if it can't connect, and another 5 if no response comes back. On a healthy connection it's quick, but if the server can't reach the internet at all, startup is delayed while it waits.
For most plugins that's fine. If you'd rather keep the check off the main thread entirely, here's how to run network calls without freezing the server.
Step 6: Ship it
How the buyer gets their key depends on where you sell:
- BuiltByBit: the key is written into the jar at download. Nothing for the buyer to do. (Setup)
- Voxel Shop (Polymart): the purchase is verified with Voxel Shop the first time the plugin starts. Nothing for the buyer to do. (Setup)
- Tebex: the key is emailed straight after payment, and the buyer pastes it into
mclicense.txt. (Setup) - Selling directly: create keys in the dashboard, from Discord with the bot, or through the API.
Remember that SpigotMC doesn't allow licensing in premium resources, so don't upload a licensed build there.
Next steps
- Obfuscate your plugin with ProGuard, so removing the check takes real effort.
- Stop key sharing by choosing sensible server limits.
- Share the license checker with buyers, so they can check their own key's status without messaging you.