From your laptop to a customer page
You never publish anything. You submit a source zip; a HomeRunner administrator reads it, builds it on their machine, and publishes the result to the plugin CDN. Everything a customer loads was produced by that person, from your source, after a human decided to allow it.
That one fact shapes the whole pipeline and explains its two most surprising properties: your build output is audited after you have been told your submission was accepted, and nothing anywhere sends you a notification. This page is the map; every rule it mentions is stated normatively on another page, and linked.
The pipeline at a glance
ACTOR STEP ARTEFACT / STATE
──────────────────────────────────────────────────────────────────────────────────
you npm run build dist/ + a stamped manifest.json
you zip the project folder source zip
│
your browser ├─ source-zip audit a failure sends nothing at all
your browser ├─ presigned PUT ────▶ PRIVATE BUCKET submissions/{slug}/….zip
│ (bytes never transit a server)
dashboard └─ metadata leg ─────▶ CENTRAL submission = pending
│
admin ├─ downloads the zip, reads your source
│ changes_requested / rejected ────────▶ back to you, review notes only
▼ approved visibility fixed here
admin ├─ runs YOUR build, zips the result built zip
admin's browser ├─ built-zip audit fails = no publish, silently
admin's browser ├─ presigned PUTs ───▶ PLUGIN CDN {env}/{slug}/{version}/…
│ the root manifest.json uploads LAST (the version prefix is immutable)
dashboard ├─ fetches that manifest back proves the CDN really serves it
dashboard └─ publish call ─────▶ CENTRAL one txn: manifest_url,
current_version, manifest_cache,
status = approved
│
dashboard └─ warm the renderer, purge every feed live in seconds, not cache TTLs
│
customer ├─ feed ▸ Plugins ▸ Marketplace ▸ Install
customer └─ Create widget from your plugin the widget row stores the keyword
plugin:{slug}[:{widget}]
│
renderer resolves that keyword against the your component renders on a
cached ROOT manifest customer's page
Who acts, and where you watch it
| # | Step | Who acts | Where you see it |
|---|---|---|---|
| 1 | Build and zip | You | Your terminal |
| 2 | Upload | You | The uploader in Dashboard ▸ Plugins ▸ My plugins |
| 3 | Record the submission | Central | Your plugin row flips to a pending badge |
| 4 | Review | An administrator | Badge + review notes on the same row |
| 5 | Build your source, publish the built zip | The same administrator | Nothing until it lands |
| 6 | Verify and repoint | Dashboard, then central | The row goes live; a “Published manifest” link appears |
| 7 | Warm and purge | Dashboard | Nothing — it is invisible and best-effort |
| 8 | Install and configure | Your customer | Nothing you can see |
Steps 3 through 7 are one machine sequence with exactly one human decision in it — step 4. After step 2 you cannot influence anything except by submitting again.
The eight steps, in words
1. You build
npm run build produces dist/ and stamps runtime.widgetCore into your tracked
manifest.json. That stamp is the platform’s evergreen-runtime gate and the reason a
build older than widget-core 0.10.0 cannot be published; it is written by the build and
must never be hand-edited. See
Install and versions.
2. You zip and upload
(Both shapes.) You zip the project without dist/ and node_modules/ — the
reviewer rebuilds those. The uploader audits the archive in your browser first: if it
fails, nothing is transmitted at all. On success your browser PUTs the bytes straight into
a private bucket using a short-lived presigned URL, so the zip never passes through a
dashboard server, and a second call registers the metadata with central.
Two consequences. First, media/ and (for suites) widgets/*.manifest.json must be
committed — nothing but your build runs later, and nothing generates them. Second, the
browser audit is convenience, not authority: central re-validates everything server-side
and adds rules the browser cannot check. Both rule sets, with every limit and every
verbatim message, live in
Packaging and publishing rules.
3. Central records the submission
The submission lands on your plugin row: status pending, your version, your parsed manifest, your notes. A new slug creates the row and you own it; a slug owned by someone else, or a legacy admin-registered row with no owner, is refused — see the API codes in Packaging and publishing rules.
4. A human reviews it
An administrator downloads your zip, verifies its checksum, and reads your source; for an update they also see a diff of the live manifest against yours. Three outcomes:
| Outcome | Notes | What happens |
|---|---|---|
| approved | optional | Your submission waits for a publish. Public-or-private visibility is fixed here, in the approve dialog. |
| changes_requested | required | The notes appear on your row. Fix and re-submit. |
| rejected | required | The notes appear on your row, and your uploaded zip is deleted from the bucket. |
Nothing is live yet. Approval is a decision, not a release.
5. The same administrator builds your source
They run your build however they like, zip the built project folder, and drop it into the publish card. A second, different audit now inspects what your build actually produced: declared assets and their hashed twins, the registration marker in every client bundle, sub-manifests, icons, screenshots and READMEs. This is the first moment anyone checks your build output, and it happens after you were told your submission was fine. A failure here does not reopen the review — it costs you a resubmission with a bumped version. Both audits are listed in full, with their exact messages, in Packaging and publishing rules.
6. The version prefix, verification, and the repoint
Files upload one by one into the immutable CDN key prefix {env}/{slug}/{version}/, root
manifest.json last. That ordering makes a half-finished publish safe to retry: until the
root manifest exists, nothing is live and the sentinel that would refuse a re-publish is
absent.
The dashboard then fetches the published manifest back from its public URL and refuses to
continue unless it serves and matches the submission — that is what “published” means
here, not “we assume the bucket worked”. Only then does central repoint manifest_url,
current_version and its cached copy of your root manifest, set the plugin to approved
and clear the submission, in one transaction. Publishing therefore also un-suspends a
suspended plugin.
Where those URLs resolve to is Keywords, assets and URLs; what a version buys you, and how to undo one, is Publishing, versions and rollback.
7. Warm and purge
Composed customer pages are cached aggressively, so publishing does not wait for TTLs to expire: the dashboard asks the renderer to load your new SSR bundle and purges the page caches of every feed that has your plugin installed. Both are best-effort and neither can fail the publish. Typical propagation is seconds; the documented worst case is the composed-page cache TTL for feeds the fan-out did not reach in its budget. The numbers are in Publishing, versions and rollback.
8. A customer installs it, per feed
Nothing is global. A customer opens a feed ▸ Plugins ▸ Marketplace, installs your plugin, activates it, and only then can create a widget from it. The widget row stores your keyword; the renderer resolves it against the root manifest central cached at publish and loads the bundles it names. Install, per-widget toggles, uninstall and the kill switches that can stop your plugin serving are in Install, customers and kill switches; the keyword and manifest side is Manifest: root fields.
The five states you can be in
Your row in Dashboard ▸ Plugins ▸ My plugins renders one of exactly five timelines.
| State | Stepper | Who is blocked | What to do |
|---|---|---|---|
| pending | Submitted ✓ · In review · Approved · Published | An administrator | Wait |
| changes_requested | Submitted ✓ · Changes requested ⚠ · Approved · Published | You | Read the notes, fix, re-submit |
| rejected | Submitted ✓ · Rejected ✕ · Approved · Published | You | Read the notes, fix, re-submit |
| approved | Submitted ✓ · Reviewed ✓ · Approved ✓ · Publish pending | An administrator | Wait |
| published (nothing in flight) | Submitted · Reviewed · Approved · Published ✓ | Nobody | Ship the next version |
Review notes are shown inline for the two failure states, followed by “— fix and re-submit the zip above.” A plugin that is live and has an update in review shows both at once: the liveness badge for what is serving, and a timeline sitting at In review.
Nobody will tell you
Not supported yet. There are no notifications of any kind. No email, no in-app alert, no webhook fires when your plugin is approved, rejected, published or suspended. Nothing in the pipeline sends one. The only channel is opening Dashboard ▸ Plugins ▸ My plugins and reading the badge, the timeline and the review notes yourself. Poll it.
Not supported yet. The built-zip audit’s warnings are visible only to the reviewer. A config schema that does not extend the base, an oversized bundle, a filename that cannot be published — none of them block the publish, and all of them are recorded in a browser you will never look at. Unless the reviewer copies them into review notes, you will never learn that your plugin shipped with an empty settings form. Run those checks yourself: Preflight and submit.
A third asymmetry to plan around: the one-shape rule is not enforced before upload. Flipping a live plugin between the single-widget and suite shapes passes the source audit and passes review, and only dies at publish. See The two manifest shapes for the decision and Packaging and publishing rules for the gate.
Re-submitting replaces whatever is in flight
(Both shapes.) Uploading a new zip for the same slug overwrites the in-flight submission from any state — pending, changes_requested, rejected, even approved-but-unpublished. The status returns to pending, the reviewer, notes and review timestamp are cleared, and the previously uploaded zip is deleted.
Your published version is never touched by this. A plugin that is live keeps serving its current version throughout review, rejection and resubmission — the only things that change what customers load are a publish and a rollback. So if an approved submission has not been published yet and you spot a bug, just submit the fix: you are not queue-jumping, you are replacing the artefact the reviewer was about to build.
You host nothing
Third-party plugins cannot be self-hosted. Registering a plugin by pointing central at a manifest URL you control still exists, but it is an administrator-only escape hatch for pre-pipeline plugins: it has no dashboard UI at all, and it refuses a slug that already exists. The zip pipeline is the only path open to you.
This matters because the scaffold looks like it disagrees. serve.js, vercel.json and
api/ in a freshly generated project are local preview scaffolding — they serve
dist/ on your own machine. They are not a deployment target, nothing on the platform
consults them, and you can delete them.
Where each step’s rules live
| You want | Page |
|---|---|
| Zip rules, audit gates, size limits, verbatim messages | Packaging and publishing rules |
| Preflight checklist, the upload UI, review states in detail | Preflight and submit |
| Version rules, propagation, rollback | Publishing, versions and rollback |
| Install, per-feed config, suspension, kill switches | Install, customers and kill switches |
| Which URL a bundle, image or font resolves to | Keywords, assets and URLs |
| The shape decision you can never reverse | The two manifest shapes |
| Every number and message in one index | Limits and error index |