Skip to content

How to add license keys to a Fabric mod ​

Short answer: add the SunLicense Java SDK (included in your jar), read the buyer's key from a file in config/, check it on a background thread from your DedicatedServerModInitializer, and switch your features off when SunLicense says no. A network error is not a no: keep running and check again later.

java
// build.gradle: implementation + include "com.sunlicense:sunlicense:0.0.1"
public final class MyMod implements DedicatedServerModInitializer {
    private static final SunLicense CLIENT = new SunLicense.Builder()
            .baseUrl("https://api.sunlicense.com").token("slk_…").build();   // API key from your build, never git
    public static volatile boolean licensed;

    @Override
    public void onInitializeServer() {
        String key = readKey(FabricLoader.getInstance().getConfigDir().resolve("mymod.properties"));
        Thread.ofVirtual().start(() -> {
            try {
                SunLicense.Result r = CLIENT.validate(key, Map.of("host", machineId()));
                licensed = r.isValid() || r.getStatus() != 400;   // only a 400 is SunLicense saying no
                if (r.getStatus() == 400) LOGGER.error("MyMod license rejected: {}", r.getBody());
            } catch (SunLicense.SunLicenseException e) {
                licensed = true;   // unreachable: keep running, check again later
            }
        });
    }
}

Every feature starts with if (!MyMod.licensed) return;. readKey reads license-key from the properties file and machineId() is the one from the Paper guide.

Start from the template instead: example-fabric-mod (Fabric 26.3, Java 25, Loom) has this plus a stable machine id, masked keys, re-checks, a signed offline license fallback, BuiltByBit placeholders, a sealed core, tests and CI. All templates.

Or let your AI agent do it: paste the setup prompt into your coding agent in the mod's folder.

Free plan: 3 products, 100 licenses, no credit card. Create a free account.

Step by step ​

  1. In the dashboard: a product for your mod, a policy (for example 2 machines: a machine is a server) and an API key, which can only validate licenses.

  2. Add the SDK to the jar. Fabric has no shading by default; Loom's include nests the SDK jar in yours:

    groovy
    dependencies {
        implementation "com.sunlicense:sunlicense:0.0.1"
        include "com.sunlicense:sunlicense:0.0.1"
    }
  3. Keep the API key out of git: put it in ~/.gradle/gradle.properties and expand it into a resource at build time (processResources), like the template's sunlicense.properties.

  4. The buyer's key goes in config/mymod.properties (license-key=), or comes from a %%__SUNLICENSE__%% placeholder that BuiltByBit and Voxel Shop fill in on download.

  5. Server side only. Use DedicatedServerModInitializer (the server entrypoint in fabric.mod.json) so the check runs on the server that holds the license, not on every player's client.

  6. Never block the server thread. Check on a background thread; act on the result on the server thread (server.execute(...)) if you need to touch the game.

  7. Machine id: a hash of OS, architecture and host name, sent as host (the Paper guide has the snippet; why it matters).

  8. Results: 200 starts your features, 400 stops them with the reason (keys masked in logs), anything else keeps running and retries in about 10 minutes, or uses an offline license. Re-check every few hours (ServerTickEvents or a scheduled executor) so a revoked key reaches running servers.

Selling it ​

Mods sold on BuiltByBit or Voxel Shop get the buyer's key written into the jar by the placeholder. Anywhere else (Tebex, Gumroad, Payhip, other stores), SunLicense issues a key per purchase and the buyer pastes it into the config file.

A license check belongs in mods you sell for servers. Free mods on Modrinth or CurseForge are free downloads: don't lock them; if you want install numbers for those, use product analytics without a license.

Going further ​

Send feedback

Found something broken, or have an idea? It goes straight to our board.

The first line becomes the title.
Images (optional, up to 5: paste, drop or capture)
Check each image before sending, and remove any that show something private.

We also receive this page's address and your browser version.

Ctrl Enter to send