Types (kinds)
Every field's type is SurrealDB's own kind grammar, passed through verbatim — Surform adds no spellings and hides none. This page covers the shapes you'll use most; SurrealQL's kind grammar is the complete reference.
Scalars
namespace acme;
database shop;
table kinds {
schemafull;
a_string: string;
an_int: int;
a_float: float;
a_number: number;
a_decimal: decimal;
a_bool: bool;
a_datetime: datetime;
a_duration: duration;
a_uuid: uuid;
some_bytes: bytes;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE kinds TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE a_string ON kinds TYPE string;
DEFINE FIELD OVERWRITE an_int ON kinds TYPE int;
DEFINE FIELD OVERWRITE a_float ON kinds TYPE float;
DEFINE FIELD OVERWRITE a_number ON kinds TYPE number;
DEFINE FIELD OVERWRITE a_decimal ON kinds TYPE decimal;
DEFINE FIELD OVERWRITE a_bool ON kinds TYPE bool;
DEFINE FIELD OVERWRITE a_datetime ON kinds TYPE datetime;
DEFINE FIELD OVERWRITE a_duration ON kinds TYPE duration;
DEFINE FIELD OVERWRITE a_uuid ON kinds TYPE uuid;
DEFINE FIELD OVERWRITE some_bytes ON kinds TYPE bytes;
number accepts both ints and floats; decimal is the exact-base-10 type for money.
Option: "or nothing"
option<T> marks a field the record may leave out. It is equivalent to the union none | T — SurrealQL's parser desugars one into the other, Surform knows they are the same type, and the canonical spelling is always option<T>:
namespace acme;
database shop;
table person {
schemafull;
nickname: option<string>;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE person TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE nickname ON person TYPE option<string>;
Collections
array<T> lists, set<T> unique lists, both with optional fixed length; object for free-form nesting:
namespace acme;
database shop;
table order {
schemafull;
items: array<string>;
matrix: array<float, 3>;
lottery: set<int>;
meta: object;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE `order` TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE items ON `order` TYPE array<string>;
DEFINE FIELD OVERWRITE matrix ON `order` TYPE array<float, 3>;
DEFINE FIELD OVERWRITE lottery ON `order` TYPE set<int>;
DEFINE FIELD OVERWRITE meta ON `order` TYPE object;
Records: references to other tables
record<table> is a pointer to a record in a named table — the type SurrealDB's graph model runs on. List tables to constrain, or leave the list empty for any:
namespace acme;
database shop;
table post {
schemafull;
author: record<user>;
related: record<post | comment>;
parent: option<record<post>>;
anything: record;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE `post` TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE author ON `post` TYPE record<user>;
DEFINE FIELD OVERWRITE related ON `post` TYPE record<post | comment>;
DEFINE FIELD OVERWRITE parent ON `post` TYPE option<record<post>>;
DEFINE FIELD OVERWRITE anything ON `post` TYPE record;
Multiple tables inside record<…> join with | — it is a union. Which is the next stop:
Unions and literals
Types combine with |, and literal values are types — hand-rolled enums, one line:
namespace acme;
database shop;
table shirt {
schemafull;
size: 'small' | 'medium' | 'large';
id_or_code: int | string;
flags: array<'red' | 'green' | 'blue'>;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE shirt TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE size ON shirt TYPE 'small' | 'medium' | 'large';
DEFINE FIELD OVERWRITE id_or_code ON shirt TYPE int | string;
DEFINE FIELD OVERWRITE flags ON shirt TYPE array<'red' | 'green' | 'blue'>;
Inline object schemas
An object type can spell out its expected shape right in the header — the braces belong to the type grammar, not to a clause block:
namespace acme;
database shop;
table webhook {
schemafull;
payload: { a: string, b: number };
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE webhook TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE payload ON webhook TYPE { a: string, b: number };
Geometry and friends
Geography, tables-as-values, files, ranges, regexes — the remaining SurrealQL kinds all pass through:
namespace acme;
database shop;
table place {
schemafull;
location: geometry<point>;
area: geometry<polygon>;
attachment: file<photo>;
window: range;
pattern: regex;
};DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE place TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE location ON place TYPE geometry<point>;
DEFINE FIELD OVERWRITE area ON place TYPE geometry<polygon>;
DEFINE FIELD OVERWRITE attachment ON place TYPE file<photo>;
DEFINE FIELD OVERWRITE window ON place TYPE range;
DEFINE FIELD OVERWRITE pattern ON place TYPE regex;
(range is its own whole kind here — it takes no inner type in the grammar.)
How types compare
Two spellings that parse to the same kind are the same type: option<string> ≡ none | string, int | int ≡ int, and unions compare flattened and deduplicated. This matters when you normalize: the database prints its desugared view (none | string), and the compiler folds it back to the option<T> you meant — your file doesn't churn when the database round-trips it.