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(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
body: String
}The node does not need to redeclare every property from the interface. When a property is omitted, the compiler injects it from the interface. If you do redeclare an inherited property locally, its type must match the interface.
For interface properties that carry @key, @unique, or @index, letting the
compiler inject them is the clearest way to preserve the interface behavior.
What the compiler checks
| Rule | Example violation |
|---|---|
| Unknown interface | Node implements Searchable, but no such interface exists. |
| Type mismatch | Interface declares title: String, node declares title: I32. |
| Nullability mismatch | Interface declares title: String, node declares title: String?. |
| Omitted property | Interface declares title, node omits it. The compiler injects title automatically. |
| Conflicting overlap | Two interfaces declare the same property name with incompatible types. |
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(title)
}
node Document implements Slugged, Searchable {
body: String
}If you want the inherited properties to stay visually explicit in the node definition, you can still redeclare them:
node Document implements Slugged, Searchable {
slug: String @key
title: String @index
embedding: Vector(1536) @embed(title)
body: String
}If two interfaces declare the same property name, the property type must be identical. Conflicting type 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(title)
}
node Decision implements Slugged, Searchable {
summary: String
status: enum(proposed, accepted, rejected, superseded)
}
node Signal implements Slugged, Searchable {
body: String
strength: enum(strong, moderate, weak)
}
node Actor implements Slugged {
name: String
role: String
}All three types share a slug key. Decision and Signal also share the
searchable title + embedding pair. If a type implementing Searchable
omits one of those properties, the compiler injects it from the interface.