A structured text format for configuration and data.
Every value carries its own type: dates, timestamps, decimals, big integers, and binary are part of the grammar.
# comments are part of the format { service: `checkout-api`, port: 8080, enabled: T, deploy_after: TIMESTAMP(2026-01-15T10:30:00Z), price_cap: DECIMAL(199.00), # scale is data account_id: BIGINT(9007199254740993), signing_key: BINARY(SGVsbG8=), regions: [`eu-west-1`, `us-east-1`], }
Why STF
A format a reader can trust without a schema beside it.
Types in the syntax
DATE, TIMESTAMP, DECIMAL, BIGINT, and BINARY are grammar, so a reader never infers a type from a key name.
Exact decimals
DECIMAL(1.5) and DECIMAL(1.50) are different values. Scale survives the round trip, so money is representable.
Strict by design
Every rejection maps to exactly one documented code, and a conversion that would lose a type fails instead.
Canonical form
One byte encoding per value, so a document can be hashed, signed, and diffed byte-for-byte.
Record streams
A .stfs file is one document per line, and a malformed record never invalidates the rest of the stream.
Editor and CLI
stf check, fmt, lint, canon, and convert, plus a language server that reports the same codes as CI.
How it compares
Where a format permits a capability but implementations disagree.
| Capability | STF | JSON | JSON5 | YAML | TOML | Ion |
|---|---|---|---|---|---|---|
| Dates and timestamps as a distinct type | ✅ | ❌ | ❌ | 🟡 | ✅ | ✅ |
| Exact decimals, scale preserved | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Integers beyond 2⁵³ without loss | ✅ | ❌ | ❌ | 🟡 | 🟡 | ✅ |
| Binary data as a distinct type | ✅ | ❌ | ❌ | 🟡 | ❌ | ✅ |
| Comments | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Duplicate keys rejected | ✅ | ❌ | ❌ | 🟡 | ✅ | ❌ |
| One documented code per rejection | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Canonical form in the same specification | ✅ | ❌ | ❌ | 🟡 | ❌ | ❌ |
One specification, no dialects
Every rule below is normative, so two conformant parsers cannot disagree about a document.
§3— eleven value kinds, defined independently of any host language.§13— parse(serialize(v)) ≡ v, and no constructor inferred from string content.§14— canonical form: one byte encoding per value.§15— a mandatory nesting depth limit, defaulting to 64.codes— one documented code per rejection; message text is not normative.
@version(1.0) {at: TIMESTAMP(2026-01-15T10:30:00Z), level: `warn`} {at: TIMESTAMP(2026-01-15T10:30:04Z), level: `info`}