Appearance
Config schema
config.schema in matrix.json points to a JSON Schema describing the shape of valid configuration. The SDK does not enforce it — resolveConfiguredPackageConfig does no validation — but a number of downstream tools do. This page covers what to put in the schema, what reads it, and where validation actually happens.
Declaration
json
"config": {
"schema": "./config/config.schema.json",
"defaults": "./config/config.defaults.json",
...
}The path is relative to the package root. The file should be a valid JSON Schema document. Convention is ./config/config.schema.json; the actual filename is your choice as long as matrix.json points at it.
What goes in the schema
A typical schema covers:
- Required vs optional fields. Mark required fields explicitly so missing values fail loudly.
- Type constraints.
stringvsintegervsboolean, plus enums for fixed value sets (log levels, providers, modes). - Numeric ranges.
minimum,maximumfor limits. - String patterns. Regex constraints for IDs, URLs, etc.
- Object structure. Nested objects with their own required/optional fields.
- Default values. JSON Schema
defaultannotations — informational, do not affect resolution; theconfig.defaultsJSON is the one source of truth for runtime defaults.
A schema for a simplified chat package might look like:
json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Chat package configuration",
"type": "object",
"additionalProperties": false,
"required": ["logLevel"],
"properties": {
"logLevel": {
"type": "string",
"enum": ["debug", "info", "warn", "error"],
"description": "Minimum log level emitted by the chat runtime."
},
"auth": {
"type": "object",
"properties": {
"provider": {
"type": "string",
"enum": ["anthropic", "google", "github"],
"description": "Default identity provider."
}
}
},
"limits": {
"type": "object",
"properties": {
"maxMessages": {
"type": "integer",
"minimum": 1,
"maximum": 10000,
"description": "Maximum messages retained per conversation."
},
"timeoutMs": {
"type": "integer",
"minimum": 100,
"description": "Request timeout in milliseconds."
}
}
}
}
}Keep field descriptions — they are read by matrix config explain and surfaced in tooling.
What does NOT validate the schema
resolveConfiguredPackageConfig does not call any schema validator. It deep-merges layers and returns the result. If you supply env var MATRIX_FOO_LOG_LEVEL=banana, the resolver does not catch the typo — your runtime sees logLevel: 'banana' and either crashes when it tries to use it or silently misbehaves.
This is intentional: the resolver is small, dependency-free, and runs identically in tests and production. Validation is a separate concern.
What DOES validate
The schema is read and used by:
| Consumer | What it does with the schema |
|---|---|
matrix config explain <package> | Reads the schema, validates the resolved config, and prints which key came from which layer plus any validation errors. |
hivecast doctor --repair | Reads the schema for each installed package and flags configs that fail to validate. |
| Editor tooling | The JSON Schema can be referenced by $schema in your config.defaults.json so editors give autocomplete and inline validation. |
| Your own runtime (recommended) | Validate inside onBootstrap() before using the config. Use a small JSON Schema validator (ajv, etc.) and fail fast. |
The pattern for actor-side validation:
ts
import Ajv from 'ajv';
import schema from '../../config/config.schema.json';
class MyPackageRoot extends MatrixActor {
protected async onBootstrap() {
const config = await loadConfig();
const validate = new Ajv().compile(schema);
if (!validate(config)) {
throw new Error(`Invalid config: ${JSON.stringify(validate.errors)}`);
}
this.config = config;
}
}Schema vs defaults
These are two different files with overlapping concerns:
config.schema.jsondescribes what valid configuration looks like.config.defaults.jsonis one specific valid configuration.
The defaults must validate against the schema. If they don't, your package is broken on a fresh install. CI typically runs both files through a validator as a smoke test.
Schema and additionalProperties
Set additionalProperties: false on the root object and (recommended) on every nested object. Without it, a typo in an env var produces a new top-level key that silently slips into the config. With it, the validator flags the unexpected key.
json
{ "type": "object", "additionalProperties": false, "properties": { ... } }When to skip the schema
Small packages with no configuration at all skip both config.schema and config.defaults. The config block is optional. If you don't need tunables, leave it out.
A package with a single tunable (e.g., a debug flag) can still benefit from a one-property schema — it documents the tunable for users who go looking.
See also
- Defaults — the
config.defaults.jsonfile. - Overrides — the layering rules.
- Config explain —
matrix config explainwalkthrough. - Package config contract — the full
configblock.
Source:
projects/matrix-3/packages/core/src/runtime/PackageConfigResolver.ts:20-25(IPackageConfigContracttype), and theconfigblock in real packages likeprojects/matrix-3/packages/chat/matrix.json:135-156.