Targ Apps Docs
Chroma

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:

PluginPackageCanonical source
chroma-style@chroma/styleChroma-Style/ (workspace root)
chroma-router@chroma/routerChroma-Router/ (workspace root) — GPLv3
chroma-t2t@chroma/t2tChroma-t2t/ (workspace root) — GPLv3
chroma-fs@chroma/fsChroma-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, assets

JSON Schemas

Published JSON Schema (draft 2020-12) files for plugin manifests and related config live under /schemas/chroma-plugins/:

SchemaValidatesCanonical example
chroma.plugin.schema.jsonplugins/<id>/chroma.plugin.jsonChroma-Style/chroma.plugin.json
chroma-fs.schema.jsonApp chroma-fs.json storage configChroma-Fs/chroma-fs.json
routes-manifest.schema.jsonapi/routes-manifest.json route tableChroma/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/"
  }
}
FieldRequiredMeaning
nameyesFolder id used in chroma.json plugins array
packageyesnpm-style package id (@chroma/...)
entryyesMain module relative to plugin root
loadAsyesKey on the object returned by loadPlugins()
hooksyesHook point → exported function name. @chroma/style lists only styles; call bootstrapStyle({ cva, cx }) from the app entry.
capabilitiesnoTags for docs and generic loaders
assetsnoStatic files (stylesheets, etc.)
routingnoRouter-specific manifest and path conventions

Lifecycle and hook points

HookWhen it runsUsed by
bootstrapOnce at app startup via plugins/loader.jsAll plugins
stylesBefore first paint (CSS injection)@chroma/style
fetchAfter router bootstrap (internal)@chroma/router (hijackFetch)
storageFS 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)

  1. Scaffold — create plugins/my-plugin/ with index.js, package.json, and chroma.plugin.json (use @chroma/style or @chroma/router as templates).
  2. Define hooks — export the functions named in chroma.plugin.json hooks, plus a plugin object with the same bindings.
  3. Register — add "my-plugin" to chroma.json plugins array.
  4. Loader — add an import branch in plugins/loader.js that calls your bootstrap hook and assigns loaded.<loadAs>.
  5. Sync (first-party only) — if the plugin lives in the workspace root (like Chroma-Style/ or Chroma-Router/), run ./scripts/sync-chroma-plugins.sh so CLI templates and Chroma-CLI/plugins/ stay current.
  6. 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 from Chroma-Fs/ source
  • chroma-style — copied from Chroma-Style/ when present, else Chroma/cli/plugins/chroma-style/
  • chroma-router — copied from Chroma-Router/ when present, else Chroma/cli/plugins/chroma-router/
  • chroma-t2t — copied from Chroma-t2t/ when present, else Chroma/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/.

On this page