@grove-notes/manifest-schema
v0.1.1
Published
Static metadata schema for Grove marketplace extensions (plugins, themes, icon packs, templates).
Readme
@grove-notes/manifest-schema
Static metadata schema for Grove marketplace extensions: plugins, themes, icon packs, templates, and (stubbed) widgets.
This package owns the on-disk shipping format every Grove extension publishes — the plugin.json, theme.json, icon-pack.json, template.json, or widget.json file an author commits to their repo. It is intentionally separate from @grove-notes/plugin-sdk, which exposes Grove's runtime API surface that plugin JavaScript code calls into. The two coexist:
| Package | Audience | What it describes |
|---|---|---|
| @grove-notes/manifest-schema | Authors of plugins, themes, icon packs, and templates; the registry CI; any external tooling that needs to validate extension metadata. | The static JSON file an extension ships. |
| @grove-notes/plugin-sdk | Authors of plugins (only — not themes/icons/templates). | The runtime API a plugin's JavaScript imports (Plugin class, PluginContext, etc.). |
The runtime representation Grove holds in memory after loading a plugin is LoadedPluginManifest in @grove-notes/plugin-sdk. The on-disk shipping shape an author writes is PluginManifest in this package. They are different concepts at different abstraction layers.
Quick start — JSON Schema (no install)
Add a $schema reference at the top of your manifest and your IDE will offer autocomplete and validation:
{
"$schema": "https://unpkg.com/@grove-notes/manifest-schema@0/schemas/plugin.schema.json",
"id": "kanban",
// ...
}The hosted schemas under unpkg.com/@grove-notes/manifest-schema@{version}/schemas/ are generated from the Valibot validators in this package and committed to the repo.
Install (for pre-submit validation in your own CI)
npm install --save-dev @grove-notes/manifest-schemaimport { parsePluginManifest } from '@grove-notes/manifest-schema';
import { readFileSync } from 'node:fs';
const raw = JSON.parse(readFileSync('plugin.json', 'utf-8'));
const manifest = parsePluginManifest(raw); // throws ValiError on failureEvery per-type parser has a safeParse…Manifest counterpart that returns { success, issues } instead of throwing.
Manifest types
PluginManifest— JavaScript-extensible feature. Plugin code is loaded into Grove's runtime.ThemeManifest— CSS-based visual customisation; may optionally bundle icons viaprovidesIcons+iconEntry.IconPackManifest— SVG sprite (single file) defining a reusable icon set.TemplateManifest— Markdown templates (directory of.mdfiles).WidgetManifest— v1 stub. Accepts any object that satisfiesCommonManifest; the full widget format ships in v1.x and will break this shape.
All extend CommonManifest, which carries id, name, description, version, author, minGroveVersion, license, and other shared fields. See manifest-schema.md for the field-by-field reference.
Validator behaviour
- Strict on known field types. No coercion.
"version": "1.2.0"is valid;"version": 1.2is not. - Lenient on unknown top-level fields. They are silently dropped during parsing. This lets older Grove clients read manifests authored against newer schemas without failing — they pick up what they understand and ignore the rest. Authors targeting newer Grove features bump
minGroveVersionso older clients filter them out entirely. - Strict on locale keys inside a
LocalizedString. Translations are only accepted for languages Grove actually renders (default,en,de,es,zh-Hans,zh-Hant). An author writing"fr": "Bonjour"for a Grove that doesn't support French is dead data — the validator fails fast rather than silently dropping. The plugin manifest'si18narray follows the same allowlist. When Grove adds a language, this package gets a minor bump and the new code becomes valid. - Validation errors include the offending field path. The registry's PR-comment pipeline depends on this.
Supported locales
The accepted locale keys in LocalizedString and i18n are exported as SUPPORTED_LANGUAGES and SupportedLanguage:
import { SUPPORTED_LANGUAGES, type SupportedLanguage } from '@grove-notes/manifest-schema';
// → ['en', 'de', 'es', 'zh-Hans', 'zh-Hant'] as constThis list is the single source of truth — Grove's i18n runtime (packages/frontend/src/i18n/locale.ts) imports SUPPORTED_LANGUAGES and SupportedLanguage directly from this package, so the validator and the renderer cannot drift. Adding a language is a minor bump; removing one is a major.
Security contract (consumers must follow)
This package validates shape, not safety. Manifest fields that surface human-readable strings — name, description, author, and any future free-text fields — are untrusted user input from the perspective of any UI that renders them.
- HTML-escape these fields before rendering. Use
{{ }}/v-textinterpolation; neverv-html. - README content fetched via the
readmefield is untrusted markdown. Run it through a sanitiser (e.g.marked+ DOMPurify) before rendering.
The SiYuan bazaar shipped without this discipline and was hit by a stored-XSS-to-RCE via displayName in 2025. Grove's schema can't enforce sanitisation at the validator boundary — but every consumer (Grove itself, the marketplace site, any external tool) must apply it at the rendering boundary.
Versioning & release cadence
The package follows semver with these definitions (see the spec, §11):
- Patch — internal changes only. No observable behaviour change.
- Minor — adding optional fields, loosening existing validation. Backward-compatible.
- Major — removing/renaming fields, tightening validation, changing types.
minGroveVersion in each manifest is the lever that lets the ecosystem migrate across major bumps without graceful-degradation gymnastics. Pre-1.0 versions are unstable by convention.
Release cadence is decoupled from Grove app releases. This package's version, changelog, and publish trigger are independent of the desktop/web app's release cycle. A Grove app release does not auto-publish this package. The current trigger is manual (pnpm --filter @grove-notes/manifest-schema publish after a version bump); a tag-based GitHub Action (e.g. manifest-schema-v* tags) is planned but not yet wired.
License
MIT. Plugin authors (any license), the registry CI, and any external tooling can use this package without copyleft concerns.
