Schema Reference
This page is the authoritative reference for declaring node and edge schemas with the Curiosity.Library SDK. Use it as a lookup when you're writing a connector, ingestion pipeline, or any code that talks to a Curiosity Workspace.
For the conceptual "why" — when to use a key vs a property, how schemas evolve, what to index — see Graph model and Schema design.
Two ways to declare a schema
You can declare a node schema either declaratively (with attributes on a C# class) or fluently (by calling builder methods). Edge schemas are always fluent — they're just a name.
Declarative (attributes on a class)
using Curiosity.Library;
[Node]
public class SupportCase
{
[Key] public string Id { get; set; }
[Property] public string Summary { get; set; }
[Property] public string Status { get; set; }
[Property] public int Priority { get; set; }
[Timestamp] public DateTimeOffset OpenedAt { get; set; }
}
var schema = Schema.NodeFromType<SupportCase>();
The class name (or [Node(Name = "…")]) becomes the node type. The [Key] property is the identity field; every node type has exactly one and it must be a string. The [Timestamp] property — if present — is what time filters and recency sort use.
Fluent (builder methods)
var supportCase = Schema
.NewNode("SupportCase", key: "Id")
.Field("Summary", FieldType.String)
.Field("Status", FieldType.String)
.Field("Priority", FieldType.Int32)
.Field("OpenedAt", FieldType.Time);
var hasDevice = Schema.NewEdge("HasDevice");
Use the fluent form when the shape isn't known at compile time — for example when a connector reads a remote source's metadata and maps it into a schema at startup.
Attribute reference
The C# SDK ships four attributes. Anything implied by other audits ([VectorIndex], [Encrypted], etc.) is not a Library attribute — those concerns are configured at the workspace level, not in code.
| Attribute | Applies to | Required | Notes |
|---|---|---|---|
[Node] |
class | yes | Marks a class as a node type. Name overrides the class name. |
[Key] |
property | yes | Exactly one per node type. Must be string. Inherits from [Property]. |
[Property] |
property | no | Maps a CLR property to a schema field. Name overrides the property name. |
[Timestamp] |
property | no | At most one per node type. Property type must be DateTimeOffset or Time. |
[Key] and [Timestamp] both derive from [Property], so on a single property only one of the three applies.
Schema-level constraints
These are enforced at registration time. Violating them throws when the schema is built.
- Node: exactly one
[Key]property of typestring. - Node: at most one
[Timestamp]property. - Node: field names
Type,UID,Timestamp,Edges,EdgeCountare reserved and cannot be used as properties. - Edge: must not declare a key (edges are not addressable by key).
- Edge: field names
Type,To,ToType,UID,Timestampare reserved. - Field names must be unique within a schema (case-sensitive). Re-adding the same field throws
Duplicated field {name}.
Field types
FieldType is the enum used by the fluent builder. The declarative form maps CLR types onto these automatically.
FieldType |
C# type | JSON wire shape | Notes |
|---|---|---|---|
String |
string |
string | Default for text. |
Boolean |
bool |
true / false |
|
Byte |
byte |
number (0–255) | |
SByte |
sbyte |
number (−128–127) | Signed byte. |
Char |
char |
string (1 char) | |
Int32 |
int |
number | |
Int64 |
long |
number / string | Large values may serialize as string to avoid loss. |
UInt32 |
uint |
number | |
UInt64 |
ulong |
number / string | |
Float |
float |
number | |
Double |
double |
number | |
Decimal |
decimal |
string | Encoded as a string to preserve precision. |
Time |
DateTime, DateTimeOffset, Time |
ISO-8601 string | Use for [Timestamp] properties. |
GeoPoint |
GeoPoint |
{ lat, lon } |
Resume indexing after enabling. |
Language |
Language |
ISO 639 code | |
UID128 |
UID128 |
base64 string | The workspace's internal node identifier. |
Collections
The same set of FieldType values is supported through three collection variants:
| Builder method | C# property type | Wire shape |
|---|---|---|
Field(name, type) |
scalar | scalar |
ListField(name, type) |
T[] or List<T> |
JSON array |
DictionaryField(name, type) |
Dictionary<string, T> |
JSON object |
TableField(name, type) |
(fluent only — tabular data) | array of arrays |
In the declarative form Curiosity infers ListField for arrays and List<T>, and DictionaryField for Dictionary<string, T>. Dictionaries must have string keys; any other key type throws at schema-build time. Nullable scalars (int?, DateTime?) collapse to their non-nullable form.
Edge schemas
An edge schema is just a name. Edges have no properties of their own at the schema level — the meaning of the edge lives in the type name and the two endpoints.
var hasDevice = Schema.NewEdge("HasDevice");
var assignedTo = Schema.NewEdge("AssignedTo");
Conventionally, name edges as a verb phrase that reads naturally from source to target: Case -> HasDevice -> Device. Add a reverse edge if you traverse both ways often: Device -> AppearsInCase -> Case. Both edges are independent — committing the forward one does not create the reverse.
Keys and identity
Keys define identity. Two nodes of the same type with the same key are the same node — re-ingesting will update, not duplicate.
Constraints:
- exactly one
[Key]per node type - type must be
string - non-empty; the SDK rejects empty keys at commit time
- stable over time; changing a key changes the node's identity
If your source doesn't expose a natural key, build one deterministically from fields that do uniquely identify the record (e.g. "jira:" + projectKey + "-" + issueNumber). Never use a generated UUID for the key — re-runs will create duplicates.
Indexing
What gets indexed for text or vector search is not a schema-level decision in code. You declare schemas, then configure indexes against them in the workspace (UI or admin API):
- text-search fields: see Text search
- vector-embedding fields and chunking: see Vector search
- entity extraction and NLP pipelines: see NLP pipelines
This keeps the schema layer small and lets you change retrieval behaviour without re-deploying connectors.
Aliases (conceptual)
Aliases are alternate strings that resolve to the same node — useful for spelling variants, abbreviations, and cross-source normalization. They're managed at the entity-extraction layer, not declared in the schema. See Entity capture.
Reserved / system node types
The workspace stores some configuration (pipelines, search indexes, scheduled tasks) as nodes in a hidden system namespace. You won't see them in user-facing search but they're inspectable for debugging through the admin UI.
Related pages
- Schema design — how to think about a schema before you write it.
- Graph query language — how to traverse the graph you just defined.
- Curiosity.Library SDK (C#) — the full client surface.
- Reindexing and re-embedding — what to do after a schema change.