WCL Reference · explanation

Introduction

6 min read · 2026-08-20 · wcl 0.33.2-alpha

WCL is a typed configuration and schema language. A .wcl file carries the data: the ports, the replica counts, the URLs. It also carries the shape that data has to fit. One command, wcl check, holds the shape against the data, and it does so before the program that needs the configuration has started.

This chapter is about why that matters and what it buys you. It sets WCL against the formats you already use, and it introduces wdoc, the document generator built on the same language. If you would rather start typing, Quick start installs the tool and walks a first document from four lines to a checked schema.

What WCL is for

Configuration is the part of a system with no compiler. The code around it is type-checked, reviewed and tested. The file that decides which database it talks to is a bag of strings that nothing looks at until the process boots. So a missing key is a crash at 3am, a replicas: 600 is an outage, and a debug: true that outlived its pull request is an incident report.

The usual answer is to describe the shape somewhere else: a JSON Schema file, a Kubernetes CRD, a struct with #[serde(deny_unknown_fields)] in the program that reads it. That works, and it puts the description of the data in a different file, a different language, and often a different repository from the data itself. The two drift.

WCL's answer is that the shape belongs in the language. A .wcl file declares its own types, and wcl check holds the data against them. It is an ordinary command, so you can run it in CI, in a pre-commit hook, or on your laptop before you push. Nothing has to consume the file for wcl check to judge it.

The type system carries more than the strings, numbers and booleans of a data format:

How WCL differs from JSON, YAML and TOML

Take a real fragment. Here is a service deployed to two environments, written the way most projects write it today:

app.yamlyaml
service: checkout

environments:
  staging:
    url: https://staging.example.com
    replicas: 1
    debug: yes
  prod:
    url: https://example.com
    replicas: 6
    debug: no

Nothing in that file says replicas holds a number. Nothing says 600 would be absurd. Nothing says environments may hold staging and prod and not stagng. Every one of those is the consuming program's problem, discovered at startup, in production, by whoever is on call. And debug: yes is not even reliably a boolean: YAML 1.1 parsers read yes and no as true and false, YAML 1.2 parsers read them as the strings "yes" and "no". Which one you get depends on a library you did not choose.

The same data in WCL, with the shape written down beside it:

app.wclwcl
@block("env")
type Env {
  @inline(0) name: identifier
  url:      utf8
  replicas: u32
  debug:    bool
}

@document
type AppConfig {
  service: utf8
  @children("env") envs: list<Env>
}

service = "checkout"

env staging {
  url      = "https://staging.example.com"
  replicas = 1
  debug    = true
}

env prod {
  url      = "https://example.com"
  replicas = 6
  debug    = false
}

It is longer, and the extra length is the whole point: the top half is the contract, and it is the reason wcl check can answer a question about the bottom half. There is no debug: yes ambiguity to resolve, because yes is not a boolean in WCL. It is a name, and a name that nothing declares fails to resolve.

QuestionJSONYAMLTOMLWCL
CommentsNoYesYesYes, and wcl fmt keeps them
Scalar typesString, number, bool, nullThe same, guessed from the textPlus dates and timesSized integers, floats, units, bool, four string encodings, identifiers, symbols, none
Where the schema livesAnother file (JSON Schema)Another fileAnother fileThis file, or one it imports
Computed valuesNoAnchors and aliases reuse, never computeNoFull expressions, let bindings and functions
Who validatesThe consuming programThe consuming programThe consuming programwcl check, before anything consumes it
Reusing a fragmentCopy it&anchor / *aliasCopy itimport, let, functions, references

The last row is the one that compounds. A configuration format without composition grows by copying, and a file that grew by copying is a file where two of the copies disagree. Namespaces and imports covers how to split a document across files. Expressions and operators covers how to compute one field from another.

WCL is not a general-purpose language

There is no I/O, no mutation, no loops that can fail to terminate. Every expression evaluates to a value, and evaluation is lazy and cached. See How a document evaluates. What WCL adds over a data format is types, composition and checking, not a runtime.

The other half: wdoc

wdoc is a document generator written in WCL. You declare pages and sites as blocks, and wcl wdoc renders them to a static website, a Markdown tree or a PDF. The book you are reading is a wdoc site. It is not a second language: import <wdoc.wcl> pulls in a library of type declarations shipped inside the binary, so every block it gives you is an ordinary @block type of the kind you can declare yourself. The same checker reads it, and the same errors come back.

Introduction to wdoc is the full account, and it opens Part II. Quick start builds a site in a dozen lines.

How to read this book

This is one reference for both halves, in three parts.

PartWhat it covers
Front matterThis chapter and Quick start: what WCL is for and how it differs from a data format, then the shortest path from an empty folder to a checked document.
The WCL languageDocuments, values, collections, types, expressions, control flow, functions, namespaces, schemas, decorators, connections, evaluation, builtins, and the CLI.
wdocIts own introduction, then pages and sites, templates, themes, visibility and output targets. Then the block reference: text, code, callouts, tables, media, icons, math, data views, diagrams, charts, timelines, terminals, wireframes, and writing your own.

Read Part I in order the first time. Its first five chapters build on one another: Documents, fields and blocks is the shape of a file, Values and primitives is what goes in it, Lists, tensors and records is what those compose into, Types is how you name a shape, and Expressions and operators is how you compute one. After those five the chapters are largely independent, and Schemas is the one to reach for when a wcl check message surprises you.

Part II assumes Part I and otherwise stands alone. Read Introduction to wdoc for what it is and how the part is laid out, then Documents, pages and sites for the shape of a build. After that, look up the block you need.

Three conventions run through every chapter:

Where to go next