Skip to main content
nanograph

Schema Language Reference

NanoGraph schemas are defined in .pg files. A schema declares node types, edge types, property types, and annotations. It is the source of truth for your graph's structure — all queries are validated against it at compile time.

Node declarations

A node type declares a named entity with typed properties:

node Person {
    name: String
    age: I32?
}

Nodes can inherit from a parent type:

node Employee : Person {
    employee_id: String @unique
}

An Employee has all Person properties plus its own. Queries binding $e: Employee can access both inherited and declared properties.

Edge declarations

An edge type connects two node types:

edge Knows: Person -> Person

Edges can have properties:

edge Knows: Person -> Person {
    since: Date?
}

Endpoint types are fixed — each edge type connects exactly one source type to one destination type. If a conceptual relationship spans multiple types, split it into separate edges (e.g. TouchedRecord: Action -> Record, TouchedOpportunity: Action -> Opportunity).

Type system

Scalar types

TypeDescriptionArrow type
StringUTF-8 textUtf8
BoolBooleanBoolean
I3232-bit signed integerInt32
I6464-bit signed integerInt64
U3232-bit unsigned integerUInt32
U6464-bit unsigned integerUInt64
F3232-bit floatFloat32
F6464-bit floatFloat64
DateCalendar dateDate32
DateTimeDate-time value parsed from datetime("...") literalsDate64 (epoch milliseconds)

Vector type

embedding: Vector(1536)

Fixed-size float vector for semantic search. The dimension must match your embedding model's output (e.g. 1536 for text-embedding-3-small). See search.md for the full embedding workflow.

Enum type

status: enum(active, completed, hold)

Enums define a closed set of allowed string values. Values are auto-sorted alphabetically and must be lowercase. Duplicate values are rejected at parse time.

List type

tags: [String]
notes: [String]?

Lists wrap any scalar type: [String], [I32], [F64], etc. List properties cannot have @key, @unique, @index, or @embed annotations.

Nullable

Append ? to make any type nullable:

rank: String?
age: I32?
tags: [String]?

Annotations

Annotations modify property behavior. They appear after the type:

slug: String @key
name: String @unique
email: String @index
embedding: Vector(1536) @embed(bio) @index
old_name: String @rename_from("previous_name")
title: String @description("Short human-readable label")
AnnotationDescription
@keyPrimary key for merge/upsert. One per node type. Auto-indexed. Required for edge endpoint resolution via from/to in JSONL data.
@uniqueUniqueness constraint enforced on load/upsert. Multiple per type. Nullable unique allows multiple nulls.
@indexCreates an index for the property: scalar index for scalar fields, vector index for Vector(dim) fields.
@embed(source_prop)Auto-generates embeddings from a String property when the vector field is missing/null during load/mutation processing. Target must be Vector(dim). See search.md for details.
@rename_from("old")Tracks property/type renames for schema migration.
@description("...")Optional semantic description for node types, edge types, and properties. Intended for describe --format json, SDK introspection, and agent context.
@instruction("...")Optional agent-facing guidance for node and edge types. Advisory only; it does not change query execution semantics.

Restrictions: List properties cannot have @key, @unique, @index, or @embed. @instruction is only valid on node and edge types, not on properties.

These annotations are metadata, not behavior:

  • describe --format json surfaces them in CLI introspection
  • TS and Swift describe() surface them in SDK schema metadata
  • query-level metadata appears in nanograph run table preambles
  • they do not change planning, execution, or load semantics

Example with agent-facing metadata:

node Task @description("Tracked work item") @instruction("Prefer querying by slug.") {
    slug: String @key @description("Stable external identifier")
    title: String @description("Short human-readable summary")
    status: enum(todo, doing, done) @description("Workflow state")
}

edge DependsOn: Task -> Task
  @description("Hard execution dependency")
  @instruction("Use only for direct blockers, not loose relatedness")

Naming conventions

  • Type names: PascalCase — Person, ActionItem, HasMentor
  • Property names: camelCase or snake_case, starting lowercase — name, createdAt, release_year
  • Edge names in queries: camelCase with lowercase first letter — schema HasMentor becomes hasMentor in queries. See queries.md for the conversion rule.

Comments

// Line comment

/* Block comment
   spanning multiple lines */

Schema migration

Edit your database's schema.pg file, then run:

nanograph migrate mydb.nano

The command diffs the old and new schema IR and generates a migration plan. Steps have safety levels:

LevelBehavior
safeApplied automatically (add nullable property, add new type)
confirmRequires --auto-approve or interactive confirmation (drop property, rename)
blockedCannot be auto-applied (add non-nullable property to populated type)

Use @rename_from("old_name") to track renames so data is preserved. See cli-reference.md for full command options.

Complete example

A Star Wars search schema with vector embeddings:

node Character {
    slug: String @key
    name: String
    alignment: enum(light, dark, neutral)
    era: enum(prequel, original, sequel, imperial)
    bio: String
    embedding: Vector(1536) @embed(bio) @index
}

node Film {
    slug: String @key
    title: String
    release_year: I32
}

edge HasParent: Character -> Character
edge SiblingOf: Character -> Character
edge MarriedTo: Character -> Character
edge HasMentor: Character -> Character
edge DebutsIn: Character -> Film

This schema demonstrates:

  • @key for merge/upsert identity and edge resolution
  • enum() for constrained categorical values
  • Vector(1536) with @embed(bio) for auto-generated embeddings
  • Multiple edge types between the same node types
  • Cross-type edges (DebutsIn: Character -> Film)