Extensions & Plugins
Chroma extension manifest, hook points, and how plugins are vendored into projects.
Chroma extensions are browser-side JS packages declared in chroma.json and copied into plugins/<name>/ when you run chroma new or chroma install.
They follow the same vendoring model as Chroma-Fs (chroma-fs), with a chroma.plugin.json manifest for tooling, loaders, and documentation. See Plugin Standard for the full contract.
Plugin manifest (chroma.plugin.json)
Each plugin includes chroma.plugin.json at its root:
{
"name": "chroma-router",
"package": "@chroma/router",
"version": "0.1.0",
"entry": "index.js",
"loadAs": "router",
"hooks": {
"bootstrap": "bootstrapRouter"
},
"capabilities": ["routing", "fetch-intercept"]
}| Field | Meaning |
|---|---|
name | Folder id in plugins/<name>/ and chroma.json |
package | npm-style package id (@chroma/<feature>) |
loadAs | Key on the object returned by loadPlugins() |
hooks | Lifecycle hook point → exported function name |
capabilities | Tags for docs and generic loaders |
manifest.json (legacy) may still be present for backward compatibility; prefer chroma.plugin.json for new plugins.
Hook points
| Hook | Used by | Purpose |
|---|---|---|
bootstrap | plugins/loader.js | Run once at app startup |
fetch | @chroma/router | Intercept window.fetch for /api/* |
styles | @chroma/style | Inject tokens.css + utilities.css |
storage | chroma-fs | Pluggable persistence backends |
New hook points should be documented in Plugin Standard before use so loaders stay consistent across templates.
Declaring plugins
{
"name": "my-app",
"mode": "dynamic",
"plugins": ["chroma-fs", "chroma-router", "chroma-style"]
}Loader pattern
Templates ship plugins/loader.js:
export async function loadPlugins() {
const manifest = await fetch("./chroma.json").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;
}Call from 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"));Built-in extensions
| Plugin | Package name | Canonical source | Docs |
|---|---|---|---|
chroma-fs | @chroma/fs | Chroma-Fs/ | Chroma-Fs |
chroma-router | @chroma/router | Chroma-Router/ | Routing |
chroma-style | @chroma/style | Chroma-Style/ | Styling extension |
chroma-t2t | @chroma/t2t | Chroma-t2t/ | Chroma-t2t |
Engine extensions vs CLI plugins
| Location | Used by | Contents |
|---|---|---|
Chroma/extensions/styling/ | Chroma Desktop shell | tokens.css, utilities.css, variants.js |
Chroma-Style/ | Canonical @chroma/style package | Full plugin (chroma.plugin.json, index.js, CSS, variants) |
Chroma/cli/plugins/ | chroma new / chroma install vendoring | Synced copies of first-party plugins |
Run ./scripts/sync-chroma-plugins.sh after editing Chroma-Style/, Chroma-Router/, or Chroma-t2t/ to refresh all vendored copies.
Install & vendoring
chroma install copies plugins into ~/.chroma/plugins/:
chroma-fs— built from Chroma-Fs source when availablechroma-style— fromChroma-Style/when present in the workspace, elseChroma/cli/plugins/chroma-style/chroma-router— fromChroma-Router/when present, elseChroma/cli/plugins/chroma-router/chroma-t2t— fromChroma-t2t/when present, elseChroma/cli/plugins/chroma-t2t/
chroma new vendors declared plugins into the project. scripts/build-chroma-cli.sh packages all first-party plugins into the distributable Chroma-CLI/plugins/.
Authoring a custom extension
See Plugin Standard → Create a new plugin for the full checklist. In short:
- Create
plugins/my-extension/withchroma.plugin.json,package.json, andindex.js. - Add the folder name to
chroma.jsonpluginsarray. - Extend
plugins/loader.jswith animportbranch. - Document hook points in
chroma.plugin.jsonand in Plugin Standard if the extension is shared.
Keep extensions browser-only — no Node APIs, no native modules.