Linter
Check chroma.json, plugins, and .chr layout for mistakes before you run the app, from the terminal, CI, or VS Code.
The Chroma linter (@chroma/linter) validates your project manifests against the published JSON Schemas, then runs extra project rules: plugin folders, entry files, semver, and the .chr source layout.
In plain words: the linter reads your configuration files and tells you what is wrong before you run the app. In VS Code you see underlines and entries in Problems. In a terminal, the command exits with code 1 when it finds an error.
Which tool should I use?
| Tool | Use it when | What it checks |
|---|---|---|
chroma validate | You already have the Chroma CLI and want a fast check before dev or build | chroma.json and chroma-fs.json against their schemas, plus a short list of structural checks (macOS/Linux; Windows runs the structural checks only). --no-schema skips the schema step. --strict is accepted but has no effect. See chroma validate |
chroma-lint | CI, scripts, or JSON output | Everything the editor shows: schemas plus all project rules below, for a whole project or single JSON files |
| VS Code extension | You edit in VS Code or Cursor | The same rules as chroma-lint, shown as diagnostics on save and when the workspace opens. See VS Code extension |
chroma validate is built into the CLI. chroma-lint and the extension cover more rules: plugin manifests and versions, .chr layout, and the routes, source, lint, and build.chrEntry keys that chroma validate ignores and the schema rejects (see chroma.json).
Run it
From a Chroma app directory (the one with chroma.json):
npx chroma-lint --project .Install the CLI from the @chroma/linter package, or run its bin/chroma-lint.mjs directly.
| Flag | Purpose |
|---|---|
--project [dir] | Full lint (schemas and rules). Default directory is the current one |
<file.json> | Validate a single file against its schema (repeatable) |
--json | Print the result as JSON (with --project), for scripts and CI |
--schemas-dir <path> | Use a different schema folder |
Set CHROMA_SCHEMAS_DIR if the linter cannot find the schemas (for example, when the package is copied elsewhere). The exit code is 1 when any error-severity diagnostic exists; warnings do not fail the command.
# JSON output for CI
npx chroma-lint --project . --jsonWhat is validated
| Artifact | Schema |
|---|---|
chroma.json | chroma.schema.json |
chroma-fs.json | chroma-fs.schema.json |
plugins/*/chroma.plugin.json | chroma.plugin.schema.json |
Common problems
Schema errors use the code chroma/schema; open the schema named in $schema for the exact definition.
| Problem | Typical cause | Fix |
|---|---|---|
Missing name or mode | Incomplete manifest | Add "name": "my-app" and "mode": "static" or "dynamic" |
Invalid entry | Wrong start HTML | Use "entry": "index.html" and create that file |
build.jsEntry not found | Wrong bundler entry | In dynamic mode set "build": { "jsEntry": "src/main.chr", "outfile": "bundle.js" } and create src/main.chr |
chroma/plugin-dir-missing | plugins lists a plugin with no folder or no chroma.plugin.json | Run chroma install and recreate the project plugins, or remove the name from the list |
chroma/chr-only-extension | A .js file under src/ in .chr mode | Rename it to .chr and update imports. See chr format |
chroma/chr-entry-missing | No .chr entry configured | Set build.jsEntry to src/main.chr |
A minimal valid dynamic manifest:
{
"$schema": "https://docs.targapps.xyz/schemas/chroma/chroma.schema.json",
"name": "my-app",
"mode": "dynamic",
"entry": "index.html",
"outDir": "dist",
"engine": { "vendorDir": "pkg", "rebuildWasm": false },
"build": {
"jsEntry": "src/main.chr",
"outfile": "bundle.js",
"format": "esm"
}
}Project rules
The rule ids are exported as RULE_IDS from @chroma/linter. The core rules are:
| Code | Severity | Description |
|---|---|---|
chroma/schema | error | JSON Schema validation (draft 2020-12) |
chroma/plugin-dir-missing | error / warning | A plugin listed in chroma.json has no folder (error) or no chroma.plugin.json (warning) |
chroma/plugin-manifest-name | error | Plugin name differs from its folder name |
chroma/entry-missing | error | The entry HTML file is missing |
chroma/js-entry-missing | error / warning | build.jsEntry is missing or not found (dynamic mode) |
chroma/build-script-missing | error | A static build.scripts[] path is missing |
chroma/build-style-missing | error | A static build.styles[] path is missing |
chroma/plugin-entry-missing | error | A plugin's entry file is missing |
chroma/plugin-version-semver | error | Invalid semver in chroma.plugin.json |
chroma/plugin-version-sync | warning | chroma.plugin.json and package.json versions differ |
chroma/pkg-version-semver | error | Vendored pkg/package.json has an invalid version |
chroma/pkg-version-chroma-sync | warning | pkg/package.json differs from ../Chroma/VERSION; only checked when that file exists next to your project (a source checkout) |
chroma/chr-only-extension | error / warning | Legacy .js/.ts application source under src/ while .chr mode is active |
chroma/chr-entry-missing | error / warning | In .chr mode: build.jsEntry or build.chrEntry does not exist on disk (error), or neither is set while src/ exists (warning) |
chroma/chr-route-missing | error | In .chr mode: a routes value in chroma.json points to a missing file |
chroma/chr-route-extension | error / warning | In .chr mode: build.jsEntry, build.chrEntry, or a routes value is not a .chr path |
chroma/chr-syntax | error | A .chr file under src/ is not valid JavaScript (ES module, including JSX and arrow expressions such as () => count()) |
.chr mode
The .chr rules run when lint.chrSource is true, or when it is not set to false and any of these is true:
source.formatis"chr"build.jsEntryends with.chrbuild.chrEntryis set- at least one
.chrfile exists undersrc/
lint.chrOnlySeverity: "warning" downgrades the extension rules (chr-only-extension, and chr-route-extension for jsEntry and routes) from error to warning. Of these keys, the schema accepts none (source, lint, build.chrEntry), so setting them also produces a chroma/schema error. Rely on the automatic detection instead: a build.jsEntry ending in .chr or any .chr file under src/ turns the rules on. Projects with only src/**/*.js skip these rules.
Use it from code
import { lintChromaProject, toLspDiagnostics, RULE_IDS } from "@chroma/linter";
const result = lintChromaProject({ projectRoot: "/path/to/app" });
// result.diagnostics: file paths, 0-based ranges, code, severity
const lsp = toLspDiagnostics(result.diagnostics);Router and routes rules
The first three rules apply only to projects that use the multi-page router layout (src/lib/router.{chr,js} with src/pages/, or logic/router.{chr,js} with pages/). chroma-finance/chroma-routes is different: it runs for every project whose chroma.json has a routes key, in or out of .chr mode.
| Code | Severity | Description |
|---|---|---|
chroma-finance/router-import-missing | error | The router imports a page module that does not exist |
chroma-finance/page-not-routed | warning | A file in src/pages/ is not imported by the router |
chroma-finance/route-page-mismatch | warning | The routes map references a symbol that is not imported |
chroma-finance/chroma-routes | error / warning | In the chroma.json routes map: a key that does not start with #/ (warning) or a value that is not an existing file (error). The schema rejects routes itself |
To add a page: create src/pages/MyPage.chr, import it in src/lib/router.chr, add it to the routes map, and run Chroma: Lint Project.