Constraints
Schema annotations enforce data validity and control index creation.
Constraints and annotations in a .pg schema control what data is allowed and
how it is indexed. They come in four forms: property annotations,
type annotations, body constraints, and edge cardinality.
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_flat vector index with the L2 distance metric, used by nearest() queries.
@embed
Marks a Vector(N) property for automatic embedding from a named text source:
node Document {
body: String
embedding: Vector(1536) @embed(body)
}The source property must be a String.
@description("...")
A human-readable description of the property, surfaced in tooling:
node Task {
status: enum(pending, done) @description("Current lifecycle state.")
}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_flat vector index (vector) |
@embed(prop) | Vector(N) | Auto-embed from a named text source |
@description("...") | Any property | Documentation |
Type annotations
These appear on a node or edge header, after the type name and before the body.
@description("...")
Documents the type itself:
node Task @description("Tracked work item") {
slug: String @key
}@instruction("...")
Prompt-level guidance for agent systems that read the schema. This is valid on node and edge types, not on individual properties.
edge DependsOn: Task -> Task @instruction("Use only for hard blockers")@rename_from("...")
Migration hint used by omnigraph schema plan to match a renamed node, edge,
or property to an accepted schema:
node Account @rename_from("User") {
full_name: String @rename_from("name")
}
edge ConnectedTo: Account -> Account @rename_from("Knows")Type annotations summary
| Annotation | Applies to | Purpose |
|---|---|---|
@description("...") | Node, Edge | Documentation |
@instruction("...") | Node, Edge | Agent guidance |
@rename_from("...") | Node, Edge, Property | Schema migration rename hint |
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)
}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 |
Edge cardinality
@card(n..m) is part of the edge declaration header, not a body constraint:
edge ManagedBy: Employee -> Manager @card(1..1)It limits how many edges of that type can originate from a single source node.
| Pattern | Meaning |
|---|---|
@card(1..1) | Exactly one. |
@card(0..1) | At most one. |
@card(1..) | At least one. |
@card(0..) | No constraint (default). |
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.