@nullable
Mark a field as accepting explicit null values — separate from NONE (absent).
Marks a field as accepting an explicit null value. Without @nullable, fields can only hold a typed value or NONE (absent) — never null.
Syntax
field Type @nullable // required but nullable: value or null
field Type? @nullable // optional and nullable: value, null, or NONEThe Three-State Model
Cerial cleanly separates SurrealDB's three field states:
| Schema | TypeScript Output | Allowed Values |
|---|---|---|
name String | name: string | value |
bio String? | bio?: string | value or NONE |
bio String @nullable | bio: string | null | value or null |
bio String? @nullable | bio?: string | null | value, null, or NONE |
?(optional) = the field can be absent (NONE). In TypeScript, this maps toundefined.@nullable= the field can hold an explicitnullvalue. In TypeScript, this maps to| null.- Both = the field can be a value,
null, or absent.
See Optional Fields for full details on NONE vs null semantics.
Basic Usage
model User {
id Record @id
name String // required, never null
bio String? @nullable // optional and nullable
deletedAt Date @nullable // required but nullable (soft delete)
}// deletedAt is required — always present in output, can be null
const user = await client.db.User.create({
data: { name: 'Alice', deletedAt: null },
});
// user.deletedAt: Date | null → null
// bio is optional and nullable — can be omitted, null, or a string
const user2 = await client.db.User.create({
data: { name: 'Bob' },
});
// user2.bio: string | null | undefined → undefined (NONE)On Record Fields
@nullable is commonly used on optional Record fields (foreign keys) to distinguish between "no relation" (null) and "field not set yet" (NONE):
model Post {
id Record @id
title String
authorId Record? @nullable
author Relation? @field(authorId) @model(User)
}// Create without author — authorId is NONE (absent)
const draft = await client.db.Post.create({ data: { title: 'Draft' } });
// Create with explicit null — authorId is null (unassigned)
const orphan = await client.db.Post.create({
data: { title: 'Orphan', authorId: null },
});
// Query by null (find unassigned posts)
const unassigned = await client.db.Post.findMany({
where: { authorId: { isNull: true } },
});
// Query by NONE (find posts without authorId set at all)
const noField = await client.db.Post.findMany({
where: { authorId: { isNone: true } },
});Disconnect Behavior
The @nullable decorator affects how disconnect works on relations:
| Schema | Disconnect sets FK to | Rationale |
|---|---|---|
Record? (no @nullable) | NONE (field removed) | Field can only be value or absent |
Record? @nullable | NULL (field set to null) | Field supports null, so null is preferred |
Default @onDelete Behavior
@nullable also affects the default @onDelete action for optional relations:
| Schema | Default @onDelete | Description |
|---|---|---|
Record? (no @nullable) | SetNone | FK removed (field absent) |
Record? @nullable | SetNull | FK set to null |
You can always override with an explicit @onDelete(Action).
Where Operators
The available null/none operators depend on the field's modifiers:
| Schema | isNull | isNone | isDefined |
|---|---|---|---|
String (required) | No | No | No |
String? (optional only) | No | Yes | Yes |
String @nullable (nullable only) | Yes | No | No |
String? @nullable (both) | Yes | Yes | Yes |
On Tuple Elements
@nullable is allowed on individual tuple elements. In fact, it is the only way to make a tuple element nullable — the ? modifier is not allowed on tuple elements because SurrealDB returns null (not NONE) for absent tuple positions.
tuple Coordinate {
x Float,
y Float,
z Float @nullable // element can be value or null
}// Create with null element
await client.db.Model.create({
data: { coord: [1.0, 2.0, null] },
});Do not use ? on tuple elements — it is not supported. Use @nullable instead to allow null values at specific positions.
Restrictions
@nullable is not allowed on:
- Object fields — SurrealDB cannot define sub-fields on a nullable object parent (
object | null). Use?for optional objects instead. - Tuple fields — Same limitation as objects. Use
?for optional tuples. Anyfields —CerialAnyalready includesnullin its union type, making@nullableredundant.@idfields — Record IDs are always present and cannot be null.@nowfields — Computed fields have no stored value to be null.
model Example {
id Record @id
address Address? // ✅ Optional object (can be absent)
address Address @nullable // ❌ Error — not allowed on object fields
coord Coordinate? // ✅ Optional tuple (can be absent)
coord Coordinate @nullable // ❌ Error — not allowed on tuple fields
}Allowed On
| Construct | Allowed |
|---|---|
| Model fields | ✅ |
| Object fields | ✅ (on sub-fields, not on the object field itself) |
| Tuple elements | ✅ (the only way to make elements nullable) |
| Enum values | ❌ |
| Literal fields | ❌ |
Mutual Exclusions
@nullable is compatible with most decorators. It is incompatible with:
@now— computed fields have no stored value to be null@id— the primary record identifier cannot be null
With @default
@default(null) requires @nullable on the field. Without it, a null default makes no sense since the field can't hold null:
model User {
id Record @id
bio String? @nullable @default(null) // ✅ Valid — omit → null stored
}