Plugin Standard
chroma.plugin.json contract, lifecycle hooks, folder layout, and how to author a new Chroma plugin.
Chroma plugins are browser-side ESM packages vendored into plugins/<id>/ when you run chroma new or chroma install. Each plugin ships a chroma.plugin.json manifest that describes its lifecycle hooks, entry module, and capabilities.
First-party reference implementations:
| Plugin | Package | Canonical source |
|---|---|---|
chroma-style | @chroma/style | Chroma-Style/ (workspace root) |
chroma-router | @chroma/router | Chroma-Router/ (workspace root) — GPLv3 |
chroma-t2t | @chroma/t2t | Chroma-t2t/ (workspace root) — GPLv3 |
chroma-fs | @chroma/fs | Chroma-Fs/ (built to dist/) |
Folder layout
plugins/my-plugin/
├── chroma.plugin.json # required — machine-readable contract
├── package.json # npm metadata + exports
├── manifest.json # optional legacy alias for docs/tooling
├── index.js # entry — exports hook functions + `plugin` object
└── … # CSS, helpers, assetsJSON Schemas
Published JSON Schema (draft 2020-12) files for plugin manifests and related config live under /schemas/chroma-plugins/:
| Schema | Validates | Canonical example |
|---|---|---|
chroma.plugin.schema.json | plugins/<id>/chroma.plugin.json | Chroma-Style/chroma.plugin.json |
chroma-fs.schema.json | App chroma-fs.json storage config | Chroma-Fs/chroma-fs.json |
routes-manifest.schema.json | api/routes-manifest.json route table | Chroma/example/api/routes-manifest.json |
Add a full HTTPS "$schema" URL for editor validation, e.g. Chroma engine plugins:
"$schema": "https://docs.targapps.xyz/schemas/chroma-plugins/chroma.plugin.schema.json"Targ-Apps-Finance vendored plugins use the TargApps manifest schema instead:
"$schema": "https://docs.targapps.xyz/schemas/targ-apps/chroma-plugin-manifest.schema.json"chroma.plugin.json
Style plugin (@chroma/style)
{
"name": "chroma-style",
"package": "@chroma/style",
"version": "0.1.0",
"description": "Design tokens, utility CSS, and cva presets for Chroma apps.",
"entry": "index.js",
"loadAs": "style",
"capabilities": ["styling", "design-tokens"],
"hooks": {
"styles": "injectStyles"
},
"assets": {
"stylesheets": ["tokens.css", "utilities.css"]
}
}Router plugin (@chroma/router)
{
"name": "chroma-router",
"package": "@chroma/router",
"version": "0.1.0",
"description": "HTTP-like route handlers for Chroma apps (browser JS, not Node).",
"entry": "index.js",
"loadAs": "router",
"capabilities": ["routing", "fetch-intercept"],
"hooks": {
"bootstrap": "bootstrapRouter"
},
"routing": {
"reservedMethods": ["GET", "POST", "PUT", "DELETE", "CUSTOM"],
"manifestPath": "./api/routes-manifest.json",
"pathConvention": "api/"
}
}| Field | Required | Meaning |
|---|---|---|
name | yes | Folder id used in chroma.json plugins array |
package | yes | npm-style package id (@chroma/...) |
entry | yes | Main module relative to plugin root |
loadAs | yes | Key on the object returned by loadPlugins() |
hooks | yes | Hook point → exported function name. @chroma/style lists only styles; call bootstrapStyle({ cva, cx }) from the app entry. |
capabilities | no | Tags for docs and generic loaders |
assets | no | Static files (stylesheets, etc.) |
routing | no | Router-specific manifest and path conventions |
Lifecycle and hook points
| Hook | When it runs | Used by |
|---|---|---|
bootstrap | Once at app startup via plugins/loader.js | All plugins |
styles | Before first paint (CSS injection) | @chroma/style |
fetch | After router bootstrap (internal) | @chroma/router (hijackFetch) |
storage | FS backend init | @chroma/fs (createStorage) |
plugin export
Every first-party plugin also exports a default plugin object for generic loaders:
export const plugin = {
name: "chroma-style",
capabilities: ["styling", "design-tokens"],
hooks: {
bootstrap: bootstrapStyle,
styles: injectStyles,
},
};
export default plugin;Declaring plugins in an app
{
"name": "my-app",
"mode": "dynamic",
"plugins": ["chroma-fs", "chroma-router", "chroma-style"]
}Loader pattern
Templates ship plugins/loader.js, which reads chroma.json and calls each plugin's hook exports:
export async function loadPlugins() {
const manifest = await fetch("./chroma.json", { cache: "no-store" }).then((r) => r.json());
const loaded = {};
for (const name of manifest.plugins ?? []) {
if (name === "chroma-fs") {
const { createStorage } = await import("./chroma-fs/index.js");
loaded.fs = await createStorage({ configPath: "./chroma-fs.json" });
}
if (name === "chroma-router") {
const { bootstrapRouter } = await import("./chroma-router/index.js");
loaded.router = await bootstrapRouter();
}
if (name === "chroma-style") {
const mod = await import("./chroma-style/index.js");
mod.injectStyles();
loaded.style = mod;
}
}
return loaded;
}Wire in src/main.js:
import { render, cva, cx } from "../pkg/chroma.js";
import { loadPlugins } from "../plugins/loader.js";
const plugins = await loadPlugins();
if (plugins.style) {
globalThis.__chromaStyle = plugins.style.bootstrapStyle({ cva, cx });
}
render(App, document.getElementById("app"));Create a new plugin (step by step)
- Scaffold — create
plugins/my-plugin/withindex.js,package.json, andchroma.plugin.json(use@chroma/styleor@chroma/routeras templates). - Define hooks — export the functions named in
chroma.plugin.jsonhooks, plus apluginobject with the same bindings. - Register — add
"my-plugin"tochroma.jsonpluginsarray. - Loader — add an
importbranch inplugins/loader.jsthat calls your bootstrap hook and assignsloaded.<loadAs>. - Sync (first-party only) — if the plugin lives in the workspace root (like
Chroma-Style/orChroma-Router/), run./scripts/sync-chroma-plugins.shso CLI templates andChroma-CLI/plugins/stay current. - Document — add hook points and API notes to this page or Extensions & Plugins.
Keep plugins browser-only — no Node APIs, no native modules.
Install and vendoring
chroma install copies plugins from the workspace canonical sources (when available) into ~/.chroma/plugins/:
chroma-fs— built fromChroma-Fs/sourcechroma-style— copied fromChroma-Style/when present, elseChroma/cli/plugins/chroma-style/chroma-router— copied fromChroma-Router/when present, elseChroma/cli/plugins/chroma-router/chroma-t2t— copied fromChroma-t2t/when present, elseChroma/cli/plugins/chroma-t2t/
chroma new vendors declared plugins into the new project. ./scripts/build-chroma-cli.sh runs ./scripts/sync-chroma-plugins.sh before assembling Chroma-CLI/.
Related docs
- Extensions & Plugins — manifest overview and built-in plugin table
- Styling extension —
@chroma/styleAPI (bootstrapStyle, presets, tokens) - API Routing —
@chroma/routerfile-based handlers - CLI —
chroma new,chroma install, templates