GameAP plugins are published on plugins.gameap.dev — that is where users install them from, straight out of the panel. This guide walks through the whole path: from creating the plugin listing to shipping new versions automatically from CI.

It assumes your plugin is already written and builds into a .wasm. You will also need an account on plugins.gameap.dev: either a regular sign-up with email confirmation, or sign-in through GitHub, GitLab or Google.

There are two ways to upload a version: manually through the dashboard, or through the API from CI. Start with the dashboard, and switch to CI once your build settles.

Step 1. A developer account #

Publishing plugins requires an account with developer status. It is granted for free and immediately — all it takes is accepting the publication rules.

In the plugin marketplace press Publish your plugin — the button in the top-right corner.

The “Publish your plugin” button in the plugin marketplace
The plugin marketplace

The publication rules page opens. In short, what you agree to:

  • Working, quality plugins — only functional, tested ones that do what their description promises.
  • No malicious code — no backdoors, hidden miners or obfuscated logic; any collection of user data must be clearly disclosed.
  • Rights and licensing — you own the code or may distribute it, third-party licenses are respected, and your plugin’s license is stated.
  • GameAP compatibility — the plugin works with current panel releases and follows the plugin format requirements.
  • Updates and support — critical bugs and security issues get fixed within a reasonable time, and user reports get an answer.
  • Moderation — every plugin and every version is reviewed, and a plugin that breaks the rules can be removed at any time.
  • No spam or duplicates — no misleading names, no keyword stuffing.

The full text is on the page itself, and it is worth reading in full.

Press Agree at the bottom. The role is granted instantly, with no review and no waiting: a Developer section appears in the sidebar with Dashboard, My Plugins, New Plugin and Support, and an email arrives telling you that you are now a developer.

Note. The rules can only be accepted with a verified email: otherwise the Agree button stays disabled and the page shows a reminder to confirm your address. Accounts created through GitHub, GitLab or Google are considered verified right away.

The Publish your plugin button is visible to everyone, including visitors who are not logged in. Without a session the rules page shows Log in to agree instead — signing in brings you back to the rules.

Step 2. Creating the plugin #

Pick New Plugin in the left menu — the creation form opens.

The plugin creation form in the plugins.gameap.dev dashboard
Creating a new plugin
FieldRequiredWhat to put there
NameyesUp to 255 characters
SummaryyesA single sentence for the catalogue card, up to 500 characters
DescriptionyesFull description in Markdown — the field has a formatting toolbar
CategorynoOne of the three below
LabelsnoKeywords that make the plugin easier to find
LicensenoFree text: MIT, GPL-3.0, Proprietary
Source URLnoLink to the sources, if they are open
Repository URLnoLink to the repository
Homepage URLnoThe plugin’s or the author’s website

The categories:

  • Server Management — most plugins. Anything that adds actions on a game server.
  • Files — file manager extensions: editors, viewers for specific formats.
  • Integrations — plugins that substantially extend what the panel can do, database management for example.

The icon cannot be uploaded at this step — the form says so explicitly, the uploader appears right after creation.

Note. Source URL and Repository URL affect more than the listing. If neither is filled in, every version will have to ship a source archive — moderators need something to verify the build against. Fill in at least one of them if your sources are public.

Step 3. The plugin ID #

After you press Create you land on the plugin’s edit page. Its first block is the Plugin ID — a read-only field with a copy button.

This ID has to go into your plugin’s source code — the id field of the PluginInfo struct returned by GetInfo. It is how the panel matches an installed plugin with its marketplace entry.

The ID is generated once at creation and cannot be changed. Under the hood it is a 64-bit number (8 random bytes) written in base32 — 13 latin characters and digits, for example fmqnme42gg7da. The same ID appears in the plugin page URL and in the CI endpoint address.

How it looks in existing plugins:

Note. Write the ID in and rebuild the .wasm before uploading the first version. Versions are immutable: you cannot re-upload a file under the same number, you would have to bump it.

Step 4. Icon and translations #

The same edit page has an icon uploader: JPG, PNG, WebP or SVG, up to 2 MB, 128×128 recommended.

The plugin page has a Translations block. The catalogue is bilingual and every visitor sees the listing in their own language: without a translation a Russian user gets the English description and vice versa. Name, summary and description are translated separately per language — five minutes of work that noticeably change how the listing reads.

Step 5. Uploading a version #

On the plugin page find the Versions card and press Upload Version.

The plugin version upload form
Uploading a new version
FieldRequiredWhat to put there
VersionyesSemantic version: 1.0.0, 1.2.0-beta.1
Plugin FileyesThe compiled .wasm, up to 100 MB
GPG SignaturenoDetached .sig or .asc signature, up to 1 MB
Source Code Archivedepends.zip or .tar.gz, up to 50 MB
ChangelognoWhat changed since the previous version, in Markdown
Stable ReleaseA switch, on by default
Min GameAP VersionnoThe panel version your plugin starts working from

The GPG signature is optional but welcome: the server stores it as is and never verifies it — it exists so that users can check the file’s authenticity themselves.

