Config Reference (`@wats/config`)
The @wats/config application config shape: YAML/JSON validation, env-secret references, and redaction.
experimental · reviewed 2026-04-28
@wats/config validates YAML or JSON config into a frozen WatsConfig object and keeps secrets as environment-variable references — never raw credential values.
import {
loadConfig,
parseConfig,
validateConfig,
redactConfig,
ConfigValidationError
} from "@wats/config";Config shape
version: 1
defaultProfile: local
profiles:
local:
graph:
apiVersion: v25.0
baseUrl: https://graph.facebook.com
whatsapp:
wabaId: "1234567890"
phoneNumberId: "9876543210"
auth:
accessToken:
env: WATS_ACCESS_TOKEN
webhook:
path: /webhook
verifyToken:
env: WATS_VERIFY_TOKEN
appSecret:
env: WATS_APP_SECRET
maxBodyBytes: 1048576
service:
host: 0.0.0.0
port: 3000
apiPrefix: /v1
bearerToken:
env: WATS_SERVICE_TOKENJSON uses the same object shape.
Checked-in templates
Alpha templates with placeholder env names only:
examples/config/wats.config.example.yamlexamples/config/wats.config.example.json.env.example
The YAML and JSON templates parse through @wats/config and match the shape above: local and prod profiles, placeholder WABA/phone ids, env-secret refs for all secret-bearing fields, service routing defaults, no raw tokens. .env.example lists placeholder env names only; copy it to an ignored local file such as .env.local before filling real values. wats init writes the same files with no-overwrite defaults and blank secret values.
Secret model
Every secret-bearing field must be an object with an env string:
{ env: "WATS_ACCESS_TOKEN" }Secret-reference fields: auth.accessToken, webhook.verifyToken, webhook.appSecret, service.bearerToken. A raw string in any of these positions is rejected with ConfigValidationError code invalid_env_ref.
API
validateConfig(value)
Validates an unknown in-memory value and returns a frozen WatsConfig. Throws ConfigValidationError for malformed input; a host TypeError never escapes for expected bad input.
parseConfig(source, options?)
Parses a config string as JSON or YAML, then validates it.
const config = parseConfig(source, { format: "yaml" });If format is omitted, JSON is inferred when the string starts with { or [; otherwise YAML is assumed. YAML support is intentionally small and dependency-free: it covers the generated onboarding mapping shape, not the full YAML language.
loadConfig(filePath, options?)
Reads a .json, .yaml, or .yml file and returns validated config.
const config = await loadConfig("./wats.config.yaml");Unsupported extensions fail with unsupported_format; read failures fail with file_read_error.
redactConfig(value)
Validates and returns a copy where every env name under a secret reference is replaced with [REDACTED_ENV]. Non-secret fields stay visible. Use it for logs, diagnostics, and wats config print-style output.
Validation rules
| Field | Rule |
|---|---|
version | Must be exactly 1. |
defaultProfile | Non-empty string and must exist in profiles. |
profiles | Object map of profile names to profile configs. |
graph.apiVersion | Must match vNN.N, for example v25.0. |
graph.baseUrl | Absolute http: or https: URL. |
whatsapp.wabaId | Non-empty string; no CR/LF/NUL. |
whatsapp.phoneNumberId | Non-empty string; no CR/LF/NUL. |
| secret env names | Non-empty string; no CR/LF/NUL. |
webhook.path | Absolute safe path with at least one segment; rejects traversal and control chars. |
webhook.maxBodyBytes | Integer 1..10_485_760; defaults to 1_048_576. |
service.host | Non-empty string; no CR/LF/NUL. |
service.port | Integer 1..65_535. |
service.apiPrefix | Absolute safe path with at least one segment. |
Error taxonomy
ConfigValidationError carries code: ConfigErrorCode, path: string, and issues: ConfigIssue[].
Codes: invalid_config, invalid_source, parse_error, unsupported_format, file_read_error, invalid_version, missing_default_profile, invalid_profiles, invalid_profile, invalid_graph, invalid_api_version, invalid_base_url, invalid_whatsapp, invalid_env_ref, invalid_webhook, invalid_webhook_path, invalid_max_body_bytes, invalid_service, invalid_service_host, invalid_service_port, invalid_service_api_prefix.
Live testing profile
Use env-secret refs for any credentialed profile. Raw tokens do not belong in config files:
version: 1
defaultProfile: live-test
profiles:
live-test:
graph:
apiVersion: v25.0
baseUrl: https://graph.facebook.com
whatsapp:
wabaId: "${WATS_WABA_ID_FROM_ENV_OR_SECRET_STORE}"
phoneNumberId: "${WATS_PHONE_NUMBER_ID_FROM_ENV_OR_SECRET_STORE}"
auth:
accessToken:
env: WATS_ACCESS_TOKEN
webhook:
path: /webhook
verifyToken:
env: WATS_VERIFY_TOKEN
appSecret:
env: WATS_APP_SECRET
maxBodyBytes: 1048576
service:
host: 0.0.0.0
port: 3000
apiPrefix: /v1
bearerToken:
env: WATS_SERVICE_TOKENLive runs also require runtime env flags such as WATS_LIVE_ENABLE=1 plus domain-specific mutation opt-ins. Keep those out of checked-in config examples.
Persistence
The current schema has no persistence field. Not implemented yet: a persistence config introducing SQLite local paths and Postgres database-URL env-secret references such as WATS_DATABASE_URL. When it lands, database URLs stay env-secret refs — raw database credentials must not be committed, printed, or passed as CLI arguments.
Non-goals
@wats/config does not implement CLI file generation, the standalone service runtime, live Graph credential checks, secret storage/encryption/vault integration, or full YAML language support.