Skip to content

The market

ShopClass browses, installs and updates plugins and themes from two public registries:

Both are ordinary GitHub repositories. Packages are submitted by pull request, validated by CI, and published as a static JSON catalog that every install reads. There is no marketplace server, no account, and no fee.

Shipped in ShopClass 6.1.0. From an install, it is Admin → Plugins → Add new and Admin → Appearance, or the market:* CLI commands.

A package either lives inside the registry repository or outside it in your own.

In-repo — your source tree goes in plugins/<slug>/ (or themes/<slug>/) alongside a small shopclass.json manifest:

{
"slug": "better-s3", // must equal the directory name
"type": "plugin",
"categories": ["storage", "media"], // from a fixed vocabulary
"tags": ["s3", "cdn", "offload"],
"short_description": "Offload uploaded images to any S3-compatible bucket.",
"icon": "assets/icon.svg",
"screenshots": [
{ "src": "assets/screenshot-1.png", "caption": "Bucket settings" }
],
"support": { "issues": "https://github.com/…/issues" },
"license": "GPL-3.0-or-later"
}

Note what it does not carry: a version. The builder reads the version from your index.php header and the release tag, so a package’s version has exactly one source of truth.

External — keep your code in your own repository and register a one-file pointer at external/<slug>.json:

{
"slug": "bender",
"type": "theme",
"source": { "kind": "github-release", "repo": "mindstellar/theme-bender" },
"asset_pattern": "^bender_.*\\.zip$",
"categories": ["general"],
"short_description": "The legacy Osclass theme, kept for compatibility."
}

The builder fetches your releases, picks the matching asset, reads the header block out of the zip — so name, version and compatibility come from the real artifact, never a hand-edited claim — computes its sha256, and emits an entry identical in shape to an in-repo one. Core never learns the difference.

Both storefront and bender are registered this way today.

CI validates only the package your PR changed, and checks that:

  • the header block parses, and its required fields are present
  • the slug matches the directory or manifest
  • compatibility metadata is sane
  • the package actually installs — a smoke install runs against a real core

Deprecated-function use is reported as a warning, never a failure. It tells you what to fix without blocking a release your users are waiting for.

On merge, CI builds the zip, tags it, cuts a GitHub Release, and rebuilds the catalog.

The catalog is static JSON on GitHub Pages, with a raw.githubusercontent.com mirror as a fallback. Core fetches it with a conditional GET about once a day and caches the result.

This is deliberate: core must never call api.github.com per installed package. Unauthenticated GitHub API allows 60 requests per hour per IP — a shared budget on shared hosting — and a site with fifteen plugins would exhaust it on a single update check. One cached catalog request answers for every package at once.

  • GPL-compatible licence. ShopClass is GPLv3; the ecosystem is too.
  • A working index.php header block — see the package specification.
  • A real support URL, so users have somewhere to go.

Commercial plugins are welcome to list a free or trial edition and sell elsewhere; what the catalog serves has to be installable and GPL-compatible.

docs/MARKET.md in the repository covers the catalog contract, the validation gate and the security posture in full.