Omnigraph
Schema

Interfaces

Declare shared properties once and require multiple node types to include them.

An interface declares a set of properties that one or more node types must include. Interfaces keep schemas consistent when several types share the same fields — for example, a common title + embedding pair for anything that needs to be searchable.

Declaring an interface

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

An interface looks like a node without storage — it has a name and a list of typed, annotated properties.

Implementing an interface

Use the implements keyword after the node name:

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

The node must redeclare every property from the interface. The compiler checks that each one matches by name, type, nullability, and annotations. A mismatch is a compile error.

What the compiler checks

RuleExample violation
Missing propertyInterface declares title: String but the node omits it.
Type mismatchInterface declares title: String, node declares title: I32.
Nullability mismatchInterface declares title: String, node declares title: String?.
Missing annotationInterface declares @index on a property, node omits it.

Multiple interfaces

A node can implement more than one interface by separating them with commas:

interface Slugged {
    slug: String @key
}

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

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

If two interfaces declare the same property name, the type and annotations must be identical. Conflicting declarations are a compile error.

Interfaces with @key

An interface can require that implementing types use a particular property as their key:

interface Slugged {
    slug: String @key
}

node Page implements Slugged {
    slug:  String @key
    title: String
}

node Post implements Slugged {
    slug:    String @key
    content: String
}

This guarantees that every type implementing Slugged has a slug key, so queries and load files can address them uniformly.

No runtime concept

Interfaces are strictly compile-time. They do not create storage tables, do not appear in query results, and have no row identity. Their only purpose is to enforce structural consistency across node types during schema compilation.

Full example

A schema using interfaces to standardize searchable, slugged content:

interface Slugged {
    slug: String @key
}

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

node Decision implements Slugged, Searchable {
    slug:      String @key
    title:     String @index
    summary:   String
    status:    enum(proposed, accepted, rejected, superseded)
    embedding: Vector(1536) @embed(source: title)
}

node Signal implements Slugged, Searchable {
    slug:      String @key
    title:     String @index
    body:      String
    strength:  enum(strong, moderate, weak)
    embedding: Vector(1536) @embed(source: title)
}

node Actor implements Slugged {
    slug: String @key
    name: String
    role: String
}

All three types share a slug key. Decision and Signal also share the searchable title + embedding pair. The compiler enforces this — if a future contributor adds a new type that implements Searchable but forgets the embedding property, the schema will not compile.

On this page