Skip to main content
The PolymartPlatform class implements the Platform interface to upload plugin files to Polymart marketplace using their two-step upload API.

Package

me.hsgamer.mcreleaser.polymart.PolymartPlatform

Class Declaration

public class PolymartPlatform implements Platform {
    private final Logger logger = LoggerFactory.getLogger(getClass());
}

Methods

createUploadRunnable

Creates a batch runnable that uploads files to Polymart.
fileBundle
FileBundle
required
The bundle of files to upload (only primary file is used)
runnable
Optional<BatchRunnable>
Returns a BatchRunnable if all required properties are present, otherwise empty
@Override
public Optional<BatchRunnable> createUploadRunnable(FileBundle fileBundle) {
    if (PropertyKeyUtil.isAbsentAndAnnounce(logger, 
        PolymartPropertyKey.KEY, 
        PolymartPropertyKey.RESOURCE)) {
        return Optional.empty();
    }
    // Create upload tasks...
}

Upload Process

Polymart uses a two-step upload process executed in three phases:

Phase 0: Client Initialization

CloseableHttpClient client = HttpClients.createMinimal();

Phase 1: Update Preparation

Prepares the update metadata and converts Markdown to HTML:
Map<String, String> jsonBody = new HashMap<>();
jsonBody.put("api_key", PolymartPropertyKey.KEY.getValue());
jsonBody.put("product", PolymartPropertyKey.RESOURCE.getValue());
jsonBody.put("version", CommonPropertyKey.VERSION.getValue());
jsonBody.put("file_name", fileBundle.primaryFile().getName());
jsonBody.put("tag", PolymartPropertyKey.TAG.getValue("release"));

if (CommonPropertyKey.NAME.getValue() != null) {
    jsonBody.put("update_title", CommonPropertyKey.NAME.getValue());
}

String description = CommonPropertyKey.DESCRIPTION.getValue();
String htmlDescription = MarkdownToHTMLConverter.convert(description);
jsonBody.put("update_description", htmlDescription);

Phase 2: Two-Step Upload

Step 1: Post Update
HttpPost request = new HttpPost("https://api.polymart.org/v1/doPostUpdate");
request.setEntity(new StringEntity(gson.toJson(jsonBody), ContentType.APPLICATION_JSON));

JsonObject responseNode = client.execute(request, response -> {
    JsonObject responseBody = JsonParser.parseString(
        EntityUtils.toString(response.getEntity())
    ).getAsJsonObject();
    JsonObject uploadNode = responseBody.getAsJsonObject("response")
                                        .getAsJsonObject("upload");
    
    String uploadUrl = uploadNode.get("url").getAsString();
    JsonObject fieldsNode = uploadNode.getAsJsonObject("fields");
    
    return Map.of("uploadUrl", uploadUrl, "uploadFields", fieldsNode);
});
Step 2: Upload File
MultipartEntityBuilder builder = MultipartEntityBuilder.create();

// Add all fields from the response
fieldsNode.entrySet().forEach(entry -> {
    builder.addTextBody(entry.getKey(), entry.getValue().getAsString());
});

// Add the file
builder.addBinaryBody("file", fileBundle.primaryFile());

HttpPost uploadRequest = new HttpPost(uploadUrl);
uploadRequest.setEntity(builder.build());

Required Properties

POLYMART_KEY
String
required
Polymart API key
POLYMART_RESOURCE
String
required
Polymart resource ID

Optional Properties

POLYMART_TAG
String
default:"release"
Update tag/type: “release”, “beta”, or “alpha”

Common Properties

NAME
String
Update title (optional)
VERSION
String
required
Version number
DESCRIPTION
String
required
Update description (Markdown, automatically converted to HTML)

Configuration Examples

Basic Plugin Update

export POLYMART_KEY="your-polymart-api-key"
export POLYMART_RESOURCE="12345"