The source code archive is required when the plugin has neither Source URL nor Repository URL filled in — the form flips the field’s label from “(Optional)” to “(Required)” on its own. The archive is used only for moderation and build verification; it is never published and is not reachable through any public endpoint.

Note. A version uploaded through the dashboard stays a draft. Until you submit it for review, nobody sees it.

Version screenshots #

Right after the upload a second step opens — Add Screenshots. Screenshots belong to a specific version: JPG, PNG or WebP, up to 10 of them, 5 MB each. The step can be skipped and picked up later from the version page.

Step 6. Submitting for review #

Plugins go through moderation — against the very publication rules you accepted in step 1. The plugin page header has a Submit for Review button — it is only visible while the plugin is in the Draft or Rejected status.

A plugin with no versions cannot be submitted — upload a version first. Submitting a version for review from the versions table also moves the plugin itself from Draft to Pending Review, so the header button is usually not needed separately.

StatusWhat it meansWhat you can do
DraftCreated, not submitted anywhereEdit, submit for review, delete
Pending ReviewWaiting for a moderatorWait
RejectedA moderator sent it backFix it and submit again
ReleasedAvailable to everyone in the catalogueUpload new versions
ApprovedAn intermediate status in the data model
DeprecatedMarked as out of dateStill installable, but flagged
RetractedWithdrawn from publicationNot available for installation

Approval moves the plugin and the version straight to Released. Only objects in that status are publicly visible: if a plugin is released but its only version is still pending review, there is nothing to install.

The rejection reason arrives by email — the plugin page only shows the status, so check your inbox.

Deploy tokens #

To publish versions from CI you need a deploy token. It lives at the very bottom of the plugin page, in the Deploy Tokens card.

The “Deploy Tokens” card at the bottom of the plugin page
The deploy tokens card

Press Create Token and fill in the dialog: a name (GitHub Actions, for example) and, optionally, an expiry date.

The deploy token creation dialog with the “Name” and “Expires At” fields
Creating a deploy token

Note. The token is shown exactly once, right after creation. Only its hash is kept on the server and the value cannot be recovered — copy it straight into your CI secrets.

Worth knowing about tokens:

  • A token is bound to one plugin and can only upload versions of it. It opens neither other plugins nor the rest of the API.
  • The format is gapd_ plus 43 characters, 48 in total. The prefix is easy for secret scanners to catch.
  • The table shows the token prefix, the creation date, the last use and the expiry — handy for working out which token is still in use.
  • Revocation is immediate: a pipeline with a revoked token breaks on its next run.
  • A single plugin can hold up to 20 tokens.

Publishing from CI #

The CI endpoint accepts a new version without an interactive login — it authenticates with a deploy token.

Uploading with curl #

curl --fail-with-body -sS \
  -H "Authorization: Bearer $GAMEAP_DEPLOY_TOKEN" \
  -F "version=1.2.3" \
  -F "file=@build/plugin.wasm" \
  -F "signature=@build/plugin.wasm.asc" \
  -F "changelog=Fixed a crash on server restart" \
  -F "min_gameap_version=4.1.0" \
  -F "is_stable=true" \
  "https://plugins.gameap.dev/api/ci/plugins/$GAMEAP_PLUGIN_ID/versions"

The request is a POST with multipart/form-data. The form fields:

FieldRequiredDescription
versionyesSemantic version, e.g. 1.2.3
fileyesThe compiled .wasm, up to 100 MB
signaturenoDetached GPG signature, up to 1 MB
sourcedependsSource archive .zip/.tar.gz up to 50 MB; required when the plugin has neither Source URL nor Repository URL
changelognoRelease notes; convenient to pass from a file: -F "changelog=<CHANGELOG.md"
min_gameap_versionnoMinimum GameAP version
min_plugin_api_versionnoMinimum Plugin API version (the dashboard form has no such field)
is_stablenotrue or 1 marks the version as stable
submitnoDefaults to true; false keeps the version a draft

Do not put a trailing slash on the URL. The router answers .../versions/ with a 301, and curl does not replay the POST body on a redirect — the upload would silently turn into a GET.

A successful response is a 201 with a body like:

{
  "id": 123,
  "plugin_id": "fmqnme42gg7da",
  "version": "1.2.3",
  "file_size": 1048576,
  "file_hash": "9f86d081884c7d65...",
  "has_source": false,
  "status": "pending_review"
}

GitHub Actions #

This workflow fires when a GitHub release is published, builds the plugin, signs it and ships it to plugins.gameap.dev. The release body becomes the changelog, and the version is marked stable only when the release is not a pre-release.

