Publishing
When your extension works locally, publish it to the Aero marketplace. The web Publish page and the aero-ext CLI run exactly the same five-step, RLS-enforced flow against Supabase — pick whichever you prefer.
Before you publish
- Your
aero.jsonmust pass the schema. versionmust be a new, unique(extension_id, version)— published versions are immutable. To ship a fix, bump the version.- The packaged
.aero-extmust be ≤ 50 MB. - You need a publisher account (Google sign-in) so the platform has your
auth.uid().
The five-step flow
Both the web page and the CLI perform these steps, enforced by Supabase Row-Level Security (no server needed in v1):
- Authenticate (Google) → you have
auth.uid(). - Upload the
.aero-extto theextension-packagesStorage bucket at<uid>/<id>/<version>.aero-ext. - Upsert the
extensionsrow (publisher_id = auth.uid()). - Insert the
extension_versionsrow (package_path, parsedmanifest,sha256,size_bytes). - Update
extensions.latest_version = version.
RLS restricts writes to your own <uid>/… Storage folder and rows owned by your auth.uid(), so you can only publish under your own publisher identity.
Publish with the CLI
# From your extension folder:
npx aero-ext login # opens Google sign-in, stores your session
npx aero-ext package # validates aero.json, then zips → .aero-ext
npx aero-ext publish # runs the five-step flow abovepublish packages first if needed, uploads to Storage, upserts the catalog row, inserts the immutable version row, and bumps latest_version. On success it prints the canonical id and version, and the extension appears in the marketplace.
Publish from the web
- Go to the marketplace Publish page (
aeroide.in/publish). - Sign in with Google.
- Drag in your
.aero-ext. The page parsesaero.json, computessha256andsize_bytes, and shows a preview. - Confirm — the page runs the same upload → upsert → insert → update flow.
What lands in the registry
| Table | Row written |
|---|---|
public.extensions | One row per extension (latest metadata): id, publisher_id, publisher_handle, name, display_name, tagline, description, category, tags, icon, latest_version, downloads, rating, verified, featured, timestamps. |
public.extension_versions | Immutable version: extension_id, version, manifest (jsonb), package_path, size_bytes, sha256, changelog, published_at. Unique (extension_id, version). |
public.extension_installs | One per (extension_id, user_id) when a user installs. |
Downloads = unique installers
You don't (and can't) set the download counter. An AFTER INSERT trigger on extension_installs bumps extensions.downloads — so the count reflects unique installers and clients can't forge it.
Categories
category in your manifest must be one of these (they match the marketplace):
Themes · Languages · Linters · Formatters · Tools · AI · SnippetsStorage & download URLs
The .aero-ext lives in the public extension-packages bucket at <uid>/<id>/<version>.aero-ext. Anyone can download via the public URL — no auth needed:
https://wfyvsawnyjewksxwvaco.supabase.co/storage/v1/object/public/extension-packages/<uid>/<id>/<version>.aero-extWhen a user installs from the marketplace, Aero downloads this URL, optionally verifies the sha256 against the version row, unzips into ~/.aero/extensions/<id>/<version>/, validates the manifest, and records an install row (which counts the download).
Shipping an update
- Bump
versioninaero.json(e.g.1.0.0→1.1.0). npx aero-ext package && npx aero-ext publish(or re-upload on the web).
The new version is inserted as a fresh immutable row and latest_version is updated. Existing installs keep their pinned version until the user updates.
Related
- Getting started — scaffold, package, sideload.
- Manifest reference — what gets parsed into
manifest. - Examples — two extensions end to end.