AstraTemplate

AstraTemplate

Template plugin for Paper/Fabric/Valocity with pure and powerful functionality

by
2.6K Downloads
bukkitfabricforgepaperpurpurspigotvelocitylibrarystorageutility
Rent Server with this Mod

About this Mod

paper
forge
neoforge

AstraTemplate

A production-grade Minecraft plugin/mod template written in Kotlin. Provides a modular, lifecycle-driven architecture that runs across Paper, Forge, and NeoForge from a single shared codebase.


Plugins built on this template


Project structure

AstraTemplate/
├── instances/
│   ├── bukkit/        ← Paper entry point + platform wiring
│   ├── forge/         ← Forge entry point + platform wiring
│   └── neoforge/      ← NeoForge entry point + platform wiring
└── modules/
    ├── api/
    │   ├── local/     ← Database (Exposed ORM, platform-agnostic)
    │   └── remote/    ← REST client (Ktor, platform-agnostic)
    ├── core/          ← Config, translations, coroutine scopes
    ├── build-konfig/  ← Compile-time constants (id, version, etc.)
    ├── feature-command/   ← All commands (platform-agnostic!)
    ├── feature-gui/
    │   ├── api/       ← GUI interfaces (Router, GuiModule)
    │   └── bukkit/    ← Bukkit chest-GUI implementation
    └── feature-event/
        ├── bukkit/    ← Bukkit event listeners
        ├── forge/     ← Forge event listeners
        └── neoforge/  ← NeoForge event listeners

Each instances/<platform> builds a fat jar via ShadowJar and is the only place that knows about a specific platform. Everything in modules/ is either fully platform-agnostic or has a clearly named platform variant.

Modules

modules/core — config, translations, coroutine scopes

The foundation every other module depends on. Provides:

  • ConfigPluginConfiguration is a @Serializable data class written to config.yml. Reloaded on /atempreload via StateFlowKrate.
  • TranslationsPluginTranslation works the same way with translation.yml. Every string has a default value so the plugin works out of the box with no files present.
  • Coroutine scopesioScope, mainScope, and unconfinedScope backed by KotlinDispatchers (platform-provided abstraction over Dispatchers.IO / main thread / etc.). All scopes are cancelled in onDisable.
modules/api/local — local database via Exposed ORM

Local database access via Jetbrains Exposed ORM. The LocalDao interface exposes suspend functions for CRUD operations on UserTable and UserRatingTable. The underlying database connection is derived reactively from the config flow, so switching from H2 to MySQL is a one-line config change and a reload.

Supported drivers (configured in libs.versions.toml): H2, SQLite, MySQL, MariaDB.

modules/api/remote — REST API client via Ktor

REST API client built with Ktor. Demonstrates fetching data from an external HTTP endpoint (the Rick & Morty API). The RickMortyApi interface returns Result<T> — errors are never thrown, always returned explicitly.

modules/build-konfig — compile-time constants

Generates compile-time constants (id, version, etc.) via the BuildConfig Gradle plugin. Import from any module that needs to reference the plugin's identity at runtime without hardcoding strings.

modules/feature-command — cross-platform commands (no platform imports)

All commands in one place, with no platform imports. Uses the Brigadier DSL from AstraLibs to define commands that compile and run identically on Paper, Forge, and NeoForge. The platform-specific MultiplatformCommand adapter is injected at the RootModule level.

modules/feature-gui — chest GUI (Bukkit, with stub for other platforms)

Split into api (the Router interface + GuiModule) and bukkit (the implementation). The Bukkit implementation provides a paginated chest inventory driven by StateFlow — the GUI re-renders automatically whenever the underlying data changes. On Forge/NeoForge a StubGuiModule satisfies the interface so the shared command module compiles without pulling in Bukkit.

modules/feature-event — platform-specific event listeners

Platform-specific event listeners, one submodule per platform. The Bukkit variant listens to BlockPlaceEvent; Forge and NeoForge variants listen to the server tick. Each submodule exposes a Lifecycle so RootModule can register and unregister listeners cleanly.


Architecture

Lifecycle tree

Every module exposes a Lifecycle with three callbacks: onEnable, onDisable, onReload. The plugin entry point creates a RootModule, chains all child lifecycles, and delegates to them:

// instances/bukkit — AstraTemplate.kt
class AstraTemplate : LifecyclePlugin() {
    private val rootModule = RootModule(this)

