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
A schema file has three top-level keys: meta (required), imports (optional), and schema (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 }
}
} The version field 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
} Inside schema, the key @ defines the expected structure of the document root.
Other keys define named types that can be referenced with @TypeName.
Imports
The imports block 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
A document MAY declare its schema using the @schema tag 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 the schema block is required; meta and imports are 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
A tagged unit denotes a type constraint.
version @int // type: must be an integer
host @string // type: must be a string Since unit payloads are implicit, @int is shorthand for @int@ — which makes Styx schemas valid Styx.
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
These tags are built-in type constraints:
| Type | Description |
|---|---|
@string | any scalar |
@bool | true or false |
@int | any integer |
@float | any finite floating point number (JSON number syntax) |
@unit | the unit value @ |
@any | any 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
Scalar types can have constraints specified in an object payload.
@string accepts optional constraints:
| Constraint | Description |
|---|---|
minLen | minimum length (inclusive) |
maxLen | maximum length (inclusive) |
pattern | ECMAScript regular expression the string must match |
r[schema.constraints.string.pattern]
The pattern constraint 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-]+$" } @int accepts optional constraints:
| Constraint | Description |
|---|---|
min | minimum value (inclusive) |
max | maximum value (inclusive) |
port @int { min 1 , max 65535 }
age @int { min 0 } @float accepts optional constraints:
| Constraint | Description |
|---|---|
min | minimum value (inclusive) |
max | maximum value (inclusive) |
ratio @float { min 0.0 , max 1.0 }
temperature @float { min -273.15 } r[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 -Infinity are 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
@optional(@T) matches either a value of type @T or 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
@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: @default implies the field is optional. Using @optional(@default(...)) is redundant.
r[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
@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
@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
@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
@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
@tuple(@A @B @C ...) defines a fixed-length sequence where each position has a distinct type.
Unlike @seq which is homogeneous (all elements same type), @tuple is heterogeneous.
point @tuple ( @int @int ) // (x, y) coordinates
entry @tuple ( @string @int @bool ) // (name, count, enabled)
range @tuple ( @float @float ) // (min, max) r[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
@map(@K @V) matches an object where all keys match @K and all values match @V.
@map(@V) is shorthand for @map(@string @V).
env @map ( @string ) // string → string
ports @map ( @int ) // string → int r[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 per r[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
Named types are defined inside the schema block. Use @TypeName to 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
Recursive types are allowed. A type may reference itself directly or indirectly.
Node @object {
value @string
children @seq ( @Node )
} 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)
r[schema.flatten.constraints]
The argument to @flatten MUST 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 @flatten entries are allowed; their fields MUST NOT overlap with each other or with explicit fields.
Enums
@enum{...} defines valid variant names and their payloads.
Unlike other composite types that use sequence payloads (@union(...), @map(...)),
@enum uses 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)
@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 )) r[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.
r[schema.one-of.any-type]
While @one-of is 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 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.
r[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
r[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.
In the meta schema, the unit value @ is used as a wildcard meaning "any type reference" —
that is, any tagged unit value like @string or @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 @
}
}