# AstrBot Plugin Market JSON Specification

Specification version: 2026-06-27

Market JSON schema version: 1

## 1. Scope

This document defines the JSON format for AstrBot plugin market sources.

## 2. Terms

- `PluginRecord`: One plugin entry in a market source.
- `MarketMetadata`: Metadata for the market source itself.
- `plugin_id`: The global plugin package identifier.
- `metadata.yaml`: The plugin metadata file inside the installed plugin package.

The terms `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` are normative.

## 3. Document Format

A plugin market source MUST be a UTF-8 JSON object.

The root object MUST contain the reserved `$meta` key.

All root keys other than `$meta` MUST be one of:

- a `plugin_id` string
- a `name` string under the compatibility exception in Section 5

Each root key other than `$meta` MUST map to a `PluginRecord` object:

```json
{
  "$meta": {
    "schema_version": 1,
    "name": "AstrBot Official Plugin Market",
    "version": "2026-06-27",
    "homepage": "https://astrbot.app",
    "repository": "https://github.com/AstrBotDevs/AstrBot_Plugins_Collection"
  },
  "owner/astrbot_plugin_foo": {
    "author": "owner",
    "name": "astrbot_plugin_foo",
    "version": "1.0.0",
    "repo": "https://github.com/owner/astrbot_plugin_foo",
    "desc": "Plugin description"
  },
  "astrbot_plugin_bar": {
    "author": "owner",
    "version": "1.0.0",
    "repo": "https://github.com/owner/astrbot_plugin_bar",
    "desc": "Compatibility record using the root key as name"
  }
}
```

The root object MUST NOT contain top-level metadata fields outside `$meta`.

## 4. Market Metadata

`$meta` MUST NOT be interpreted as a `PluginRecord`.

`$meta` MUST be a `MarketMetadata` object.

`MarketMetadata` MUST contain:

| Field            | Type    | Requirement                                                     |
| ---------------- | ------- | --------------------------------------------------------------- |
| `schema_version` | integer | Market JSON schema version. MUST be `1` for this specification. |

`MarketMetadata` MAY contain:

| Field         | Type   | Requirement                                                         |
| ------------- | ------ | ------------------------------------------------------------------- |
| `name`        | string | Human-readable market source name.                                  |
| `version`     | string | Market source data version. MUST NOT be used as the schema version. |
| `homepage`    | string | HTTPS homepage URL.                                                 |
| `repository`  | string | HTTPS repository URL for the market source data.                    |
| `description` | string | Market source description.                                          |
| `updated_at`  | string | ISO 8601 timestamp.                                                 |

`$meta` MUST NOT be used for:

- plugin identity
- install source resolution
- update source resolution
- plugin security validation

## 5. Plugin Identity

`plugin_id` is defined as:

```text
metadata.author + "/" + metadata.name
```

For every `PluginRecord`:

- The root key SHOULD equal `${author}/${name}`.
- The root key MAY equal `name` under the compatibility exception below.
- `author` MUST equal `metadata.yaml.author`.
- `name` MUST equal `metadata.yaml.name`.
- `plugin_id` MUST be globally unique across the AstrBot plugin ecosystem.
- Two `plugin_id` values that are equal after lowercase normalization MUST NOT both exist.
- `repo` MUST NOT be used as plugin identity.
- `root_dir_name`, local directory names, display names, and registry names MUST NOT be used as plugin identity.

`author` and `name`:

- MUST be non-empty strings.
- MUST be trimmed.
- MUST NOT contain `/`.
- MUST NOT contain ASCII control characters.
- SHOULD be stable package identity values, not display names.

### 5.1. Name Key Compatibility Exception

A root key MAY equal `metadata.yaml.name` instead of `${author}/${name}`.

When this exception is used:

- The root key MUST NOT contain `/`.
- `author` MUST be present.
- `name` MAY be omitted.
- If `name` is present, it MUST equal the root key.
- The root key MUST equal `metadata.yaml.name`.
- Consumers MUST construct `plugin_id` as `${author}/${root_key}`.

## 6. Required Fields

Each `PluginRecord` MUST contain:

| Field     | Type   | Requirement                                                                                                              |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------ |
| `author`  | string | Plugin author namespace. MUST match `metadata.yaml.author`.                                                              |
| `name`    | string | Plugin package name. MUST match `metadata.yaml.name`. MAY be omitted only when the root key equals `metadata.yaml.name`. |
| `version` | string | Plugin version. MUST match `metadata.yaml.version`.                                                                      |
| `repo`    | string | HTTPS GitHub repository URL used for source attribution and installation.                                                |
| `desc`    | string | Default plugin description.                                                                                              |

## 7. Optional Fields

| Field               | Type     | Requirement                                                                        |
| ------------------- | -------- | ---------------------------------------------------------------------------------- |
| `display_name`      | string   | Human-readable plugin name.                                                        |
| `short_desc`        | string   | Short description for compact UI.                                                  |
| `download_url`      | string   | HTTPS ZIP download URL. Installed package identity MUST still match `author/name`. |
| `logo`              | string   | HTTPS image URL or relative static asset path.                                     |
| `tags`              | string[] | Tag list.                                                                          |
| `category`          | string   | Market category key.                                                               |
| `support_platforms` | string[] | Supported platform keys.                                                           |
| `astrbot_version`   | string   | AstrBot version requirement expression.                                            |
| `social_link`       | string   | Author or project HTTPS URL.                                                       |
| `updated_at`        | string   | ISO 8601 timestamp.                                                                |
| `i18n`              | object   | Localized display fields.                                                          |
| `pinned`            | boolean  | Market recommendation flag.                                                        |
| `stars`             | integer  | Non-negative star count or ranking signal.                                         |
| `download_count`    | integer  | Non-negative download count.                                                       |