    override fun onEnable() = rootModule.lifecycle.onEnable()
    override fun onDisable() = rootModule.lifecycle.onDisable()
    override fun onReload() = rootModule.lifecycle.onReload()
}
// instances/bukkit — RootModule.kt
class RootModule(plugin: AstraTemplate) {
    val coreModule = CoreModule(plugin.dataFolder, DefaultBukkitDispatchers(plugin))
    val apiLocalModule = ApiLocalModule(coreModule.configKrate.cachedStateFlow, coreModule.ioScope)
    val apiRemoteModule = ApiRemoteModule()
    val eventModule = EventModule(coreModule, plugin)
    val guiModule = BukkitGuiModule(coreModule, apiLocalModule)
    val commandModule = CommandModule(coreModule, apiRemoteModule, guiModule, ...)

    val lifecycle = Lifecycle.Lambda(
        onEnable = { listOf(coreModule, eventModule, apiLocalModule, commandModule).forEach(Lifecycle::onEnable) },
        onDisable = { /* same list, reversed */ },
        onReload = { /* same list */ }
    )
}

This makes the plugin reloadable at runtime/atempreload walks the same chain in reverse and re-enables it, picking up any config or translation changes on the fly.

graph TD
    Plugin --> RootModule
    RootModule --> CoreModule
    RootModule --> ApiLocalModule
    RootModule --> ApiRemoteModule
    RootModule --> EventModule
    RootModule --> CommandModule
    EventModule --> TemplateEvent
    EventModule --> BetterAnotherEvent

Dependency injection

There is no DI framework. Each module is a plain class whose constructor receives other module interfaces it depends on. RootModule is the composition root and instantiates everything in the right order, using lazy {} where initialization must be deferred.

// Pass the whole module interface, not individual services extracted from it
val commandModule = CommandModule(
    coreModule = coreModule,
    guiModule = guiModule,
    apiRemoteModule = apiRemoteModule,
    ...
)

This keeps coupling explicit and avoids hidden runtime failures from missing bindings.


Cross-platform commands

Commands live in modules/feature-command — a plain Kotlin module with zero platform dependencies. They use the Brigadier DSL from AstraLibs, which abstracts over Paper's and Forge's native Brigadier adapters.

// Works on Paper, Forge, and NeoForge without any changes
command("rickandmorty") {
    literal("random") {
        runs { ctx ->
            scope.launch(dispatchers.IO) {
                rmApi.getRandomCharacter(Random.nextInt(0, 100))
                    .onSuccess { ctx.getSender().sendMessage(...) }
                    .onFailure { ctx.getSender().sendMessage(...) }
            }
        }
    }
    literal("specific") {
        argument("number", IntegerArgumentType.integer()) { numberArg ->
            runs { ctx -> send(ctx.getSender(), ctx.requireArgument(numberArg)) }
        }
    }
}

On each platform the RootModule provides a MultiplatformCommand backed by the right adapter (PaperMultiplatformCommands, MinecraftMultiplatformCommands). The shared command code never needs to change.

Available commands

Command Description
/add <player> <material> [amount] Add item to a player's inventory
/translation Show current translation value (useful after reload)
/adamage <player> <amount> Deal damage to a player
/atempgui Open the sample paginated GUI
/rickandmorty random Fetch a random Rick & Morty character via REST
/rickandmorty specific <id> Fetch a specific character by id
/atempreload Reload config, translations, and database connection

Configuration

Config and translations are plain @Serializable data classes serialized to YAML via kaml. Inline doc-comments render directly in the generated YAML file:

@Serializable
data class PluginConfiguration(
    @YamlComment("First line description for config1", "Second line description for config2")
    @SerialName("config_1")
    val config1: String = "NONE",

    @SerialName("database")
    val database: DatabaseConfiguration = DatabaseConfiguration.H2("db")
)

Both config and translations are stored as StateFlowKrate / CachedKrate. Any module that reads them always sees the latest value after a reload — no manual propagation needed.


Local database

modules/api/local uses Jetbrains Exposed as the ORM. The database connection is derived reactively from the config flow — when the config is reloaded with a new database URL, the connection is replaced automatically:

private val databaseFlow = configFlow
    .map { it.database }
    .distinctUntilChanged()
    .flatMapLatest { configuration -> configuration.connectAsFlow() }
    .onEach { db ->
        transaction(db) { SchemaUtils.create(UserRatingTable, UserTable) }
    }
    .shareIn(ioScope, SharingStarted.Eagerly, 1)

