Config Wars - Chapter 3: CUE
Welcome to “Choosing a Schema for Your Robotics Stack!” This series is designed to help scaling robotics teams understand why schemas are useful for their configuration management and navigate which schema is best suited for their stack.
In this blog, we’ll do a deep dive into CUE, a popular choice for schemas in robotics. We’ll discuss its origin, features, and some of its pros and cons.
History / Motivation:
CUE, which is short for “Configure, Unify, Execute” was developed at Google by Marcel van Lohuizen, one of the original creators of Go. The original motivation stemmed from Google's internal struggles in managing complex, large-scale configurations for infrastructure and applications. JSON and YAML were too weak for expressing constraints, logic, or composition, so CUE set out to solve that.
It was meant to replace:
Handwritten configs, which were repetitive and error-prone
External scripts for validation
Inability to reuse and override structured data
Config and code being separate, creating mismatches when keeping them in sync
Instead of building and managing external tools to validate, patch, or generate configs, CUE itself is a fully programmable config engine. This means CUE wholly encapsulates the configuration data, the schema, and the logic (how to derive those config values).
Usage in Practice:
CUE is still niche, but it’s gaining adoption in environments where configs are large, layered, and dynamic.
It’s used by:
Teams at Google
Kubernetes projects that need validation beyond YAML
Broadly, teams where JSON Schema isn’t expressive enough, and logic-based merging is necessary
Community / Ecosystem:
CUE has a small, active core team of maintainers, including the original Go team led by van Lohuizen.
GitHub Activity:
Tooling:
CUE ships with a CLI that helps you validate, format, and export your configs
cue vet
: Validate data against constraintscue eval
: Merge constraints and output final datacue export
: Emit valid JSON/YAMLcue fmt
: Formatter, likegofmt
for CUEcue def
: Convert JSON/YAML into a base schema
Editor Support:
VS Code extension: First-class support with syntax highlighting, formatting, hover docs, and linting
JetBrains IDEs: Limited community support; no official plugin yet
Code Generation:
CUE is tightly integrated with Go, but struggles with other languages.
If you’re not using Go (which most robotics stacks aren’t), your best bet is to go from CUE → JSON Schema → code generation.
For static languages like Rust, this works well. For dynamic languages, you’ll need CI checks to make sure your code and schema don’t drift.
Feature Evaluation:
Validation Model:
CUE uses a constraint-based, structural validation model. Instead of separating types from values, CUE treats both as constraints, so you define them in the same place. In CUE, the schema is the set of constraints your data must satisfy.
Here’s an example:
RobotConfig: {
id: string & !=""
armLength: float & >0.1 & <2.0
payloadKg: int & >= 0 & <= 25
mode: "idle" | "active" | "emergency"
ip: string & =~ "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"
}
Each field has an inline constraint to prevent misconfigurations. The &
operator lets you intersect multiple constraints.
Here’s an instance that conforms to the schema:
myRobot: RobotConfig & {
id: "arm-001"
armLength: 1.2
payloadKg: 18
mode: "active"
ip: "192.168.1.42"
}
When you run cue vet
, it checks that all constraints from RobotConfig
are satisfied by myRobot
.
If we change payloadKg
to 100, the instance is now invalid:
myRobot.payloadKg: conflicting values 100 and int & >=0 & <=25:
conflicting values 100 and <=25
Code Generation:
CUE has poor codegen. There is no way to natively go from CUE to Python, Rust, or C++.
Here’s how you could work around this by going from CUE JSON Schema.
First, create a .cue
file. Here’s our robot.cue
example.
This defines a Robot
type with:
A required
robot_id
(string)A required
enabled
(bool)A
max_velocity
that must be ≥ 0A
sensors
list that defaults to an empty array
Robot: {
robot_id: string
enabled: bool
max_velocity: number & >=0
sensors: [...string] | *[]
}
Next, export it to JSON Schema using cue export
.
cue export robot.cue --out=jsonschema
Here’s the JSON Schema output!
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"robot_id": { "type": "string" },
"enabled": { "type": "boolean" },
"max_velocity": {
"type": "number",
"minimum": 0
},
"sensors": {
"type": "array",
"items": { "type": "string" },
"default": []
}
},
"required": ["robot_id", "enabled", "max_velocity", "sensors"]
}
From the JSON Schema, we can generate our Rust struct.
cargo install schemafy_cli
schemafy robot.schema.json > robot_config.rs
Here’s the output:
#[derive(Debug, Serialize, Deserialize)]
pub struct Robot {
pub robot_id: String,
pub enabled: bool,
pub max_velocity: f64,
pub sensors: Vec<String>,
}
This works, but it’s not first class. You’re stitching together a few different tools, which can cause some lossy translation. For example, going from CUE to JSON Schema means that you lose some of the CUE constraint logic if JSON Schema does not support it.
Composability / Overrides:
CUE does very well here as it has built composability into its language semantics.
CUE uses a logical conjunction (&
) to merge constraints and values. That means you can define reusable schemas, partial defaults, and environment-specific overrides as independent pieces and merge them when needed.
Here’s an example of a base + override:
BaseConfig: {
armLength: 1.2
payloadKg: <= 20
mode: "idle"
}
ProductionOverride: {
mode: "active"
payloadKg: 18
}
robot01: BaseConfig & ProductionOverride
Evaluating robot01
merges the two objects. All constraints are preserved and checked at merge time.
{
"armLength": 1.2,
"payloadKg": 18,
"mode": "active"
}
This construct allows you to build layered configs, either merging various components together or creating overrides on a per-robot basis. This makes CUE uniquely good for managing configs at scale!
Templating / Logic / Computation:
CUE supersedes the functionality of a templating engine. It’s a declarative language, so you can write rules, expressions, and constraints that safely compute values based on inputs.
CUE supports:
Arithmetic & expressions (
torque / (payload + 1)
)Conditional logic (
status: if emergency then "offline" else "ready"
)List comprehensions (
[for id in robotIDs { id: id, zone: "default" }]
)Constraint merging (
limit: >10
,limit: <=25
→limit: >10 & <=25
)
CUE is not Turing-complete. It can’t do:
Recursion
Unbounded loops
General-purpose function definitions
Import values at runtime
And that’s by design. It’s expressive enough to compute what you need, but also bounded enough to stay predictable. Sometimes, templating engines like Jsonnet and Jinja can be too powerful. They can introduce infinite loops and runtime failures that deliver a bad config, and therefore crash your application.
Self-Documentation / Readability:
CUE is reasonably self-documented. It has a strong type system, default values, and field declarations. However, compared to JSON Schema, it lacks richer metadata that are nice to haves with a schema language.
Field types are explicit:
name: string
payloadKg: int & >= 0 & <= 25
You can define enums and constraints inline, which makes intent very clear:
mode: "idle" | "active" | "emergency"
You can annotate with comments, and they persist through evaluation and formatting:
// Max load the robot can carry in kg
payloadKg: int & >= 0 & <= 25
You can also use cue def
or cue export
to generate a "flattened" version of the config—useful for introspection or documentation tooling.
That being said, it borrows a lot from Go, so for engineers not familiar with the language, there can be some ramp-up needed.
Summary:
CUE is a schema language purpose-built for configuration. It combines validation, logic, and composition into a single language, making it especially useful for teams managing complex, dynamic configs.
CUE’s strengths come from its logic and computation. You can write conditionals, compute derived values, and merge overrides natively. In terms of logic and expressiveness, CUE offers more out of the box than any other schema language we’ve evaluated.
The downside is that CUE has limited language support outside of Go. While the ecosystem is growing quickly, this will remain a problem in the short to medium term. Teams using CUE alongside other languages will need to translate CUE into JSON Schema to unlock code generation.
If you’re looking for a config language that has logic, inheritance, and validation all in one place, CUE is an option worth exploring.
In the next post, we’ll explore Protocol Buffers, a data serialization format that’s been popularized as a schema format in embedded systems.
See you in Chapter 4!
Config Wars Series Index
Config Wars - Chapter 4: Protocol Buffers (Protobuf) [Coming Soon]