When I first started building the MoeKoe Music plugin ecosystem, there was no online plugin marketplace feature yet. Later, based on community suggestions, the plugin marketplace came into being [Add official/community plugin repositories to extend product functionality ]

If plugins can be developed by the community, how should the plugin marketplace be managed?

The most straightforward approach would be to pull all plugin source code into a single monorepo. But the more I thought about it, the more awkward it felt. Each plugin has its own author, its own release cadence, its own build process. Stuffing them all into the official repo would not only drive up maintenance costs but also blur the lines of responsibility.

So this repository was ultimately not designed as a “plugin source code repository” but rather as a “plugin registration and indexing repository.” It only does a few things:

A traceable logbook: who submitted it, which version was reviewed, what the download URL is, whether it has network or file permissions — all captured in a clear set of data.

A Marketplace Index, Not a Source Repository

The most important file in the repository root is plugins.json. It is the actual data source consumed by the plugin marketplace.

{
  "id": "custom-app-background",
  "name": "Custom Background Image",
  "description": "Provides custom background image capability for MoeKoe Music, with transparency support.",
  "iconUrl": "...",
  "version": "1.0.0",
  "minversion": "1.6.1",
  "...":"....",
  "buildRequired": false,
  "networkAccess": false,
  "fileAccess": false,
  "binaryContent": false,
  "snapshot": {
    "iconUrl": "...",
    "repository": "MoeKoeMusic/custom-app-background-plugin",
    "commitSha": "dbf72d38c8cf6b1d1cefdf8ce15798d565678995",
    "downloadUrl": "hxx.zip",
    "release": null
  }
}

I didn’t want the marketplace record to always point to the “current latest code” of the plugin repository. Because the code an author commits today may not be the same as what was seen on the day of review. What the plugin marketplace should truly recognize is: the version that passed review.

So there is a core principle in this project:

What gets listed is not a drifting repository address, but a locked snapshot.

For plugins that don’t need compilation, the snapshot is pinned to the commit on the default branch at that time. For plugins that require building, the snapshot is pinned to the corresponding Release tag and its assets. This way, if problems arise later, you can look back and know exactly what was reviewed.

Actions Run First, Humans Make the Final Call

When a user submits an Issue, they need to select:

The Action performs automatic validation, then writes the result back as a comment on the Issue, and labels the Issue with check-passed or check-failed.

Automation is responsible for organizing the facts; humans are responsible for making the final judgment:

  1. User submits a plugin using the Issue template
  2. Action parses the Issue form
  3. Validates repository, manifest, version, author permissions
  4. Locks the plugin snapshot
  5. Automatically comments with validation results
  6. Maintainer performs manual review
  7. Maintainer closes the Issue with Close as completed
  8. Another Action reads the validation result and generates a PR to update plugins.json and README.md
  9. After the PR is merged, the plugin officially enters the marketplace index

The “close method” is also leveraged here. If the Issue is closed as Close as not planned, the script will not execute the listing. In other words, GitHub’s native Issue status is used as the review button.

Snapshot Mechanism

If the user says “no building required before installation”:

If the user says “building is required”:

These two paths solve the same problem: regardless of how the plugin source publishes, the end result must be a snapshot that is reviewable, downloadable, and traceable — one that won’t change just because the latest repository changes.

Permission Identification

Security review can’t rely solely on scripts making judgment calls. But scripts can help maintainers flag risk points first:

It doesn’t just say “risky” — it tells you why it determined the capability exists, for example:

This is certainly not a perfect security scan. It may miss things or produce false positives. But its role is not to replace manual review — it’s to highlight the places maintainers should look at more closely.

It’s not a “judge”; it’s more like a “highlighter.”

Hidden Snapshot Data

After validation completes, a comment is posted on the Issue. In addition to human-readable validation results, the comment contains a hidden payload.

It generates an HTML comment like this:

<!-- plugin-publish-snapshot:base64... -->

This is a clever design. Because different GitHub Actions workflows don’t always share memory. Listing validation happens when the Issue is created or edited; actual listing happens when the Issue is closed. Hours or even days may pass between the two.

So how does the closing action know which snapshot originally passed validation?

The answer is: read it back from the Issue comment.

This turns the Issue comment into more than just a “notification” — it becomes a lightweight review record. It needs no extra database or service; it simply borrows GitHub’s own record-keeping ability.

Listing Action

The actual script that writes to plugins.json is scripts/publish-plugin-close.js.

It sets up several gates first:

These gates ensure one thing: ordinary users cannot write plugins into the marketplace by closing Issues or forging the process.

There’s a detail here: when updating a plugin, the original author is preserved — submitting an update Issue won’t change the author field. At the same time, updates must be submitted by the original author themselves, and the repository address must match the existing record.

New or updated plugins are placed at the top of the list. This choice also aligns with plugin marketplace intuition: the most recently reviewed content is shown first, making it easier for users and maintainers to see the latest changes.

Finally, a Markdown table is generated from the current plugins.json to replace the plugin list in the README.

Delisting and Reporting

The process is very similar to listing:

  1. User creates a delisting or report Issue
  2. plugin-moderation-validate.js checks whether the plugin exists
  3. If the author is voluntarily delisting, verify the submitter is the author
  4. Maintainer performs manual review
  5. Maintainer closes with Close as completed
  6. plugin-moderation-close.js changes the plugin status to delisted
  7. A PR is automatically generated to update plugins.json and the README

AI Review

The project also has a publish-plugin-ai-audit.js that performs an AI static audit on the plugin snapshot.

The script’s approach is:

What I think works well here: the AI review is not designed as the “final judge.” It simply provides maintainers with an additional reference.

This is far more reliable than “AI says it’s safe so auto-list it.”

Why Not Use a Database or Backend Service?

The most interesting thing about this project isn’t how complex the code is, but rather how it avoids introducing extra systems.

It makes full use of what GitHub provides out of the box:

So it doesn’t need a backend, doesn’t need a database, and doesn’t need a separate admin panel. For an open-source music player’s plugin marketplace, this level of complexity is just right.

ps: GitHub Actions is really great to work with, including my previous AJue’s Blog Internationalization Journey

Ending

Looking back at this project, it’s not a “heavy architecture” plugin platform. It’s more like a plainly dressed registration system where every step is traceable.

Users submit via Issues, Actions auto-check, maintainers manually confirm, and PR merges take effect. All key actions stay on GitHub, and all marketplace data lives in plugins.json. When there’s a problem, you can investigate; when there’s an update, there’s a record; when something is delisted, history isn’t erased.

It didn’t try to build a complete backend all at once — instead, it built the core trust chain that a plugin marketplace needs first.

The first real thing a community plugin ecosystem needs may not be a flashy interface, but a process that everyone can understand, trust, review, and continue to improve.

This repository MoeKoeMusic-Plugins came to be exactly this way.

Plugin Marketplace Web Version

Of course, none of the above was achieved overnight — it was built and refined step by step into what it is today.

Copyright Notice

Author: MoeJue

Link: https://ja.moejue.cn/en/posts/334/

License: クリエイティブ・コモンズ表示-非営利-継承4.0国際ライセンス

この作品は、クリエイティブ・コモンズ表示-非営利-継承4.0国際ライセンスに基づいてライセンスされています。

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut