Skip to content

Manifest — aero.json

Every extension is described by a single aero.json at the root of its .aero-ext archive. This page is the complete field reference, generated from aero-manifest.schema.json. The main process validates every manifest against that schema on install; the aero-ext CLI validates it on package.

A complete example

json
{
  "name": "dracula-warm",
  "displayName": "Dracula Warm",
  "publisher": "aero",
  "version": "1.0.0",
  "description": "A cozy, warm take on Dracula for Aero — plus a command to apply it.",
  "tagline": "Cozy warm dark colors",
  "category": "Themes",
  "icon": "🧛",
  "tags": ["dark", "warm", "theme"],
  "engines": { "aero": ">=0.5.0" },
  "main": "extension.js",
  "activationEvents": ["onCommand:dracula-warm.apply"],
  "contributes": {
    "themes": [
      { "label": "Dracula Warm", "uiTheme": "dark", "path": "themes/dracula-warm.json" }
    ],
    "commands": [
      { "command": "dracula-warm.apply", "title": "Dracula Warm: Apply Theme" }
    ],
    "keybindings": [
      { "key": "cmd+k cmd+d", "command": "dracula-warm.apply", "when": "always" }
    ],
    "snippets": [
      { "language": "javascript", "path": "snippets/javascript.json" }
    ]
  }
}

Top-level fields

The schema sets additionalProperties: false — unknown top-level keys are a validation error.

FieldTypeRequiredConstraint
namestringyes^[a-z0-9][a-z0-9-]*$, ≤ 64 chars. kebab-case.
displayNamestringyes1–80 chars. Human title shown on cards.
publisherstringyes^[a-z0-9][a-z0-9-]*$, ≤ 64 chars.
versionstringyes^\d+\.\d+\.\d+$ (semver x.y.z).
descriptionstringno≤ 2000 chars. Long marketplace copy.
taglinestringno≤ 80 chars. One-liner for cards.
categorystringnoOne of the categories. Default Tools.
iconstringno≤ 8 chars. Emoji/glyph preferred — no asset deps.
tagsstring[]no≤ 12 items, each ≤ 32 chars.
enginesobjectno{ "aero": "<semver range>" }.
mainstringnoRelative JS entry for command logic. See below.
activationEventsstring[]noSee Activation events.
contributesobjectnoThe contributions. See below.

name

The machine name, kebab-case, unique within a publisher. Combined with publisher it forms the canonical id "<publisher>.<name>" (e.g. aero.dracula-warm). That id is the registry primary key and the on-disk folder name — it is derived, not stored in the manifest.

version

Strict three-part semver (1.0.0). Published versions are immutable — to ship a fix, bump the version and publish again. The registry enforces a unique (extension_id, version).

category

If present, must be one of:

Themes · Languages · Linters · Formatters · Tools · AI · Snippets

These match the marketplace's CATEGORIES. Omit it to default to Tools.

icon

An emoji or single glyph (≤ 8 chars). Aero prefers emoji over image assets so extensions stay dependency-free and tiny. (An icon.png may sit in the archive, but the manifest emoji is the recommended path.)

engines.aero

Optional semver range checked against the running app version, e.g. { "aero": ">=0.5.0" }. Use it to gate on APIs introduced in a given Aero release.

main

Relative path to a sandboxed JS entry, used only for command logic. The pattern forbids a leading slash and any backslash (^[^/\\][^\\]*$) — paths are relative and forward-slash. Theme-, snippet-, and keybinding-only extensions need no main. See The aero.* API.

Code runs sandboxed

main JS executes inside a sandboxed <iframe sandbox="allow-scripts"> with no DOM, Node, or window.api access. Its only channel is a postMessage bridge to the capability-gated aero.* API. Declarative contributions carry no code at all.

contributes

The container for the four v1 contribution types. additionalProperties: false applies here too, so only these keys are valid.

json
"contributes": {
  "themes":      [ /* … */ ],
  "commands":    [ /* … */ ],
  "keybindings": [ /* … */ ],
  "snippets":    [ /* … */ ]
}

contributes.themes[]

FieldTypeRequiredNotes
labelstringyesName shown in the theme list (≥ 1 char).
uiThemestringyes"light" or "dark" — base palette.
pathstringyesRelative path to the theme JSON file.

Full token list and theme-file shape: Themes.

contributes.commands[]

FieldTypeRequiredNotes
commandstringyesId, ^[A-Za-z0-9_.-]+$. Convention name.verb.
titlestringyesPalette label (≥ 1 char).

Details and JS handlers: Commands.

contributes.keybindings[]

FieldTypeRequiredNotes
keystringyesSpace-separated chords (≥ 1 char).
commandstringyes^[A-Za-z0-9_.-]+$. Own or built-in command.
whenstringno"editorFocus" or "always".

Chord syntax and contexts: Keybindings.

contributes.snippets[]

FieldTypeRequiredNotes
languagestringyesMonaco language id (≥ 1 char).
pathstringyesRelative path to the snippet JSON file.

Snippet-file shape: Snippets.

Path rules (all path fields)

Every path in the manifest — main, themes[].path, snippets[].path — must:

  • be relative and forward-slash separated;
  • resolve inside the archive root;
  • contain no .., no absolute paths, and no symlinks.

The packager and the main process both reject paths that escape the archive.

Validating locally

bash
npx aero-ext package   # validates aero.json against the schema, then zips

You can also validate aero.json against the published schema directly. Add the $schema key in your editor for inline hints:

json
{
  "$schema": "https://aeroide.in/schemas/aero-manifest.schema.json",
  "name": "my-extension"
}

Lean, AI-ready, under your control.