Supported drivers (swap in libs.versions.toml): H2, SQLite, MySQL, MariaDB.


Remote API

modules/api/remote shows how to call an external REST endpoint using Ktor. The interface is minimal:

interface RickMortyApi {
    suspend fun getRandomCharacter(id: Int): Result<RMResponse>
}

Errors are returned as Result<T> — never thrown — so callers handle failures explicitly.


GUI (Bukkit)

The GUI layer sits behind a Router interface defined in modules/feature-gui/api. The Bukkit implementation provides a paginated chest inventory with reactive state via Kotlin StateFlow:

  • SampleGuiComponent owns state (Loading / Items / Users)
  • SampleGUI observes state and re-renders on every emission
  • Navigation (next/prev page, change mode, add user, back/close) is handled by dedicated button objects

On Forge/NeoForge a StubGuiModule satisfies the GuiModule interface so the shared CommandModule compiles without a Bukkit dependency.


Building

# Paper plugin
./gradlew :instances:bukkit:shadowJar

# Forge mod
./gradlew :instances:forge:shadowJar

# NeoForge mod
./gradlew :instances:neoforge:shadowJar

# Run all tests
./gradlew allTests

Output jars land in each instance's build/libs/ directory and are optionally copied to a remote server by the FTP Gradle plugin (configure the destination in libs.versions.toml).


Test server (Docker)

docker-compose.yml at the project root starts a local test server using itzg/minecraft-server.

Before running, manually edit docker-compose.yml to uncomment the block for your target platform (Forge, NeoForge, or Paper) and comment out the others. Each block sets the TYPE, VERSION, and platform-specific version variables, and the matching volumes entry below it.

docker compose up

Available Versions

AstraTemplate-fabricrelease
MC 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21fabric
August 20, 2025
AstraTemplate-velocityrelease
MC 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21velocity
August 20, 2025
AstraTemplate-bukkitrelease
MC 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21bukkit
August 20, 2025
AstraTemplate-forgerelease
MC 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21forge
August 20, 2025
AstraTemplate-bukkitrelease
MC 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21bukkit
April 5, 2025

How to Install AstraTemplate on Your Server

1

Order Server

Order a Minecraft Java server with at least 3 GB RAM (4 GB recommended).

2

Set bukkit Loader

In the panel under "Egg", select the bukkit loader and matching Minecraft version (1.21.3).

3

Install Mod

Open the mod browser in the dashboard and search for "AstraTemplate". Click "Install" – done! Alternatively, upload the .jar via SFTP to the /mods folder.

Compatibility

Mod Loaders

bukkitfabricforgepaperpurpurspigotvelocity

Minecraft Versions

1.21.3, 1.21.2, 1.21.1 (+25 more)

Server-side

Unsupported

Recommended RAM

4 GB(min. 3 GB)

Frequently Asked Questions

AstraTemplate server crashes on startup – what to do?

Most common cause: wrong bukkit version or insufficient RAM. Check the server log (latest.log) for "OutOfMemoryError" or "Mixin" errors. With Mado Hosting: ensure at least 3 GB RAM is allocated and the loader matches the mod version (1.21.3). You can switch loaders with one click in the panel.

Is AstraTemplate compatible with bukkit and fabric and forge and paper and purpur and spigot and velocity?

AstraTemplate officially supports bukkit, fabric, forge, paper, purpur, spigot, velocity for Minecraft 1.21.3, 1.21.2, 1.21.1. Note: Forge and Fabric mods are NOT cross-compatible – pick one loader and stick with it. The Mado dashboard automatically detects incompatible loader combinations.

Server lagging with AstraTemplate – how to optimize performance?

Recommended RAM: 4 GB (per 8 players). Use /spark profiler to check if AstraTemplate consumes the most tick time. Common fixes: reduce server view-distance to 8-10, install "performant" or "starlight" as supplementary mods on Forge. With Mado Hosting, your server runs on NVMe SSDs with dedicated CPU cores for minimal latency.

Rent Modded Server

Install AstraTemplate with just one click on your server.

Recommended RAM
4 GBab €8/mo
Min. 3 GB | +1 GB pro 8 Spieler
Create Server Now
1-Click Mod Install
NVMe SSD Storage
DDoS Protection included

Details

License
MIT License
Server-side
Unsupported

Supported Versions

1.21.31.21.21.21.11.211.20.61.20.51.20.41.20.31.20.21.20.1+18 more