Omnigraph
Schema

Nodes

Node types define the vertices of your graph with typed properties, an optional key, and constraints.

A node declaration creates a vertex type in the graph. Each node type gets its own storage table with an auto-generated id column plus one column per property.

Basic syntax

node Person {
    name: String @key
    age:  I32?
}

The identifier after node is the type name. Inside the braces you declare properties, each with a name, a type, and zero or more annotations.

Properties

Every property has a name, a colon, and a type. Supported scalar types include String, Bool, I32, I64, U32, U64, F32, F64, Date, and DateTime. You can also use enums, lists, vectors, and blobs.

node Paper {
    doi:       String  @key
    title:     String  @index
    year:      I32
    abstract:  String?
    keywords:  [String]
    embedding: Vector(1536) @embed
    status:    enum(draft, review, published)
}

Required vs optional

By default every property is required — a row cannot be inserted without it. Append ? to make a property nullable:

node Person {
    name: String       # required
    age:  I32?         # optional, may be null
}

Property annotations

Annotations appear after the type on the same line.

AnnotationPurpose
@keyStable external identity. At most one per type. Immutable after creation. Implies @index.
@uniqueNo two rows may share the same value in this column.
@indexBuild a BTree index for fast lookups and range scans.
@embedMark a vector property for automatic embedding. Optionally specify a source: @embed(source: title).
@description("...")Human-readable description, surfaced in tooling and documentation.
@instruction("...")Prompt-level guidance for agent systems that read the schema.

The @key annotation

A key is the stable, external identity of a node. It is how edges reference nodes in load files, how queries bind variables, and how agents address entities.

node Company {
    name: String @key
}

Rules:

  • At most one @key per node type.
  • The key property must be required (not nullable).
  • Key values are immutable — once a node is created, its key cannot change.
  • @key implies @index; you do not need to add both.

The @embed annotation

Used on Vector(N) properties to indicate that the vector should be populated from a text source property:

node Document {
    title:     String @index
    body:      String
    embedding: Vector(1536) @embed(source: body)
}

Body constraints

Constraints declared inside the node body (but outside any property line) apply to the type as a whole.

node Measurement {
    sensor_id:  String
    timestamp:  DateTime
    value:      F64

    @unique(sensor_id, timestamp)
    @index(sensor_id, timestamp)
    @range(value, 0.0..1000.0)
}
ConstraintPurpose
@range(prop, min..max)Restrict a numeric property to a closed range. Checked at load and mutation time.
@check(prop, regex)Validate a string property against a regular expression.
@unique(p1, p2, ...)Composite uniqueness constraint across multiple properties.
@index(p1, p2, ...)Composite BTree index across multiple properties.

Type-level annotations

Annotations can also appear directly after the opening brace to describe the type itself:

node Actor {
    @description("A person or system that performs actions within the workflow.")
    @instruction("When creating Actor nodes, always provide a role.")

    name: String @key
    role: String
}

Implementing interfaces

A node type can implement one or more interfaces to inherit a required set of properties:

interface Searchable {
    title:     String @index
    embedding: Vector(1536) @embed(source: title)
}

node Article implements Searchable {
    slug:  String @key
    title: String @index
    embedding: Vector(1536) @embed(source: title)
    body:  String
}

The compiler checks that every property declared in the interface is present in the node with a matching name, type, and nullability.

Full example

A schema from a decision-tracking graph with multiple node types:

interface Searchable {
    title:     String @index
    embedding: Vector(1536) @embed(source: title)
}

node Actor {
    name: String @key
    role: String
    bio:  String?
    @description("A person or system involved in decisions.")
}

node Decision implements Searchable {
    slug:      String @key
    title:     String @index
    summary:   String
    status:    enum(proposed, accepted, rejected, superseded)
    embedding: Vector(1536) @embed(source: title)
    @description("A recorded decision with its current lifecycle status.")
}

node Signal implements Searchable {
    slug:      String @key
    title:     String @index
    body:      String
    strength:  enum(strong, moderate, weak)
    embedding: Vector(1536) @embed(source: title)
    @description("An observation or data point that informs decisions.")
}

On this page