Schema
Schemas define the expected structure of Styx documents for validation purposes. They are optional — deserialization works with target types directly (e.g., Rust structs). Schemas are useful for text editors, CLI tools, and documentation.
Why Styx works for schemas
Styx schemas are themselves Styx documents. This works because of tags and implicit unit:
- A tag like
@stringis shorthand for@string@— a tag with unit payload - In schema context, tags name types:
@string,@int,@MyCustomType - Built-in tags like
@union,@map,@enumtake payloads describing composite types - User-defined type names are just tags referencing definitions elsewhere in the schema
For example:
host @string // field "host" must match type @string
port @int // field "port" must match type @int
id @union ( @int @string ) // @union tag with sequence payload The @union(@int @string) is:
- Tag
@unionwith payload(@int @string) - The payload is a sequence of two tagged unit values
- Semantically: "id must match @int or @string"
This uniformity means schemas require no special syntax — just Styx with semantic interpretation of tags as types.
In schema definitions, the unit value @ (not a tag) is used as a wildcard meaning "any type reference" —
that is, any tagged unit value like @string or @MyType.
Schema file structure
schema[schema.file] A schema file has three top-level keys:
meta(required),imports(optional), andschema(required).meta { id https://example.com/schemas/server version 2026-01-11 description "Server configuration schema" } schema { @ @object{ server @Server } Server @object{ host @string port @int{min 1, max 65535} } }
schema[schema.meta.version] The
versionfield in schema metadata MUST use datever format:YYYY-MM-DD(ISO 8601 date). This enables simple chronological ordering of schema versions.meta { id https://example.com/schemas/config version 2026-01-16 }
schema[schema.root] Inside
schema, the key@defines the expected structure of the document root. Other keys define named types that can be referenced with@TypeName.
Imports
schema[schema.imports] The
importsblock maps namespace prefixes to external schema locations (URLs or paths). Paths are resolved relative to the importing schema file. Imported types are referenced as@namespace.TypeName.meta { id https://example.com/schemas/app version 2026-01-11 } imports { common https://example.com/schemas/common.styx auth https://example.com/schemas/auth.styx } schema { @ @object{ user @auth.User settings @common.Settings } }
Schema declaration in documents
schema[schema.declaration] A document MAY declare its schema using the
@schematag at the document root. The value is either a URL/path string (external reference) or an inline schema object. Inline schemas use a simplified form: only theschemablock is required;metaandimportsare optional.// External schema reference @schema https://example.com/schemas/server.styx server {host localhost, port 8080}// Inline schema (simplified form) @schema { schema { @ @object{server @object{host @string, port @int}} } } server {host localhost, port 8080}
Types and constraints
schema[schema.type] A tagged unit denotes a type constraint.
version @int // type: must be an integer host @string // type: must be a stringSince unit payloads are implicit,
@intis shorthand for@int@— which makes Styx schemas valid Styx.
schema[schema.literal] A scalar denotes a literal value constraint. The unit value
@is also a literal constraint.version 1 // literal: must be exactly "1" enabled true // literal: must be exactly "true" tag "@mention" // literal: must be exactly "@mention" (quoted) nothing @ // literal: must be exactly @ (unit)
Standard types
schema[schema.type.primitives] These tags are built-in type constraints:
Type Description @stringany scalar @booltrueorfalse@intany integer @floatany finite floating point number (JSON number syntax) @unitthe unit value @@anyany value Composite type constructors (
@optional,@union,@seq,@tuple,@map,@enum,@one-of,@flatten) are described in their own sections. Modifiers (@default,@deprecated) are described in their own sections.
Type constraints
schema[schema.constraints] Scalar types can have constraints specified in an object payload.
schema[schema.constraints.string]
@stringaccepts optional constraints:
Constraint Description minLenminimum length (inclusive) maxLenmaximum length (inclusive) patternECMAScript regular expression the string must match schema[schema.constraints.string.pattern] The
patternconstraint uses ECMAScript (JavaScript) regular expression syntax as defined in ECMA-262. The pattern is implicitly anchored — it must match the entire string, not just a substring. Implementations SHOULD support at minimum the common subset: character classes, quantifiers, alternation, grouping, and Unicode escapes.name @string{minLen 1, maxLen 100} slug @string{pattern "^[a-z0-9-]+$"}
schema[schema.constraints.int]
@intaccepts optional constraints:
Constraint Description minminimum value (inclusive) maxmaximum value (inclusive) port @int{min 1, max 65535} age @int{min 0}
schema[schema.constraints.float]
@floataccepts optional constraints:
Constraint Description minminimum value (inclusive) maxmaximum value (inclusive) ratio @float{min 0.0, max 1.0} temperature @float{min -273.15}schema[schema.constraints.float.syntax] Float values use JSON number syntax: an optional minus sign, integer digits, optional decimal fraction, and optional exponent.
NaN,Infinity, and-Infinityare NOT valid float values.float = "-"? integer ("." digits)? exponent? integer = "0" | [1-9] digits digits = [0-9]* exponent = ("e" | "E") ("+" | "-")? digitsExamples:
3.14,-273.15,6.022e23,1E-10,0.0
Optional fields
schema[schema.optional]
@optional(@T)matches either a value of type@Tor absence of a value. Absence means the field key is not present in the object (it does not mean the field value is@).server @object{ host @string timeout @optional(@duration) }
Default values
schema[schema.default]
@default(value @T)specifies a default value for optional fields. If the field is absent, validation treats it as if the default value were present. The first element is the default value, the second is the type constraint.server @object{ host @string port @default(8080 @int{min 1, max 65535}) timeout @default(30s @duration) enabled @default(true @bool) }Note:
@defaultimplies the field is optional. Using@optional(@default(...))is redundant.schema[schema.default.validation] The default value MUST satisfy all validation rules of its type constraint. A schema with an invalid default value (e.g.,
@default(0 @int{min 1})) is itself invalid.
Deprecation
schema[schema.deprecated]
@deprecated("reason" @T)marks a field as deprecated. Validation produces a warning (not an error) when deprecated fields are used. The first element is the deprecation message, the second is the type constraint.server @object{ host @string // Old field, use 'host' instead hostname @deprecated("use 'host' instead" @string) }
Composite types
Objects
schema[schema.object]
@object{...}defines an object schema mapping field names (scalars) to schemas. By default, object schemas are closed: keys not mentioned in the schema are forbidden.To allow additional keys, use a special entry with key
@(unit key) to define the schema for all additional fields. If present, any key not explicitly listed MUST match the@entry's schema. The key@is reserved for this purpose and cannot be used to describe a literal unit-key field.// Closed object (default): only host and port allowed Server @object{ host @string port @int } // Open object: allow any extra string fields Labels @object{ @ @string } // Mixed: known fields plus additional string→string Config @object{ name @string @ @string }
Unions
schema[schema.union]
@union(...)matches if the value matches any of the listed types.id @union(@int @string) // integer or string value @union(@string @unit) // nullable string
Sequences
schema[schema.sequence]
@seq(@T)defines a sequence schema where every element matches type@T.hosts @seq(@string) // sequence of strings servers @seq(@object{ // sequence of objects host @string port @int }) ids @seq(@union(@int @string)) // sequence of ids
Tuples
schema[schema.tuple]
@tuple(@A @B @C ...)defines a fixed-length sequence where each position has a distinct type. Unlike@seqwhich is homogeneous (all elements same type),@tupleis heterogeneous.point @tuple(@int @int) // (x, y) coordinates entry @tuple(@string @int @bool) // (name, count, enabled) range @tuple(@float @float) // (min, max)schema[schema.tuple.validation] Validation checks that:
- The value is a sequence
- The sequence has exactly the expected number of elements
- Each element matches its corresponding positional type
Maps
schema[schema.map]
@map(@K @V)matches an object where all keys match@Kand all values match@V.@map(@V)is shorthand for@map(@string @V).env @map(@string) // string → string ports @map(@int) // string → intschema[schema.map.keys] Valid key types are scalar types that can be parsed from the key's text representation:
@string,@int, and@bool. Non-scalar key types (objects, sequences) are not allowed. Key uniqueness is determined by the parsed key value perschema[entry.key-equality]in the parser spec, not by the typed interpretation —"1"and"01"are distinct keys even if both parse as integer 1.
Named types
schema[schema.type.definition] Named types are defined inside the
schemablock. Use@TypeNameto reference them. By convention, named types use PascalCase (e.g.,TlsConfig,UserProfile). This is not enforced but aids readability and distinguishes user types from built-in types.TlsConfig @object{ cert @string key @string } server @object{ tls @TlsConfig }
Recursive types
schema[schema.type.recursive] Recursive types are allowed. A type may reference itself directly or indirectly.
Node @object{ value @string children @seq(@Node) }
Flatten
schema[schema.flatten]
@flatten(@Type)inlines fields from another type into the current object. The document is flat; deserialization reconstructs the nested structure.User @object{name @string, email @string} Admin @object{ user @flatten(@User) permissions @seq(@string) }Document:
name Alice, email alice@example.com, permissions (read write)schema[schema.flatten.constraints] The argument to
@flattenMUST be a named object type or a type alias to an object. Flattening unions or primitives is not allowed. If flattened fields conflict with explicitly declared fields in the same object, validation MUST fail. Multiple@flattenentries are allowed; their fields MUST NOT overlap with each other or with explicit fields.
Enums
schema[schema.enum]
@enum{...}defines valid variant names and their payloads. Unlike other composite types that use sequence payloads (@union(...),@map(...)),@enumuses an object payload because variants have names.status @enum{ ok pending err @object{message @string} }Values use the tag syntax:
@ok,@pending,@err{message "timeout"}.
Value constraints (one-of)
schema[schema.one-of]
@one-of(@type (value1 value2 ...))constrains values to a finite set. The first element is the base type, the remaining elements are allowed values.level @one-of(@string (debug info warn error)) env @one-of(@string (development staging production)) method @one-of(@string (GET POST PUT DELETE PATCH))schema[schema.one-of.validation] Validation first checks that the value matches the base type, then checks that the value is one of the allowed values. Error messages include typo suggestions when the value is close to an allowed value.
schema[schema.one-of.any-type] While
@one-ofis most commonly used with@string, it works with any base type:priority @one-of(@int (1 2 3 4 5)) enabled @one-of(@bool (true)) // must be true, false not allowed
Validation
schema[schema.validation] Schema validation checks that a document conforms to a schema. Validation produces a list of errors and warnings; an empty error list means the document is valid.
schema[schema.validation.errors] Validation errors MUST include:
- The path to the invalid value (e.g.,
server.port)- The expected constraint (e.g.,
@int{min 1, max 65535})- The actual value or its type
Common error conditions:
- Type mismatch: value doesn't match the expected type (e.g.,
"abc"for@int)- Constraint violation: value doesn't meet constraints (e.g.,
0for@int{min 1})- Missing required field: a non-optional field is absent
- Unknown field: a field not in the schema (for closed objects)
- Literal mismatch: value doesn't match a literal constraint
- Union failure: value doesn't match any variant in a union
schema[schema.validation.warnings] Validation warnings are non-fatal issues:
- Deprecated field: a field marked with
@deprecatedis present
Meta schema
The schema for Styx schema files.
schema[schema.meta.wildcard] In the meta schema, the unit value
@is used as a wildcard meaning "any type reference" — that is, any tagged unit value like@stringor@MyType. This is a semantic convention for the meta schema; it leverages the fact that@(unit) is a valid Styx value, and in schema context represents "match any type tag here".
meta {
id https://styx.bearcove.eu/schemas/schema
version 2026-01-16
description "Schema for Styx schema files"
}
schema {
/// The root structure of a schema file.
@ @object {
/// Schema metadata (required).
meta @Meta
/// External schema imports (optional).
imports @optional ( @map ( @string @string ))
/// Type definitions: @ for document root, strings for named types.
schema @map ( @union ( @string @unit ) @Schema )
}
/// Schema metadata.
Meta @object {
/// Unique identifier for the schema (URL recommended).
id @string
/// Schema version (datever format: YYYY-MM-DD).
version @string { pattern "^\\d{4}-\\d{2}-\\d{2}$" }
/// Human-readable description.
description @optional ( @string )
}
/// String type constraints.
StringConstraints @object {
minLen @optional ( @int { min 0 })
maxLen @optional ( @int { min 0 })
pattern @optional ( @string )
}
/// Integer type constraints.
IntConstraints @object {
min @optional ( @int )
max @optional ( @int )
}
/// Float type constraints.
FloatConstraints @object {
min @optional ( @float )
max @optional ( @float )
}
/// A type constraint.
Schema @enum {
/// String type with optional constraints.
string @optional ( @StringConstraints )
/// Integer type with optional constraints.
int @optional ( @IntConstraints )
/// Float type with optional constraints.
float @optional ( @FloatConstraints )
/// Boolean type.
bool
/// Unit type (the value must be @).
unit
/// Any type (accepts any value).
any
/// Object schema: @object{field @type, @ @type}.
object @object { @ @Schema }
/// Sequence schema: @seq(@type).
seq ( @Schema )
/// Tuple schema: @tuple(@A @B @C ...).
tuple @seq ( @Schema )
/// Union: @union(@A @B ...).
union @seq ( @Schema )
/// Optional: @optional(@T).
optional ( @Schema )
/// Enum: @enum{variant, variant @object{...}}.
enum @object { @ @Schema }
/// Value constraint: @one-of(@type (value1 value2 ...)).
one-of @seq ( @Schema @seq ( @any ))
/// Map: @map(@V) or @map(@K @V).
map @seq ( @Schema )
/// Flatten: @flatten(@Type).
flatten @
/// Default value: @default(value @type).
default @seq ( @union ( @string @Schema ))
/// Deprecated: @deprecated("reason" @type).
deprecated @seq ( @union ( @string @Schema ))
/// Type reference (user-defined type).
type @
}
}