WP Manifestindependent plugin directory
manifest / updater

The WP Manifest updater

Dashboard → Updates from your own forge. One JSON file, one release zip, a short class — no WordPress.org required.

Most plugin authors think Dashboard → Updates only works if you are on WordPress.org. It does not. A manifest.json on your default branch, a release zip on the forge, and a short updater class are enough for WordPress to offer updates from your source — the same screen, no middleman, no license server.

This is a convention, not a required library. Copy the pattern, keep the “WP Manifest updater” branding if you like, change what you need. Used today by Disembark, Minn Admin, CaptainCore Manager, WP Freighter, WP Shipyard, and a handful of other listed plugins. The 2023 walkthrough is the origin story; this page is the current guideline.

The three things that matter

  1. Publish a manifest.json on the default branch (main or master). Point the updater at the raw URL. Sites check that file, not the release API.
  2. Attach an installable plugin zip to each release. Name it like the plugin folder (your-plugin.zip). Source tarballs from “Download ZIP” break installs and do not count as downloads here.
  3. Cache the check. Honour a transient (an hour is plenty; a day is fine). Flush it after a successful update. Do not hit the forge on every admin page load.

Everything else on this page is optional hardening or niceties.

Ask an AI agent

If you already work with Cursor, Claude, Copilot, or similar, you do not have to wire this by hand. Paste one of these into the agent with your plugin repo open. Point it at a real reference implementation and let it adapt the files.

Baseline — same shape as Disembark

Wire up plugin updates for this plugin the same way Austin Ginder’s Disembark plugin does.

Reference: https://github.com/DisembarkHost/disembark
- app/Updater.php
- manifest.json on the default branch
- release zip attached to GitHub Releases (not the auto-generated source archive)

Requirements:
1. Add a root manifest.json with name, slug, version, download_url, requires, tested, requires_php, homepage, and a short sections.description.
2. Add a small Updater class that filters plugins_api and site_transient_update_plugins, caches the remote manifest in a transient, falls back to the local manifest.json if GitHub is unreachable, and purges the cache after a successful update.
3. Point the remote check at the raw manifest.json on this repo’s default branch.
4. Document the release steps: bump Version, build an installable {slug}.zip, attach it to the GitHub release, update manifest.json version + download_url, push the default branch.
5. Do not add Composer packages or Plugin Update Checker unless I ask. Keep it a short class I own.

With translations — same shape as Minn Admin

Set up this plugin to update from GitHub, including translations, like Minn Admin.

Reference: https://github.com/austinginder/minn-admin
- includes/class-minn-admin-updater.php
- manifest.json (including translations[] and sha256)
- language packs as separate release assets
- docs/i18n-roadmap.md for the delivery model

Requirements:
1. Manifest-based plugin updates from the raw manifest.json on the default branch, with a cached transient and local fallback.
2. Allowlist package URLs (https, this repo’s /releases/download/ path) and require sha256 verification on download.
3. Offer language packs through $transient->translations from manifest.translations[], only for locales the site actually uses.
4. Each language-pack zip should include .po, .mo, and .l10n.php (plus JS JSON if we enqueue scripts), installed to wp-content/languages/plugins/.
5. Stamp sha256 for the plugin zip and each pack into manifest.json as part of the release process; do not trust the copy of the manifest bundled inside the plugin zip for verification.
6. Keep the implementation in-repo (no new SaaS). Match Minn’s behaviour, adapted to this plugin’s slug and namespace.

Swap the reference URLs if you prefer CaptainCore Manager or WP Shipyard as the template. The agent should read those files and rename the class, slug, and cache key for your plugin — not invent a parallel update system.

What goes in manifest.json

Minimum fields the updater needs:

{
  "name": "Your Plugin",
  "slug": "your-plugin",
  "author": "<a href=\"https://example.com\">You</a>",
  "version": "1.2.0",
  "download_url": "https://github.com/you/your-plugin/releases/download/v1.2.0/your-plugin.zip",
  "requires": "6.0",
  "tested": "6.9",
  "requires_php": "7.4",
  "homepage": "https://example.com",
  "sections": {
    "description": "One or two sentences for the plugin details modal."
  }
}

Useful extras:

Keep version and download_url in lockstep with the release you just cut. Stamp sha256 after the zip exists; the copy of manifest.json inside the zip cannot honestly contain that zip’s own hash, so verification always reads the remote file on the default branch.

Where the updater looks

Pin a single HTTPS URL to the raw file on the default branch, for example:

https://raw.githubusercontent.com/you/your-plugin/main/manifest.json

That is the source of truth for “is there a newer version?” The release asset is only the package. Serving the same JSON from your marketing site also works (WP Freighter started that way); the default-branch file is simpler and is what WP Manifest’s crawler already understands as a manifest updater.

Ship a copy of manifest.json inside the plugin too. When GitHub is unreachable, fall back to the local file so the details modal and version display still work. Never use the bundled copy as the authority for sha256 verification.

Caching

WordPress reads site_transient_update_plugins often — admin screens, cron, the admin-bar nag. Without a cache, every one of those becomes a live request to your forge.

The release zip

GitHub’s “Source code (zip)” links use different folder names and inflate download stats that are not plugin installs. WP Manifest only counts .zip release assets toward downloads and the active-site estimate.

What the PHP side does

Three hooks are enough:

  1. plugins_api — when WordPress asks for plugin_information about your slug, return name, version, sections, and download_link from the manifest.
  2. site_transient_update_plugins — if the remote version is newer than the installed constant, put a response object in $transient->response with package set to download_url.
  3. upgrader_process_complete — purge your cache.

That is the whole “library”: a short class you can paste and rename. Reference implementations live in Disembark (app/Updater.php), Minn Admin (includes/class-minn-admin-updater.php), and CaptainCore Manager.

Hardening worth stealing

From the newer plugins (Minn Admin, CaptainCore Manager):

Translations (optional, but native)

You do not need translate.wordpress.org to offer language packs. Put them on the same release and list them in the manifest:

"translations": [
  {
    "language": "de_DE",
    "version": "1.2.0",
    "updated": "2026-09-11 12:00:00",
    "package": "https://github.com/you/your-plugin/releases/download/v1.2.0/your-plugin-de_DE.zip",
    "sha256": "…"
  }
]

Then, in the same site_transient_update_plugins filter, append matching entries to $transient->translations. WordPress installs them into wp-content/languages/plugins/ and refreshes them after a plugin update on its own.

Tips that saved real pain on Minn Admin:

Release checklist

  1. Bump Version: in the main plugin file (and any mirrored constant).
  2. Build your-plugin.zip with the correct root folder.
  3. Create the forge release (v1.2.0) and attach the zip (plus any language-pack zips).
  4. Compute sha256 of the zip(s).
  5. Update manifest.json on the default branch: version, download_url, last_updated, sha256, and translations if you ship packs.
  6. Commit and push the branch. Sites pick it up on the next cached check.

Automate steps 4–5 in release scripts when the project grows; Minn Admin’s bin/release-manifest.js is one example of stamping hashes and translation entries without hand-editing JSON.

How this shows up on WP Manifest

You do not need to register anything with WP Manifest for updates to work. The directory indexes what you already ship; the updater talks only to your forge.

Related options

The Manifest updater is for authors who want Dashboard → Updates without a middleman and without a large dependency: one JSON file, one zip, a short class.

Examples

Questions or a listing that should credit a Manifest updater and does not: feedback@wpmanifest.xyz.