Targ Apps Docs
Chroma

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"]
}
FieldMeaning
nameFolder id in plugins/<name>/ and chroma.json
packagenpm-style package id (@chroma/<feature>)
loadAsKey on the object returned by loadPlugins()
hooksLifecycle hook point → exported function name
capabilitiesTags for docs and generic loaders

manifest.json (legacy) may still be present for backward compatibility; prefer chroma.plugin.json for new plugins.

Hook points

HookUsed byPurpose
bootstrapplugins/loader.jsRun once at app startup
fetch@chroma/routerIntercept window.fetch for /api/*
styles@chroma/styleInject tokens.css + utilities.css
storagechroma-fsPluggable 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

PluginPackage nameCanonical sourceDocs
chroma-fs@chroma/fsChroma-Fs/Chroma-Fs
chroma-router@chroma/routerChroma-Router/Routing
chroma-style@chroma/styleChroma-Style/Styling extension
chroma-t2t@chroma/t2tChroma-t2t/Chroma-t2t

Engine extensions vs CLI plugins

LocationUsed byContents
Chroma/extensions/styling/Chroma Desktop shelltokens.css, utilities.css, variants.js
Chroma-Style/Canonical @chroma/style packageFull plugin (chroma.plugin.json, index.js, CSS, variants)
Chroma/cli/plugins/chroma new / chroma install vendoringSynced 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 available
  • chroma-style — from Chroma-Style/ when present in the workspace, else Chroma/cli/plugins/chroma-style/
  • chroma-router — from Chroma-Router/ when present, else Chroma/cli/plugins/chroma-router/
  • chroma-t2t — from Chroma-t2t/ when present, else Chroma/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:

  1. Create plugins/my-extension/ with chroma.plugin.json, package.json, and index.js.
  2. Add the folder name to chroma.json plugins array.
  3. Extend plugins/loader.js with an import branch.
  4. Document hook points in chroma.plugin.json and in Plugin Standard if the extension is shared.

Keep extensions browser-only — no Node APIs, no native modules.

On this page