{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://emdashcms.com/schemas/emdash-plugin.schema.json", "title": "EmDash plugin manifest", "description": "Hand-authored manifest for publishing a plugin to the EmDash plugin registry. Lives next to the plugin's `package.json` as `emdash-plugin.jsonc`.", "type": "object", "properties": { "$schema": { "$ref": "#/$defs/__schema0" }, "slug": { "$ref": "#/$defs/__schema1" }, "version": { "$ref": "#/$defs/__schema2" }, "license": { "$ref": "#/$defs/__schema3" }, "publisher": { "$ref": "#/$defs/__schema4" }, "capabilities": { "$ref": "#/$defs/__schema5" }, "allowedHosts": { "$ref": "#/$defs/__schema7" }, "storage": { "$ref": "#/$defs/__schema9" }, "admin": { "$ref": "#/$defs/__schema16" }, "author": { "$ref": "#/$defs/__schema27" }, "authors": { "$ref": "#/$defs/__schema32" }, "security": { "$ref": "#/$defs/__schema33" }, "securityContacts": { "$ref": "#/$defs/__schema37" }, "name": { "$ref": "#/$defs/__schema38" }, "description": { "$ref": "#/$defs/__schema39" }, "keywords": { "$ref": "#/$defs/__schema40" }, "sections": { "$ref": "#/$defs/__schema42" }, "repo": { "$ref": "#/$defs/__schema52" }, "release": { "$ref": "#/$defs/__schema53" } }, "required": [ "slug", "license", "publisher", "capabilities", "allowedHosts", "storage" ], "additionalProperties": false, "$defs": { "__schema0": { "type": "string", "description": "Path or URL to the JSON Schema describing this file. Editors use this for completion and validation." }, "__schema1": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "title": "Slug", "description": "URL-safe plugin identifier within the publisher's namespace. ASCII letter then letters/digits/hyphens/underscores, max 64 characters. Combined with the publisher DID, this is the registry's primary key.", "examples": [ "gallery", "image-resizer", "my-plugin" ] }, "__schema2": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?$", "title": "Version", "description": "Plugin version. Semver 2.0 subset; build-metadata `+...` is disallowed (the atproto record-key alphabet has no `+`). Bumped on every release.", "examples": [ "0.1.0", "1.2.3", "1.0.0-rc.1" ] }, "__schema3": { "type": "string", "minLength": 1, "maxLength": 256, "title": "License", "description": "SPDX license expression (e.g. \"MIT\", \"Apache-2.0\", \"MIT OR Apache-2.0\"). Required on first publish; ignored on subsequent publishes (the existing profile wins).", "examples": [ "MIT", "Apache-2.0", "MIT OR Apache-2.0" ] }, "__schema4": { "type": "string", "title": "Publisher", "description": "Atproto DID or handle of the publishing identity. Pinned on first publish to prevent accidental publishes from a different account. DIDs are recommended (durable); handles work but are mutable.", "examples": [ "did:plc:abc123def456", "example.com" ] }, "__schema5": { "default": [], "maxItems": 32, "type": "array", "items": { "$ref": "#/$defs/__schema6" }, "title": "Capabilities", "description": "Trust contract: what runtime APIs the plugin is allowed to use. Changing this between releases requires a version bump because installed users have consented to the old contract." }, "__schema6": { "type": "string", "minLength": 1 }, "__schema7": { "default": [], "maxItems": 64, "type": "array", "items": { "$ref": "#/$defs/__schema8" }, "title": "Allowed hosts", "description": "Allow-list of outbound host patterns when `network:request` is declared. Subdomain wildcards use a leading `*.`. Required (non-empty) when `network:request` is declared without `network:request:unrestricted`.", "examples": [ [ "api.example.com", "*.cdn.example.com" ] ] }, "__schema8": { "type": "string", "minLength": 1, "maxLength": 256 }, "__schema9": { "default": {}, "type": "object", "propertyNames": { "$ref": "#/$defs/__schema10" }, "additionalProperties": { "$ref": "#/$defs/__schema11" }, "title": "Storage", "description": "Storage collections the plugin uses. Each collection is namespaced to this plugin at runtime." }, "__schema10": { "type": "string", "minLength": 1, "pattern": "^[a-z][a-z0-9_]*$" }, "__schema11": { "type": "object", "properties": { "indexes": { "$ref": "#/$defs/__schema12" }, "uniqueIndexes": { "$ref": "#/$defs/__schema14" } }, "required": [ "indexes" ], "additionalProperties": false, "title": "Storage collection", "description": "Index configuration for a single storage collection. Indexes are either single field names or composite (array of field names)." }, "__schema12": { "type": "array", "items": { "anyOf": [ { "type": "string", "minLength": 1 }, { "minItems": 1, "type": "array", "items": { "$ref": "#/$defs/__schema13" } } ] } }, "__schema13": { "type": "string", "minLength": 1 }, "__schema14": { "type": "array", "items": { "anyOf": [ { "type": "string", "minLength": 1 }, { "minItems": 1, "type": "array", "items": { "$ref": "#/$defs/__schema15" } } ] } }, "__schema15": { "type": "string", "minLength": 1 }, "__schema16": { "type": "object", "properties": { "pages": { "$ref": "#/$defs/__schema17" }, "widgets": { "$ref": "#/$defs/__schema22" } }, "additionalProperties": false, "title": "Admin surface", "description": "Pages and widgets the plugin exposes in the admin UI. The plugin's `admin` route handler renders Block Kit content for each path / widget id at runtime." }, "__schema17": { "maxItems": 32, "type": "array", "items": { "$ref": "#/$defs/__schema18" } }, "__schema18": { "type": "object", "properties": { "path": { "$ref": "#/$defs/__schema19" }, "label": { "$ref": "#/$defs/__schema20" }, "icon": { "$ref": "#/$defs/__schema21" } }, "required": [ "path", "label" ], "additionalProperties": false, "title": "Admin page", "description": "A single admin page declaration. The plugin's `admin` route handler is responsible for rendering Block Kit content for this path." }, "__schema19": { "type": "string", "minLength": 2, "maxLength": 128, "pattern": "^\\/[a-z0-9][a-z0-9/_-]*$" }, "__schema20": { "type": "string", "minLength": 1, "maxLength": 128 }, "__schema21": { "type": "string", "minLength": 1, "maxLength": 64 }, "__schema22": { "maxItems": 32, "type": "array", "items": { "$ref": "#/$defs/__schema23" } }, "__schema23": { "type": "object", "properties": { "id": { "$ref": "#/$defs/__schema24" }, "title": { "$ref": "#/$defs/__schema25" }, "size": { "$ref": "#/$defs/__schema26" } }, "required": [ "id" ], "additionalProperties": false, "title": "Admin widget", "description": "A single dashboard widget declaration." }, "__schema24": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$" }, "__schema25": { "type": "string", "minLength": 1, "maxLength": 128 }, "__schema26": { "type": "string", "enum": [ "full", "half", "third" ] }, "__schema27": { "$ref": "#/$defs/__schema28" }, "__schema28": { "type": "object", "properties": { "name": { "$ref": "#/$defs/__schema29" }, "url": { "$ref": "#/$defs/__schema30" }, "email": { "$ref": "#/$defs/__schema31" } }, "required": [ "name" ], "additionalProperties": false, "title": "Author", "description": "A single author entry. Mirrors the lexicon's author shape." }, "__schema29": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Display name." }, "__schema30": { "type": "string", "maxLength": 1024, "format": "uri", "description": "Author's homepage or profile URL. Either this or `email` is recommended." }, "__schema31": { "type": "string", "maxLength": 256, "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", "description": "Author's contact email. Either this or `url` is recommended." }, "__schema32": { "minItems": 1, "maxItems": 32, "type": "array", "items": { "$ref": "#/$defs/__schema28" }, "title": "Authors (multiple)", "description": "Multi-author form. Mutually exclusive with `author`. Use the singular `author` if there is only one." }, "__schema33": { "$ref": "#/$defs/__schema34" }, "__schema34": { "type": "object", "properties": { "url": { "$ref": "#/$defs/__schema35" }, "email": { "$ref": "#/$defs/__schema36" } }, "additionalProperties": false, "title": "Security contact", "description": "A single security contact. At least one of `url` or `email` must be present." }, "__schema35": { "type": "string", "maxLength": 1024, "format": "uri", "description": "Security disclosure URL (e.g. a security.txt or vulnerability-reporting page). Either this or `email` is required." }, "__schema36": { "type": "string", "maxLength": 256, "format": "email", "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", "description": "Security contact email. Either this or `url` is required." }, "__schema37": { "minItems": 1, "maxItems": 8, "type": "array", "items": { "$ref": "#/$defs/__schema34" }, "title": "Security contacts (multiple)", "description": "Multi-contact form. Mutually exclusive with `security`. Use the singular `security` if there is only one." }, "__schema38": { "type": "string", "minLength": 1, "maxLength": 1024, "title": "Display name", "description": "Human-readable name shown in directory listings. Defaults to the plugin's `id` when omitted." }, "__schema39": { "type": "string", "minLength": 1, "maxLength": 1024, "title": "Description", "description": "Short description (<= 140 graphemes by FAIR convention). Aggregators may truncate longer values when displaying in compact lists." }, "__schema40": { "maxItems": 5, "type": "array", "items": { "$ref": "#/$defs/__schema41" }, "title": "Keywords", "description": "Search keywords (<= 5 entries, FAIR convention)." }, "__schema41": { "type": "string", "minLength": 1, "maxLength": 128 }, "__schema42": { "type": "object", "properties": { "description": { "$ref": "#/$defs/__schema43" }, "installation": { "$ref": "#/$defs/__schema48" }, "faq": { "$ref": "#/$defs/__schema49" }, "changelog": { "$ref": "#/$defs/__schema50" }, "security": { "$ref": "#/$defs/__schema51" } }, "additionalProperties": false, "title": "Profile sections", "description": "Long-form package documentation (description / installation / faq / changelog / security). Each value is CommonMark Markdown (inline or a `{ file }` ref) capped at 20000 bytes / 2000 graphemes. Rendered on the package's registry page." }, "__schema43": { "$ref": "#/$defs/__schema44" }, "__schema44": { "anyOf": [ { "$ref": "#/$defs/__schema45" }, { "$ref": "#/$defs/__schema46" } ], "title": "Section value", "description": "Either inline CommonMark Markdown or a `{ file }` reference to a sibling Markdown file." }, "__schema45": { "type": "string", "description": "Inline CommonMark Markdown for this section. Capped at 20000 bytes / 2000 graphemes." }, "__schema46": { "type": "object", "properties": { "file": { "$ref": "#/$defs/__schema47" } }, "required": [ "file" ], "additionalProperties": false, "title": "Section file reference", "description": "Loads the section's Markdown from a sibling file instead of inlining it in the manifest." }, "__schema47": { "type": "string", "minLength": 1, "maxLength": 1024, "description": "Path to a CommonMark Markdown file, relative to the manifest. Its content is read inline into the section at publish time." }, "__schema48": { "$ref": "#/$defs/__schema44" }, "__schema49": { "$ref": "#/$defs/__schema44" }, "__schema50": { "$ref": "#/$defs/__schema44" }, "__schema51": { "$ref": "#/$defs/__schema44" }, "__schema52": { "type": "string", "maxLength": 1024, "format": "uri", "pattern": "^https:\\/\\/", "title": "Source repository", "description": "HTTPS URL of the plugin's source repository. Surfaced in registry listings.", "examples": [ "https://github.com/emdash-cms/plugin-gallery" ] }, "__schema53": { "type": "object", "properties": { "requires": { "$ref": "#/$defs/__schema54" }, "artifacts": { "$ref": "#/$defs/__schema57" } }, "additionalProperties": false, "title": "Release", "description": "Per-release fields: environment constraints (`requires`) and media artifacts (icon / screenshot / banner)." }, "__schema54": { "type": "object", "propertyNames": { "$ref": "#/$defs/__schema55" }, "additionalProperties": { "$ref": "#/$defs/__schema56" }, "title": "Environment requirements", "description": "Host environment constraints for this release, keyed by `env:*` (e.g. \"env:astro\", \"env:emdash\") with semver-range values. EmDash refuses to install a release whose constraints the host does not satisfy.", "examples": [ { "env:emdash": ">=1.0.0", "env:astro": ">=4.16" } ] }, "__schema55": { "type": "string" }, "__schema56": { "type": "string", "minLength": 1 }, "__schema57": { "type": "object", "properties": { "icon": { "$ref": "#/$defs/__schema58" }, "banner": { "$ref": "#/$defs/__schema62" }, "screenshots": { "$ref": "#/$defs/__schema63" } }, "additionalProperties": false, "title": "Artifacts", "description": "Release media artifacts. `icon` and `banner` are single images; `screenshots` is a gallery array." }, "__schema58": { "$ref": "#/$defs/__schema59" }, "__schema59": { "type": "object", "properties": { "file": { "$ref": "#/$defs/__schema60" }, "lang": { "$ref": "#/$defs/__schema61" } }, "required": [ "file" ], "additionalProperties": false, "title": "Artifact file reference", "description": "A media file (PNG / JPEG / WebP / GIF / AVIF) bundled into a release as an icon, screenshot, or banner." }, "__schema60": { "type": "string", "minLength": 1, "maxLength": 1024, "description": "Path to the image file, relative to the manifest. Resolved, hashed, measured, and uploaded at publish time." }, "__schema61": { "type": "string", "minLength": 2, "maxLength": 64, "description": "BCP 47 language tag for a localised artifact (e.g. \"en\", \"pt-BR\").", "examples": [ "en", "pt-BR" ] }, "__schema62": { "$ref": "#/$defs/__schema59" }, "__schema63": { "minItems": 1, "maxItems": 8, "type": "array", "items": { "$ref": "#/$defs/__schema59" }, "title": "Screenshots", "description": "Screenshot gallery for the plugin's detail page (<= 8 entries)." } } }