The ModrinthPlatform class implements the Platform interface to upload mod files to Modrinth using the Modrinth API v2.
Package
me.hsgamer.mcreleaser.modrinth.ModrinthPlatform
Class Declaration
public class ModrinthPlatform implements Platform {
private final Logger logger = LoggerFactory.getLogger(getClass());
}
Constructor
Creates a new Modrinth platform instance and inherits game version configuration from common properties if not explicitly set.
public ModrinthPlatform() {
if (ModrinthPropertyKey.GAME_VERSIONS.isAbsent() &&
CommonPropertyKey.GAME_VERSIONS.isPresent()) {
ModrinthPropertyKey.GAME_VERSIONS.setValue(
CommonPropertyKey.GAME_VERSIONS.getValue()
);
}
if (ModrinthPropertyKey.GAME_VERSION_TYPE.isAbsent() &&
CommonPropertyKey.GAME_VERSION_TYPE.isPresent()) {
ModrinthPropertyKey.GAME_VERSION_TYPE.setValue(
CommonPropertyKey.GAME_VERSION_TYPE.getValue()
);
}
}
Methods
createUploadRunnable
Creates a batch runnable that uploads files to Modrinth.
The bundle of files to upload as version files
Returns a BatchRunnable if all required properties are present, otherwise empty
@Override
public Optional<BatchRunnable> createUploadRunnable(FileBundle fileBundle) {
if (PropertyKeyUtil.isAbsentAndAnnounce(logger,
ModrinthPropertyKey.TOKEN,
ModrinthPropertyKey.PROJECT,
ModrinthPropertyKey.LOADERS,
ModrinthPropertyKey.GAME_VERSIONS)) {
return Optional.empty();
}
// Create upload tasks...
}
Upload Process
The upload is executed in two phases:
Phase 1: Preparation
-
Fetch Minecraft Versions
List<String> gameVersionFilters = Arrays.asList(
StringUtil.splitSpace(ModrinthPropertyKey.GAME_VERSIONS.getValue())
);
VersionTypeFilter gameVersionTypeFilter = VersionTypeFilter.RELEASE;
MinecraftVersionFetcher.fetchVersionIds(
gameVersionFilters,
gameVersionTypeFilter
).whenComplete((versionIds, throwable) -> {
// Use normalized version IDs
});
-
Build Request JSON
JsonObject request = new JsonObject();
request.addProperty("project_id", ModrinthPropertyKey.PROJECT.getValue());
request.addProperty("featured", ModrinthPropertyKey.FEATURED.asBoolean(true));
request.addProperty("name", CommonPropertyKey.NAME.getValue());
request.addProperty("changelog", CommonPropertyKey.DESCRIPTION.getValue());
request.addProperty("version_number", CommonPropertyKey.VERSION.getValue());
request.addProperty("version_type", ModrinthPropertyKey.VERSION_TYPE.getValue("release"));
request.add("loaders", gson.toJsonTree(loaders));
request.add("game_versions", gson.toJsonTree(gameVersions));
request.add("dependencies", dependencies);
request.add("file_parts", gson.toJsonTree(fileParts));
Phase 3: Upload
Uploads the version to Modrinth:
String url = baseUrl + "/v2/version";
HttpPost post = new HttpPost(url);
post.setHeader("Authorization", token);
post.setHeader("User-Agent", userAgent);
post.setHeader("Accept", "application/json");
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("data", gson.toJson(request), ContentType.APPLICATION_JSON);
for (File file : fileBundle.allFiles()) {
builder.addBinaryBody(file.getName(), file);
}
Required Properties
Modrinth project ID or slug
Space-separated list of mod loaders (e.g., “fabric forge paper”)
Space-separated Minecraft version patterns (e.g., “1.20 1.20.1”)
Optional Properties
Version type: “release”, “beta”, or “alpha”
MODRINTH_GAME_VERSION_TYPE
Filter for Minecraft versions: “RELEASE”, “SNAPSHOT”, or “ALL”
JSON array of dependencies (see examples below)
MODRINTH_ENDPOINT
String
default:"production"
API endpoint: “production” or “staging”
Common Properties
Version changelog (supports Markdown)
Configuration Examples
Basic Fabric Mod Release
export MODRINTH_TOKEN="mrp_1234567890abcdef"
export MODRINTH_PROJECT="my-mod"
export MODRINTH_LOADERS="fabric"
export MODRINTH_GAME_VERSIONS="1.20 1.20.1"
export NAME="My Mod v1.0.0"
export VERSION="1.0.0"
export DESCRIPTION="Initial release with cool features"
export FILES="build/libs/*.jar"
java -jar mcreleaser-cli.jar
Multi-Loader Support
export MODRINTH_LOADERS="fabric forge"
export MODRINTH_GAME_VERSIONS="1.19 1.20"
export MODRINTH_VERSION_TYPE="beta"
With Dependencies
export MODRINTH_DEPENDENCIES='[
{
"project_id": "fabric-api",
"dependency_type": "required"
},
{
"project_id": "cloth-config",
"dependency_type": "optional"
}
]'
Dependency types:
required - Must be installed
optional - Recommended but not required
incompatible - Conflicts with this version
embedded - Included in the mod file
Version Patterns
# Specific versions
export MODRINTH_GAME_VERSIONS="1.20.1 1.20.2"
# Version ranges (resolved automatically)
export MODRINTH_GAME_VERSIONS="1.20"
export MODRINTH_GAME_VERSION_TYPE="RELEASE"
# Include snapshots
export MODRINTH_GAME_VERSION_TYPE="ALL"
Staging Environment
export MODRINTH_ENDPOINT="staging"
# Uses https://staging-api.modrinth.com instead of production
Paper Plugin
export MODRINTH_LOADERS="paper spigot purpur"
export MODRINTH_GAME_VERSIONS="1.20 1.20.1 1.20.2"
Property Keys Source
Defined in ModrinthPropertyKey:
public interface ModrinthPropertyKey {
PropertyPrefix MODRINTH = new PropertyPrefix("modrinth");
PropertyKey TOKEN = MODRINTH.key("token");
PropertyKey PROJECT = MODRINTH.key("project");
PropertyKey VERSION_TYPE = MODRINTH.key("versionType");
PropertyKey GAME_VERSIONS = MODRINTH.key("gameVersions");
PropertyKey GAME_VERSION_TYPE = MODRINTH.key("gameVersionType");
PropertyKey FEATURED = MODRINTH.key("featured");
PropertyKey LOADERS = MODRINTH.key("loaders");
PropertyKey DEPENDENCIES = MODRINTH.key("dependencies");
PropertyKey ENDPOINT = MODRINTH.key("endpoint");
}
Supported Loaders
fabric - Fabric mod loader
forge - Forge mod loader
quilt - Quilt mod loader
neoforge - NeoForge mod loader
paper - Paper server
spigot - Spigot server
purpur - Purpur server
bukkit - Bukkit server
folia - Folia server
Version Type Values
release - Stable release
beta - Beta version
alpha - Alpha/development version
File Handling
Modrinth uploads all files from the FileBundle:
// Primary file is marked explicitly
if (fileBundle.primaryFile() != null) {
request.addProperty("primary_file", fileBundle.primaryFile().getName());
}
// All files are uploaded
for (File file : fileBundle.allFiles()) {
builder.addBinaryBody(file.getName(), file);
}
Error Handling
Invalid Version Type
try {
gameVersionTypeFilter = VersionTypeFilter.valueOf(
ModrinthPropertyKey.GAME_VERSION_TYPE.getValue().toUpperCase()
);
} catch (IllegalArgumentException e) {
logger.error("Invalid version type: {}", value, e);
process.complete();
}
Invalid Dependencies JSON
try {
JsonArray dependencies = gson.fromJson(
ModrinthPropertyKey.DEPENDENCIES.getValue(),
JsonArray.class
);
} catch (Exception e) {
logger.error("Invalid dependencies", e);
process.complete();
}
API Response
Successful upload logs the version ID:
JsonObject versionDto = client.execute(post, response -> {
if (response.getCode() != 200) {
throw new HttpException("Execute not successful. Got: %d %s",
response.getCode(), EntityUtils.toString(response.getEntity()));
}
String body = EntityUtils.toString(response.getEntity());
return gson.fromJson(body, JsonObject.class);
});
logger.info("Uploaded the version: {}", versionDto.get("id").getAsString());