chroma.json and schemas
Every chroma.json field, the manifest the templates generate, and how to validate it in your editor and with the CLI.
chroma.json is the manifest the CLI reads for dev, build, and validate, and the file the browser plugin loader reads at runtime. Its shape is described by JSON Schema draft 2020-12 files published on this site.
Schema files
All schemas live under /schemas/chroma/ and are the canonical source. The aliases under /schemas/targ-apps/ exist for apps in the Targ-Apps suite; each alias references the same rules, so either URL validates a chroma.json.
| File | Describes |
|---|---|
chroma.schema.json | chroma.json, the app manifest |
chroma.plugin.schema.json | chroma.plugin.json, a plugin manifest |
chroma-fs.schema.json | chroma-fs.json, storage configuration |
chroma-pkg.schema.json | The engine's pkg/package.json |
jsconfig.schema.json | An editor jsconfig.json |
legacy-lib-registry.schema.json | The legacy-library registry |
Examples for each are in /schemas/chroma/examples/. Plugin-specific formats are under /schemas/chroma-plugins/.
Typical dynamic manifest
This is exactly what chroma new <name> --template dynamic generates (the standard template is the same apart from name):
{
"$schema": "https://docs.targapps.xyz/schemas/chroma/chroma.schema.json",
"name": "chroma-dynamic-app",
"mode": "dynamic",
"plugins": ["chroma-fs", "chroma-router", "chroma-style"],
"entry": "index.html",
"outDir": "dist",
"engine": { "vendorDir": "pkg", "rebuildWasm": true },
"build": { "jsEntry": "src/main.chr", "outfile": "bundle.js", "format": "esm", "minify": true, "sourcemap": true },
"package": { "zip": true },
"dev": { "port": 5173, "desktop": true }
}Two defaults to know about:
dev.desktop: truemakeschroma devopen Chroma Desktop. Pass--no-desktopor set it tofalseto stay in the browser. This affects development host only; it does not add a second production build target (Runtime modes).engine.rebuildWasm: truemakeschroma buildrecompile the engine, which needs a source-checkout install. Set it tofalseto build against thepkg/already in your project.
The plugins array is copied into every chroma build output. Removing a plugin changes what ships in dist/, not which runtime mode you choose at deploy time. Host-specific plugin behavior (for example Chroma-Fs IndexedDB vs the Desktop filesystem bridge) is documented under Runtime modes and Plugins.
Only static and dynamic are valid mode values. standard is a template name, not a mode.
Field reference
Three programs read chroma.json, and they do not agree on the full field list. In the table, Schema is whether chroma.schema.json accepts the field, CLI is whether chroma dev, chroma build, chroma new, or chroma add read it, and Linter is whether @chroma/linter (and the VS Code extension) reads it beyond schema validation. "Schema only" means the field is accepted and documented but nothing acts on it.
| Field | Type | Default | Schema | CLI | Linter | Effect |
|---|---|---|---|---|---|---|
$schema | string | - | accepted | no | selects the schema file | Editor hints; the linter resolves the schema from it. |
name | string | required | required | yes | no | dev window title, archive name when package.zip is true, required by validate. chroma new rewrites it. |
mode | "static" | "dynamic" | required | required | yes | yes (absent counts as dynamic) | Selects the build path. Any other value stops build with Unknown mode. |
entry | string | index.html | accepted | yes | yes | HTML entry file. |
outDir | string | dist | accepted | yes | no | Build output folder, recreated on every build. |
plugins | string[] | - | accepted | yes | yes | Plugin ids under plugins/<id>/. chroma new vendors them; validate and the linter check the folders; the browser plugins/loader.js loads them. |
engine.vendorDir | string | pkg | accepted | yes | no | Where the compiled engine lives in the project. |
engine.rebuildWasm | boolean | false | accepted | yes (dynamic) | no | Recompile the engine before bundling. Needs a source-checkout install. |
build.jsEntry | string | - | required in dynamic mode | yes | yes | Entry module for the bundle, for example src/main.chr. |
build.outfile | string | bundle.js | accepted | yes (dynamic) | no | Bundle file name. |
build.scripts, build.styles | string[] | - | accepted | yes (static) | yes (static) | Files minified individually. The linter reports a missing file. |
build.format | "esm" | - | accepted | no | no | Schema only. The CLI always passes --format=esm. |
build.minify, build.sourcemap | boolean | - | accepted | no | no | Schema only. The CLI always minifies and never emits source maps, whatever these say. |
package.zip | boolean | - | accepted | yes (dynamic) | no | Archive the build output. |
dev.port | integer | 4173 | accepted | yes | no | Port for chroma dev; --port overrides it. |
dev.desktop | boolean | false | accepted | yes | no | Open Chroma Desktop in develop mode (dynamic only). |
dev.hotReload | boolean | true | accepted | yes | no | Browser live reload; see Live reload. |
legacyLibraries | object[] | - | accepted | written by chroma add only | no | Declares vendored legacy UMD libraries (id, source, version, url, disabled). dev and build do not read it. See Legacy libraries. |
Fields the schema rejects
The schema sets additionalProperties: false on the root and on engine, build, package, dev, and every legacyLibraries item, so any key not in the table above fails schema validation. Four keys are read by the linter anyway:
| Field | Read by | Real behavior |
|---|---|---|
routes (object, hash to page file) | Linter | Each key should start with #/ (warning otherwise) and each value must be an existing file (error). In .chr mode each value must also end in .chr. The CLI ignores it. |
source.format | Linter | "chr" turns on the .chr rules. The CLI ignores it. |
lint.chrSource, lint.chrOnlySeverity | Linter | chrSource forces .chr mode on or off; chrOnlySeverity: "warning" downgrades the .chr extension errors to warnings. The CLI ignores them. |
build.chrEntry | Linter | Alternative entry checked when build.jsEntry is absent. The CLI never reads it (a dynamic build needs build.jsEntry). |
So a manifest that uses any of these gets chroma/schema errors from the linter next to the linter's own diagnostics, and chroma validate fails at its schema step (it exits before the structural checks). To keep chroma validate clean, leave them out; the linter infers .chr mode on its own when build.jsEntry ends in .chr or src/ contains a .chr file.
How the CLI finds a key
On macOS and Linux the CLI looks up each key by name at any depth in the file (with jq, or a text search when jq is missing), so a key name that appears twice, such as name or scripts, can resolve to the wrong one. On Windows (chroma.ps1) the CLI reads the exact paths shown above (engine.vendorDir, build.jsEntry, dev.port, and so on). Keep key names unique and use the paths in the table.
Validate
Add $schema as the first key so VS Code and other editors show completions and errors:
{
"$schema": "https://docs.targapps.xyz/schemas/chroma/chroma.schema.json",
"name": "my-app",
"mode": "dynamic"
}Then check the project from the terminal:
chroma validate # schema + structure
chroma validate --no-schema # structure only, works offlinechroma validate, the linter, and the schema check different things; the Linter page compares them. Flags and where the CLI looks for the schema catalog are in chroma validate. On Windows, chroma validate runs the structural checks only.
Common validation errors
| Message | Fix |
|---|---|
"mode" must be "static" or "dynamic" | Use dynamic for bundled apps; standard is only a chroma new template. |
dynamic mode requires build.jsEntry | Add "build": { "jsEntry": "src/main.chr" }. |
entry file not found | Create the HTML file or fix entry. |
build.jsEntry not found | Fix the path or create the file. |
engine vendor dir missing WASM pkg | Copy pkg/ from a template or re-run chroma new. |
plugin "<id>" missing at plugins/<id>/ | Vendor the plugin or remove it from plugins. |
Related
- Runtime modes — Portable (web) vs Executable (Desktop); manifest fields that affect dev host only
- CLI —
validate,dev,build, and the compile pipeline - Templates — default
dev.desktopandpluginsper template