### 7.1. Repository URL

`repo` MUST be one of:

```text
https://github.com/{owner}/{repository}
https://github.com/{owner}/{repository}.git
https://github.com/{owner}/{repository}/tree/{branch}
```

`owner`, `repository`, and `branch` MUST match:

```text
[A-Za-z0-9_-]+
```

`repo` MUST NOT be:

- an HTTP URL
- an SSH URL
- a non-GitHub URL
- a GitHub Enterprise URL
- a GitHub file, release, pull request, issue, or subdirectory URL

`download_url` MAY point to any HTTPS ZIP URL.

### 7.2. Localized Fields

`i18n` keys SHOULD be BCP 47 locale tags.

Each locale object MAY contain:

```json
{
  "display_name": "Localized name",
  "desc": "Localized description",
  "short_desc": "Localized short description"
}
```

## 8. Reserved Fields

Market sources MUST NOT use these fields inside `PluginRecord` objects:

| Field                      | Reason                                               |
| -------------------------- | ---------------------------------------------------- |
| `plugin_id`                | Derived from `author/name`.                          |
| `market_plugin_id`         | Runtime/client compatibility field, not market data. |
| `market_plugin_identifier` | Deprecated runtime/client compatibility field.       |
| `root_dir_name`            | Local installation field.                            |
| `local_plugin_name`        | Local installation field.                            |
| `install_method`           | Local installation field.                            |
| `registry_url`             | Local installation field.                            |
| `registry_name`            | Local installation field.                            |
| `installed_at`             | Local installation field.                            |

Consumers MUST NOT use reserved fields for plugin identity.

The root key `$meta` is reserved for `MarketMetadata`.

## 9. Deprecated Fields

These fields MAY be accepted by clients during migration, but new market sources MUST NOT emit them:

| Deprecated Field   | Replacement         |
| ------------------ | ------------------- |
| `support_platform` | `support_platforms` |
| `platform`         | `support_platforms` |

## 10. Unknown Fields

Consumers MAY preserve unknown fields.

Consumers MUST NOT use unknown fields for:

- plugin identity
- install source resolution
- update source resolution
- security validation

## 11. Validation Rules

A market source is valid only if all rules pass:

1. The root JSON value is an object.
2. `$meta` is present.
3. `$meta` is an object.
4. `$meta.schema_version` is the integer `1`.
5. Every root key other than `$meta` is either a `plugin_id` or a `name` key.
6. Every root value other than `$meta` is a `PluginRecord` object.
7. Every record contains all required fields except `name` MAY be omitted when the root key is a `name` key.
8. Every required string field is non-empty after trimming.
9. `author` and present `name` values do not contain `/`.
10. The root key equals `${author}/${name}` exactly, or the root key equals `name` exactly under the compatibility exception.
11. No two IDs are equal after lowercase normalization.
12. `repo` is an HTTPS GitHub repository URL.
13. If present, `download_url` is an HTTPS URL.
14. If present, `tags` is an array of strings.
15. If present, `support_platforms` is an array of strings.
16. If present, `stars` is a non-negative integer.
17. If present, `download_count` is a non-negative integer.
18. If present, `updated_at` is an ISO 8601 timestamp.
19. If present, `$meta.homepage` is an HTTPS URL.
20. If present, `$meta.repository` is an HTTPS URL.
21. If present, `$meta.updated_at` is an ISO 8601 timestamp.
22. The package resolved from `repo` or `download_url` contains `metadata.yaml`.
23. `metadata.yaml.author` equals `author`.
24. `metadata.yaml.name` equals `name`.
25. `metadata.yaml.version` equals `version`.

## 12. Installation Rules

For market installation:

1. The client sends the selected `plugin_id` and registry source to the backend.
2. The backend resolves the `PluginRecord` from that registry source by `plugin_id`.
3. The backend installs using `download_url` when available; otherwise it installs using `repo`.
4. After installation, the backend reads the installed `metadata.yaml`.
5. The installed `metadata.yaml.author/name` MUST equal the selected `plugin_id`.
6. If identity validation fails, installation MUST fail.
7. A successful market install MAY persist `repo`, `download_url`, registry information, and install time in local state.

## 13. Update Rules

For market-installed plugins:

1. Update checks MUST use the registry source recorded at installation time.
2. Update checks MUST resolve the market record by `plugin_id`.
3. Version comparison MUST use the installed metadata version and the market record `version`.
4. Update execution MUST use the resolved market record `repo/download_url`.

For non-market installs:

1. URL installs, Git installs, and upload installs MUST NOT be treated as market installs.
2. They MAY persist `plugin_id` from installed `metadata.yaml`.
3. They MUST NOT enable market update checks unless explicitly bound to a market source.

## 14. Source Binding Rules

When binding an installed non-market plugin to a market source:

1. The installed plugin identity MUST be read from installed `metadata.yaml`.
2. Candidate market records MUST have the same `plugin_id`.
3. Candidate market records SHOULD have the same repository identity.
4. Binding MUST persist the selected registry source and `plugin_id`.

## 15. Compatibility

Consumers MAY read legacy market sources during migration.

For legacy sources:

- Consumers MAY accept sources without `$meta.schema_version` during migration.
- Consumers SHOULD construct `plugin_id` from `author/name`.
- Consumers SHOULD reject entries without `author` or `name` for market installation.
- Consumers MAY use legacy root keys for display only.
- Consumers MUST NOT use legacy root keys as plugin identity unless the key equals `${author}/${name}`.

New market sources MUST include `$meta.schema_version`.