name: Publish plugin

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build plugin
        run: make wasm            # must produce build/plugin.wasm

      - name: Sign plugin
        env:
          GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
        run: |
          printf '%s' "$GPG_SIGNING_KEY" | gpg --batch --import
          gpg --batch --yes --detach-sign --armor \
            -o build/plugin.wasm.asc build/plugin.wasm

      - name: Publish to plugins.gameap.dev
        env:
          GAMEAP_DEPLOY_TOKEN: ${{ secrets.GAMEAP_DEPLOY_TOKEN }}
          GAMEAP_PLUGIN_ID: ${{ vars.GAMEAP_PLUGIN_ID }}
          TAG: ${{ github.event.release.tag_name }}
          RELEASE_BODY: ${{ github.event.release.body }}
          PRERELEASE: ${{ github.event.release.prerelease }}
        run: |
          VERSION="${TAG#v}"
          printf '%s' "$RELEASE_BODY" > "$RUNNER_TEMP/changelog.md"
          ARGS=(--fail-with-body -sS
            -H "Authorization: Bearer ${GAMEAP_DEPLOY_TOKEN}"
            -F "version=${VERSION}"
            -F "file=@build/plugin.wasm"
            -F "signature=@build/plugin.wasm.asc"
            -F "changelog=<${RUNNER_TEMP}/changelog.md")
          if [ "$PRERELEASE" != "true" ]; then
            ARGS+=(-F "is_stable=true")
          fi
          curl "${ARGS[@]}" \
            "https://plugins.gameap.dev/api/ci/plugins/${GAMEAP_PLUGIN_ID}/versions"

VERSION="${TAG#v}" strips the leading v so that tag v1.2.3 publishes as version 1.2.3.

The production version of this workflow, with a frontend build, a Cargo cache and an HTTP status check, lives in plugin-mysql.

Configuring the GitHub repository #

The token and the plugin ID must not sit in the code — they belong in the repository settings. Open Settings → Secrets and variables (under Security and quality) → Actions.

The “Secrets and variables → Actions” item in the GitHub repository settings sidebar
The repository settings section

On the Secrets tab press New repository secret and add:

NameValue
GAMEAP_DEPLOY_TOKENThe deploy token you copied at creation time (it starts with gapd_)
GPG_SIGNING_KEYAn ASCII-armored private GPG key, if you sign your builds
The Secrets tab with the GAMEAP_DEPLOY_TOKEN and GPG_SIGNING_KEY secrets
Repository secrets

On the Variables tab press New repository variable and add:

NameValue
GAMEAP_PLUGIN_IDThe plugin ID, e.g. fmqnme42gg7da
The Variables tab with the GAMEAP_PLUGIN_ID variable
Repository variables

The plugin ID is the same base32 identifier you wrote into PluginInfo. Copy it with the button next to the Plugin ID field on the edit page, or from the Details block on the plugin page.

The ID goes into variables rather than secrets on purpose: it is not a secret, and having it visible in the CI logs makes failures far easier to diagnose. The token, on the other hand, is always a secret.

Here is how to export a private GPG key for GPG_SIGNING_KEY:

gpg --armor --export-secret-keys YOUR_KEY_ID

Publish the public key somewhere users can find it — in the plugin’s README, for instance — so that the signature is verifiable.

Dashboard and CI: different defaults #

WhatDashboardCI
Stable releaseThe switch is on by defaultis_stable defaults to false — pass it explicitly
Submitting for reviewManual, by pressing a buttonsubmit defaults to true — the version goes to review at once
ScreenshotsOffered right after the uploadNot supported, add them through the dashboard

If all you want from CI is to put the file up and finish the metadata by hand, pass submit=false and the version stays a draft.

Errors #

CodeCause
400Invalid plugin ID, a malformed multipart body, or no file passed
401The token is missing, invalid or expired; the header is not in the Bearer gapd_... form
403The token is valid but was issued for a different plugin
409A version with that number already exists
422The version fails the semver check; the source archive is missing, too large or not a .zip/.tar.gz; the file or signature size limits are exceeded

Error bodies come in a common shape:

{
  "status": "error",
  "error": "version already exists",
  "message": "version already exists",
  "http_code": 409
}

After a 5xx error, do not blindly retry the upload. Writing the version and submitting it for review are not a single transaction: the version may already exist, and a retry returns 409. Open the dashboard and check what actually happened.

Updating and withdrawing versions #

A new version is uploaded exactly like the first one — through the dashboard or from CI. Versions already uploaded are immutable: re-uploading a file under the same number is not possible, the attempt returns 409. If the build was wrong, bump the patch version.

Every row in the versions table has its own actions:

  • Edit — change the changelog and the metadata.
  • Submit for Review — for drafts and rejected versions.
  • Deprecate — for released versions. The version stays available but is flagged as out of date.
  • Retract — for released and deprecated versions. The version is withdrawn from publication.

Only a plugin in the Draft status can be deleted. A released plugin cannot be removed — it can be retracted.

Checklist #

  • The email is verified, the publication rules are accepted, and the account has developer status.
  • The plugin is created, and the name and both descriptions are filled in.
  • Source URL or Repository URL is filled in — otherwise every version needs a source archive.
  • The plugin ID is written into PluginInfo.id and the .wasm was rebuilt afterwards.
  • The icon is uploaded and the listing translations are in place.
  • The version is uploaded, with a signature and screenshots attached.
  • The version is submitted for review, not left as a draft.
  • For CI: a deploy token is created and stored in the secrets, the plugin ID is in the variables.
  • The workflow has no trailing slash on the URL and passes is_stable explicitly.
  • After the release, the plugin and the version are confirmed to be in the Released status.