Contensio logo

Publishing to Packagist

Make your plugin installable via Composer, discoverable on Packagist, and releasable on GitHub.

Publishing a plugin is a one-time setup per package, then every release is an automatic GitHub → Packagist sync.

Naming convention

Contensio recommends prefixing all plugin package names with plugin-:

  • contensio/plugin-social-connect (official)
  • acme/plugin-awesome (community)

Themes use theme-:

  • contensio/theme-blogger

Why: Packagist search works better, ecosystem scanners can filter by prefix, and the contensio/* namespace stays clean.

Prerequisites

  • A GitHub repo for your plugin (public).
  • A Packagist account at packagist.org.
  • Your plugin's composer.json with:
    • A unique name ({vendor}/plugin-{slug})
    • Correct autoload PSR-4 mapping
    • extra.cms.type = plugin

One-time setup

1. Push your plugin to GitHub

Your repo root should be the plugin root (not a subdirectory). Composer clones from the repo root.

2. Submit to Packagist

Packagist scans your composer.json and creates a package page.

3. Set up the GitHub webhook (auto-sync)

On your GitHub repo → Settings → Webhooks → Add webhook:

  • Payload URL: https://packagist.org/api/github?username=YOUR_PACKAGIST_USERNAME
  • Secret: your Packagist API token (find it at packagist.org/profile/)
  • Content type: application/json
  • Events: Just push and create events

From now on, every push and every tag triggers Packagist to re-scan your repo.

Releasing a version

Every release is a Git tag + a GitHub release:

git tag 1.0.0
git push origin 1.0.0

Packagist picks it up within a minute. composer require acme/plugin-awesome:^1.0 now works.

GitHub releases (recommended)

For non-technical users who install via ZIP, create a GitHub release so they have a nice download:

  1. Go to your repo → Releases → Draft a new release
  2. Pick the tag you just pushed
  3. Fill in the release notes
  4. Publish

GitHub auto-generates a source ZIP. Users download it and upload in the admin's Plugins → Install Plugin.

Composer.json requirements

{
    "name": "acme/plugin-awesome",
    "description": "Makes things awesome.",
    "type": "library",
    "license": "MIT",
    "require": {
        "php": "^8.3",
        "contensio/contensio": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "Acme\\Awesome\\": "src/"
        }
    },
    "extra": {
        "cms": {
            "type":     "plugin",
            "provider": "Acme\\Awesome\\AwesomeServiceProvider"
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}

The extra.cms.type is the signal: Contensio's PluginRegistry scans vendor/ for packages with this marker.

Semantic versioning

Follow semver.org:

  • 1.0.0 — first stable
  • 1.0.1 — bug fix
  • 1.1.0 — new features, no breaking changes
  • 2.0.0 — breaking changes

Contensio users pin with ^1.0 in their own composer.json. Respecting semver keeps upgrades safe.

Pre-release tags

For RCs, betas, alphas:

  • 1.0.0-rc.1
  • 1.0.0-beta.2
  • 1.0.0-alpha.1

Mark the GitHub release as a "pre-release" so casual installers see the last stable by default.

License

If you license your plugin, keep it compatible with AGPL-3.0-or-later (Contensio's license). Common choices:

  • AGPL-3.0-or-later — same as Contensio
  • MIT — permissive, compatible
  • GPL-3.0-or-later — compatible

Questions

Can I charge for a plugin?
The plugin's license determines this, not Contensio. AGPL-compatible licenses allow commercial distribution but require source availability. If you want a closed-source paid plugin, you'd need a different arrangement with the Contensio project — open an issue to discuss.

Can I host a private plugin (not public)?
Yes. Use a private Composer repo (Satis, Private Packagist) or a GitHub repo behind auth. Contensio doesn't care where the package comes from.

How do I update my plugin's description on Packagist?
Edit your composer.json and push — Packagist resyncs automatically.