Skip to content
Decl

Describe your data.

A semantic modeling language

Values, relationships, and rules.
One executable model.

A paper model of a data tree: demo branches into services and links. The services branch contains gateway on port 8080 and auth on port 9001. Blue reference threads connect a Link’s source to gateway and its target to auth. Attached tabs show the derived endpoints gateway:8080 and auth:9001. The relationship satisfies source != target.View largerA paper model of a data tree: demo branches into services and links. The services branch contains gateway on port 8080 and auth on port 9001. Blue reference threads connect a Link’s source to gateway and its target to auth. Attached tabs show the derived endpoints gateway:8080 and auth:9001. The relationship satisfies source != target.View larger
Trees describe structure. Graphs describe relationships. Decl models both.
See the model below

Structure and relationships, in one model

A paper model of a data tree: demo branches into services and links. The services branch contains gateway on port 8080 and auth on port 9001. Blue reference threads connect a Link’s source to gateway and its target to auth. Attached tabs show the derived endpoints gateway:8080 and auth:9001. The relationship satisfies source != target.A paper model of a data tree: demo branches into services and links. The services branch contains gateway on port 8080 and auth on port 9001. Blue reference threads connect a Link’s source to gateway and its target to auth. Attached tabs show the derived endpoints gateway:8080 and auth:9001. The relationship satisfies source != target.

A model you can read and run

This Service Graph describes two services and a link between them. Defaults fill their ports, expressions derive their addresses, and a rule prevents self-links. The services form a tree; ref<Service> connects them without copying their values.

services.decl
// Values, derived meaning, and relationships in one model.
type Service = {
name: /[a-z][a-z0-9-]*/
port?: 1024..65535 = 8080
endpoint = `${name}:${port}`
inbound_count = std.array.count($referrers(Link, "target"))
}
type Link = {
source: ref<Service>
target: ref<Service>
assert different_services: source != target
else error `a service must not link to itself`
}
type Topology = { services: Service[], links: Link[] }
export output demo: Topology = {
services: [{ name: "gateway" }, { name: "auth", port: 9001 }]
links: [{ source: services[0], target: services[1] }]
}
// External JSON documents use exactly the same model.
input deployed: Topology
Evaluated output
{
"demo": {
"services": [
{
"name": "gateway",
"port": 8080,
"endpoint": "gateway:8080",
"inbound_count": 0
},
{
"name": "auth",
"port": 9001,
"endpoint": "auth:9001",
"inbound_count": 1
}
],
"links": [
{
"source": "$.services[0]",
"target": "$.services[1]"
}
]
}
}

The output includes the resolved values and relationships: auth has one incoming link, and each endpoint is a path to a service. Change the model in the playground to see its output and diagnostics update.

Values describe content. References preserve identity. Two services can have identical settings. A reference says which one a link reaches.

Why does immutable data need references?

Not for mutation. For identity and relationships.

Give two services the same port: 8080, then connect the first to the second. Their content is equal; their places in the model are distinct.

References compare canonical target paths; ordinary values compare content. Moving an entity to a different key or index changes its identity in the model.

Explore identity and relationships →
Same content. Different targets.
{
"same_content": true,
"same_entity": false,
"target_port": 8080,
"target_path": "graph.services[1]"
}

Use the same model for the data you create and the documents you receive.

Describe

Keep types, relationships, derived values, and rules in one description.

Generate

Resolve defaults and derived values. Produce JSON, YAML, or text from the model.

Validate

Check external documents against those same rules. Get diagnostics that name the failed rule and its place in the data.

The same core describes ports, connections, propagated widths, and rules across connected components. The Hardware Interconnect example puts these relationships to work in a 2×2 crossbar, with runnable source and evaluated output.

More models and examples: configuration, test generation, and the evidence behind each case.

Immutable values. Pure evaluation. Deterministic results. A terminating core. Decl focuses on describing the meaning of data; the host application handles I/O and workflows. Read why Decl is shaped this way.

Applications can use that meaning too. Decl for AI agents and conceptual models shows how existing models and diagnostics can guide another tool’s work.

Install the implementation that fits your environment. All three share the same specified behavior and ship the CLI, formatter, and language server.

Terminal window
npm install -g decl-lang

TypeScript reference implementation. Node.js ≥ 22.

Installation and editor setup covers the next steps.

© 2026 Luuvish. Decl is open source under the MIT License.

Type: Literata and IBM Plex, under the SIL Open Font License. Built with Astro Starlight.