export NAME="MyPlugin v1.0.0"
export VERSION="1.0.0"
export DESCRIPTION="## Features\n\n- New feature 1\n- Bug fix 2"
export FILES="build/libs/my-plugin.jar"

java -jar mcreleaser-cli.jar

Beta Release

export POLYMART_TAG="beta"
export VERSION="2.0.0-beta.1"
export DESCRIPTION="Beta release for testing"

Alpha Release

export POLYMART_TAG="alpha"
export VERSION="3.0.0-alpha.1"

Without Update Title

# NAME is optional - can be omitted
export VERSION="1.5.0"
export DESCRIPTION="Minor update"

Property Keys Source

Defined in PolymartPropertyKey:
public interface PolymartPropertyKey {
    PropertyPrefix POLYMART = new PropertyPrefix("polymart");
    PropertyKey KEY = POLYMART.key("key");
    PropertyKey RESOURCE = POLYMART.key("resource");
    PropertyKey TAG = POLYMART.key("tag");
}
Property Name Mapping:
  • Property: polymartKey → Environment: POLYMART_KEY
  • Property: polymartResource → Environment: POLYMART_RESOURCE
  • Property: polymartTag → Environment: POLYMART_TAG

Tag Values

  • release - Stable release (default)
  • beta - Beta version
  • alpha - Alpha/development version

Markdown to HTML Conversion

Polymart requires HTML descriptions. MCReleaser automatically converts Markdown:
String description = CommonPropertyKey.DESCRIPTION.getValue();
String htmlDescription = MarkdownToHTMLConverter.convert(description);
jsonBody.put("update_description", htmlDescription);
Example Conversion:
## New Features

- Feature 1
- Feature 2

**Important:** Breaking changes!
Converts to:
<h2>New Features</h2>
<ul>
<li>Feature 1</li>
<li>Feature 2</li>
</ul>
<p><strong>Important:</strong> Breaking changes!</p>

File Handling

Important: Polymart only uploads the primary file from the FileBundle:
jsonBody.put("file_name", fileBundle.primaryFile().getName());
// ...
builder.addBinaryBody("file", fileBundle.primaryFile());
Secondary files are ignored.

Two-Step Upload Flow

  1. doPostUpdate: Registers the update and gets upload URL
    • Returns signed upload URL
    • Returns form fields required for upload
  2. File Upload: Uploads file to the signed URL
    • Uses multipart form data
    • Includes all fields from step 1
    • Adds the file

Error Handling

Post Update Failure

if (response.getCode() != 200) {
    String responseBody = EntityUtils.toString(response.getEntity());
    logger.error("Failed to post update: {} - {}", 
        response.getCode(), responseBody);
    return false;
}

Markdown Conversion Failure

try {
    String htmlDescription = MarkdownToHTMLConverter.convert(description);
    jsonBody.put("update_description", htmlDescription);
} catch (Exception e) {
    logger.error("Failed to convert description", e);
    process.complete();
}

Upload Failure

if (response.getCode() != 200 && response.getCode() != 204) {
    String responseBody = EntityUtils.toString(response.getEntity());
    logger.error("Failed to upload file: {} - {}", 
        response.getCode(), responseBody);
    return false;
}

API Endpoints

Post Update

POST https://api.polymart.org/v1/doPostUpdate
Content-Type: application/json
Request Body:
{
  "api_key": "your-api-key",
  "product": "12345",
  "version": "1.0.0",
  "file_name": "my-plugin.jar",
  "tag": "release",
  "update_title": "MyPlugin v1.0.0",
  "update_description": "<p>HTML description</p>"
}
Response:
{
  "response": {
    "upload": {
      "url": "https://upload.url",
      "fields": {
        "key": "value",
        // ... other fields
      }
    }
  }
}

File Upload

POST <url from previous response>
Content-Type: multipart/form-data
Includes all fields from the upload response plus the file.

Build docs developers (and LLMs) love