Constraints
Schema annotations enforce data validity and control index creation.
Constraints are declared inside a .pg schema to control what data is allowed and how it is indexed. They come in two forms: property annotations that appear inline on a property declaration, and body constraints that appear on their own line inside a node or edge block.
Property annotations
These appear after the type on the same line as a property.
@key
Marks a property as the stable external identity of the node type.
node Person {
name: String @key
}- At most one
@keyper node type. - The property must be required (not nullable).
- Key values are immutable after creation.
- Implies
@index— no need to add both.
@unique
Ensures no two rows share the same value in this column.
node Account {
email: String @unique
}@index
Creates a BTree index for fast lookups, range scans, and sort operations.
node Event {
timestamp: DateTime @index
}For Vector(N) properties, @index creates an IVF-HNSW index for approximate nearest-neighbor search instead.
@embed
Marks a Vector(N) property for automatic embedding. Optionally specify the text source:
node Document {
body: String
embedding: Vector(1536) @embed(source: body)
}Without source, the vector must be provided directly at load time.
@description("...")
A human-readable description of the property, surfaced in tooling:
node Task {
status: enum(pending, done) @description("Current lifecycle state.")
}@instruction("...")
Prompt-level guidance for agent systems that read the schema:
node Task {
priority: I32 @instruction("Use 1 for critical, 2 for high, 3 for normal.")
}Property annotations summary
| Annotation | Applies to | Purpose |
|---|---|---|
@key | Any required scalar | Stable identity, implies index |
@unique | Any scalar | No duplicate values |
@index | Any scalar or vector | BTree index (scalar) or IVF-HNSW (vector) |
@embed | Vector(N) | Auto-embed from text source |
@description("...") | Any property | Documentation |
@instruction("...") | Any property | Agent prompt guidance |
Body constraints
These appear on their own line inside a node or edge block, after the property declarations.
@range(prop, min..max)
Restricts a numeric property to a closed range. Checked at load and mutation time.
node Score {
value: F64
@range(value, 0.0..100.0)
}@check(prop, regex)
Validates a string property against a regular expression.
node Account {
slug: String @key
@check(slug, "^[a-z0-9\\-]+$")
}@unique(p1, p2, ...)
Composite uniqueness constraint across multiple properties.
node Reading {
sensor_id: String
timestamp: DateTime
value: F64
@unique(sensor_id, timestamp)
}@index(p1, p2, ...)
Composite BTree index across multiple properties.
node Event {
category: String
timestamp: DateTime
@index(category, timestamp)
}@card(n..m)
Cardinality constraint on an edge type. Limits how many edges of this type can originate from a single source node.
edge ManagedBy: Employee -> Manager {
@card(1..1)
}| Pattern | Meaning |
|---|---|
@card(1..1) | Exactly one. |
@card(0..1) | At most one. |
@card(1..) | At least one. |
@card(0..) | No constraint (default). |
Body constraints summary
| Constraint | Scope | Purpose |
|---|---|---|
@range(prop, min..max) | Node | Numeric range validation |
@check(prop, regex) | Node | String regex validation |
@unique(p1, p2, ...) | Node, Edge | Composite uniqueness |
@index(p1, p2, ...) | Node, Edge | Composite BTree index |
@card(n..m) | Edge | Cardinality limit |
Constraint evaluation order
All constraints are evaluated during data ingestion (omnigraph load) and mutations (omnigraph change). If any constraint fails, the entire operation is rejected and no data is written. The schema compiler also validates that constraint references (property names, types) are consistent at compile time.