Skip to content

1. Project setup

Martijn Muijsers edited this page May 30, 2026 · 10 revisions

First, we set up our project.

Tip: you can get started quickly if you use the template. The template README.md already contains instructions on how to make it into your own plugin. If you do so, you can skip all of this page and go straight to adding blocks and items.

Otherwise, start by setting up a Paper plugin project.
(See the link to learn how to set up a Paper plugin; from this point on it is assumed you already have a Paper plugin set up.)


Add the Spout API as a dependency

Remove the compileOnly("io.papermc.paper:paper-api:...) dependency and replace it with the Spout API:

repositories {
    mavenCentral()
    maven("https://repo.papermc.io/repository/maven-public/")
    maven("https://jitpack.io")
}

dependencies {
    compileOnly("com.github.ModernSpout:Spout:1.14")
}

Note: if you want to support both Paper and Spout (for extra features) in your plugin, using only the Spout API as a dependency works, as long as you avoid calling Spout-only code on Paper servers (see further below).


Add a bootstrapper

Your plugin must have a bootstrapper:

public class MyPluginBootstrap implements PluginBootstrap {
    @Override
    public void bootstrap(BootstrapContext context) {
        // Do bootstrap stuff here
    }
}

Add it to paper-plugin.yml, for example:

bootstrapper: com.example.exampleplugin.MyPluginBootstrap

Register data pack and resource pack

Adding custom content is done with a data pack and resource pack.
The data pack defines server behavior (drop tables, recipes and more), and the resource pack defines visuals (models, textures, names and more).

First, create a data pack and resource pack folder:

src/
└── main/
    └── resources/
        ├── data_pack/
        └── resource_pack/

Create a pack.mcmeta file in the data_pack folder:

src/
└── main/
    └── resources/
        └── data_pack/
            └── pack.mcmeta

pack.mcmeta is a regular data pack mcmeta file. For example:

{
  "pack": {
    "description": "The data pack for the ExtraBricks plugin",
    "min_format": [101, 1],
    "max_format": [101, 1]
  }
}

You must let the server know your plugin is providing a data pack and resource pack.
Put the below code in your bootstrapper class (you always need this):

    @Override
    public void bootstrap(BootstrapContext context) {
        registerIncludedDataPack(context);
        registerIncludedResourcePack(context);
    }

    /**
     * Makes sure the included data pack is loaded.
     */
    private void registerIncludedDataPack(BootstrapContext context) {
        context.getLifecycleManager().registerEventHandler(
                LifecycleEvents.DATAPACK_DISCOVERY,
                event -> {
                    try {
                        event.registrar().discoverPack(
                            this.getClass().getResource("/data_pack").toURI(),
                            "provided"
                        );
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
        );
    }

    /**
     * Makes sure the included resource pack is loaded.
     */
    private void registerIncludedResourcePack(BootstrapContext context) {
        context.getLifecycleManager().registerEventHandler(
                SpoutEvents.PLUGIN_RESOURCE_PACK_DISCOVERY,
                event -> event.register(this, context)
        );
    }

Optional: make your plugin Paper-safe

Maybe, you want your plugin to work on Paper but provide extra features on Spout. Or, you want to publish your plugin on a platform that requires the plugin to be loadable on Paper (even if all functionality requires Spout).

The spout.server.paper.api.SpoutMarker class doesn't do anything, but its presence can be used by plugins to check whether the server supports Spout.

Here is an example utility that you can use to check for Spout:

public class CheckSpout {

    public static boolean checkSpout() {
        try {
            Class.forName("spout.server.paper.api.SpoutMarker");
            return true;
        } catch (ClassNotFoundException ignored) {
            ComponentLogger.logger().warn("This plugin requires Spout: https://github.com/ModernSpout/Spout");
            return false;
        }
    }

}

You could call it at the start of your JavaPlugin.onEnable and PluginBootstrap.bootstrap to avoid calling Spout-only code on a Paper server:

public class ExamplePlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Skip if the server doesn't support Spout
        if (!CheckSpout.checkSpout()) {
            return;
        }

        // Register events and such
        Bukkit.getPluginManager().registerEvents(new ExamplePluginListener(), this);
        getLogger().info(getName() + " enabled!");
    }

}
class ExamplePluginBootstrap implements PluginBootstrap {

    @Override
    public void bootstrap(BootstrapContext context) {
        // Skip if the server doesn't support Spout
        if (!CheckSpout.checkSpout()) {
            return;
        }

        // Include data and resource pack
        registerIncludedDataPack(context);
        registerIncludedResourcePack(context);
    }

    ...

}

Clone this wiki locally