For the complete documentation index, see llms.txt. This page is also available as Markdown.

๐Ÿ–ฅ๏ธFor Developers

To protect a plugin you implement the ProtectedModule contract from the protected-api module. Your plugin is not loaded by Bukkit's plugin manager โ€” the thin loader downloads the encrypted jar, decrypts it in memory, finds the class named by the Obsidian-Module manifest attribute, instantiates it via its public no-arg constructor, and drives its lifecycle.

The contract

public interface ProtectedModule {

    /**
     * Called once after the license has been validated and the module loaded
     * into memory.
     *
     * @param context bridge to the host JavaPlugin (events, commands, logger, config)
     */
    void onEnable(PluginContext context);

    /**
     * Called when the host plugin is disabled (server stop / reload). Clean up
     * here; registered listeners/commands are also torn down automatically.
     */
    void onDisable();
}

PluginContext

The loader hands your module a PluginContext so it can integrate with the running server without being a Bukkit plugin itself.

registerCommand registers a command that does not exist in any plugin.yml via Bukkit's CommandMap, and unregisters it on disable.

Tier & entitlements

tier() and entitlements() are delivered inside the signed activation envelope, so they're tamper-proof. This is how one product sells Basic vs Premium with no separate builds โ€” the module reads them at runtime:

Classloader boundary

PluginContext and ProtectedModule live in protected-api, which is bundled into the loader and owned by the loader's classloader. Protected modules reference protected-api as compileOnly and resolve it at runtime via parent delegation โ€” so casts across the loader/module boundary stay valid. Don't shade protected-api into your module jar.

Packaging and uploading

Build your plugin the way you normally would (a standard Paper jar) โ€” just make it implement ProtectedModule and set the Obsidian-Module manifest attribute to your entry class. Then upload the jar to your product in the dashboard; the platform encrypts it and serves it to loaders. You do not build the loader yourself โ€” download it from the dashboard and ship it to customers.

Automating uploads from CI

Generate a personal API key in the dashboard and use it to upload a freshly built jar as a new product version from your CI pipeline. New uploads are retained as versions, so you can put trusted customers on a beta channel and promote or roll back instantly.

Last updated