Bundles and Host plugins
A bundle is a package whose package.json declares dsh.bundle.patch; the YAML patch inserts plugin entries. Give the package and rows unique names; use the Loader's existing YAML syntax, including !!js where expressions are needed. Read an existing patch before editing it: a matching override replaces the complete config.
Manifest
A Host-only bundle needs no dependencies, install scripts, or build tool:
{
"name": "@local/my-plugin",
"version": "1.0.0",
"private": true,
"type": "module",
"exports": { ".": "./index.js" },
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}cordis.patch.yml:
- insert:
- id: my-plugin
name: '@local/my-plugin'
config: {}Display metadata and icon
Plugin Manager cards, bundle details, component rows, and the Settings plugin inventory read display text and an icon from the manifest without activating the plugin. Put the title and description in locale/en.json (other languages such as locale/zh.json use the same fields), and declare the icon as a top-level icon in package.json:
{ "meta": { "title": "My Decoration", "description": "Draws a badge under the composer." } }{
"icon": "./icon.svg",
"exports": { "./package.json": "./package.json", "./locale/*.json": "./locale/*.json" },
"files": ["locale/*.json", "icon.svg"]
}icon is a path relative to the manifest directory; SVG, PNG, JPEG, and WebP up to 256 KiB are accepted, while absolute paths, URLs, paths outside the directory, and symlinks leaving it are rejected. Missing fields fall back to package.json name and description and to the panel's default artwork; malformed metadata produces a diagnostic and keeps the valid text.
Host plugin export forms
index.js exports one of these forms; do not mix them:
export function apply(ctx, config) {}with optionalexport const inject = ['tools']andexport const Config.- A service class as the default export.
Register every resource inside apply with ctx.effect or ctx.on and return its cleanup. A plugin that declares Config validates the row's config at activation; query Config.listConfigs for an installed plugin's schema before writing its config, and follow $defs references in the returned document.
Install, enable, and observe
plugin_manager install_bundle performs package installation and bundle selection; do not reproduce those steps with shell commands. Only pass approvedBuilds after the user explicitly approves the reported pending build scripts. Preserve returned failures and pending states; report success only after observing the requested capability.
list_plugins and list_bundles return exact identifiers for existing installations. set_plugin and set_bundle toggle them; remove_bundle removes a bundle. Inspect saved-state and activation outcomes separately: failed requires diagnosis, overridden means a higher-priority layer wins, and restart-required means the change is not live. Installing a new bundle can activate through HMR; replacing an installed package requires restart to load a fresh JavaScript module generation. Do not infer updated browser code from an unchanged slot id.