wats.sh
Reference

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_TOKEN

JSON uses the same object shape.

Checked-in templates

Alpha templates with placeholder env names only:

  • examples/config/wats.config.example.yaml
  • examples/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

FieldRule
versionMust be exactly 1.
defaultProfileNon-empty string and must exist in profiles.
profilesObject map of profile names to profile configs.
graph.apiVersionMust match vNN.N, for example v25.0.
graph.baseUrlAbsolute http: or https: URL.
whatsapp.wabaIdNon-empty string; no CR/LF/NUL.
whatsapp.phoneNumberIdNon-empty string; no CR/LF/NUL.
secret env namesNon-empty string; no CR/LF/NUL.
webhook.pathAbsolute safe path with at least one segment; rejects traversal and control chars.
webhook.maxBodyBytesInteger 1..10_485_760; defaults to 1_048_576.
service.hostNon-empty string; no CR/LF/NUL.
service.portInteger 1..65_535.
service.apiPrefixAbsolute 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_TOKEN

Live 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.

On this page