Complex Record
Record(int), Record(uuid), and other typed record ID syntax
By default, SurrealDB generates string IDs for new records. The Record(Type) syntax lets you control the ID's value type, which flows through to all generated TypeScript types — input types, output types, and any FK fields that reference the model.
Syntax
model ModelName {
id Record(Type) @id
// ...
}Replace Type with one or more supported ID types. Multiple types create a union.
Supported Types
Four primitive types are valid inside Record():
| Type | Schema | TypeScript output | Required on create? |
|---|---|---|---|
int | Record(int) @id | CerialId<number> | Yes |
number | Record(number) @id | CerialId<number> | Yes |
string | Record(string) @id | CerialId<string> | No (auto-generated) |
uuid | Record(uuid) @id | CerialId<string> | No (auto-generated) |
You can also use tuple refs and object refs as ID types:
| Type | Schema | TypeScript output | Required on create? |
|---|---|---|---|
| Tuple ref | Record(TupleName) @id | CerialId<TupleName> | Yes |
| Object ref | Record(ObjName) @id | CerialId<ObjName> | Yes |
A plain Record @id without a type parameter defaults to string and is optional on create.
float is not a valid ID type. Use int for integers, number for auto-detected numerics, or string/uuid for string-based IDs. Other types like bool, date, decimal, duration, bytes, geometry, and any are also invalid as ID types.
Create Optionality
Whether the id field is required or optional in the create input depends on the ID type:
| Declaration | Create id field | Why |
|---|---|---|
Record @id | id?: string | SurrealDB auto-generates string IDs |
Record(string) @id | id?: string | SurrealDB auto-generates string IDs |
Record(uuid) @id | id?: string | Cerial injects rand::uuid::v7() |
Record(int) @id | id: number | No auto-generation for integers |
Record(number) @id | id: number | No auto-generation for numbers |
Record(TupleName) @id | id: TupleNameInput | No auto-generation for tuple IDs |
Record(ObjName) @id | id: ObjNameInput | No auto-generation for object IDs |
The rule is: if string is in the type, or it's uuid as the sole type, or it's a plain Record @id, the ID is optional because SurrealDB (or Cerial) can generate one. For everything else, you must provide the ID yourself.
uuid only makes the ID optional when it's the sole type in the declaration. In a union like Record(uuid, int), the ID is required — the uuid optionality rule doesn't apply when combined with other types.
Practical Examples
Integer IDs
model Product {
id Record(int) @id
name String
price Float
}// id is required — no auto-generation for integers
const product = await client.db.Product.create({
data: { id: 1, name: 'Widget', price: 9.99 },
});
product.id; // CerialId<number>
product.id.id; // 1
// Query with the integer ID directly
const found = await client.db.Product.findOne({ where: { id: 1 } });UUID IDs
model Session {
id Record(uuid) @id
token String
expiresAt Date
}// id is optional — Cerial auto-generates a UUID v7
const session = await client.db.Session.create({
data: { token: 'abc', expiresAt: new Date() },
});
session.id; // CerialId<string>
session.id.id; // '01942f3e-...' (auto-generated UUID string)
// Or provide your own UUID
const session2 = await client.db.Session.create({
data: {
id: '550e8400-e29b-41d4-a716-446655440000',
token: 'def',
expiresAt: new Date(),
},
});Union IDs
Pass multiple types separated by commas to accept more than one ID form:
model Asset {
id Record(string, int) @id
name String
}// Optional: string is in the union, so SurrealDB auto-generates when omitted
const a1 = await client.db.Asset.create({ data: { name: 'Auto' } });
// Provide a string ID
const a2 = await client.db.Asset.create({ data: { id: 'logo', name: 'Logo' } });
// Provide an integer ID
const a3 = await client.db.Asset.create({ data: { id: 100, name: 'Banner' } });
a1.id; // CerialId<string | number>Tuple and Object IDs
Structured types can be used as record IDs for composite keys:
tuple Coordinate {
x Int,
y Int
}
model Tile {
id Record(Coordinate) @id
terrain String
}// Tuple IDs are always required
const tile = await client.db.Tile.create({
data: { id: [10, 20], terrain: 'grass' },
});
tile.id; // CerialId<[number, number]>
tile.id.id; // [10, 20]Standalone Record Typing
Record(Type) can also be used on non-FK record fields — fields without a paired Relation. This is useful when you store a reference to a record but don't need Cerial's relation features (nested create, connect, disconnect):
model AuditLog {
id Record @id
targetId Record(int) // typed record ref, no Relation
action String
}// Output
interface AuditLog {
id: CerialId<string>;
targetId: CerialId<number>; // typed
}
// Input — accepts the raw value or a CerialId
const log = await client.db.AuditLog.create({
data: { targetId: 42, action: 'delete' },
});CerialId Generic
All record ID fields produce a CerialId<T> in the output type, where T matches the declared ID type:
| Declaration | Output type | .id returns |
|---|---|---|
Record @id | CerialId<string> | string |
Record(int) @id | CerialId<number> | number |
Record(uuid) @id | CerialId<string> | string (UUID) |
Record(Coord) @id | CerialId<Coordinate> | [number, number] |
Record(string, int) | CerialId<string|number> | string | number |
CerialId provides .id (the raw value), .table (the table name), .toString() for display, and .equals() for comparison. In input positions (create, where, connect), you can pass the raw value, a CerialId, a RecordId, or a StringRecordId — all are accepted via the RecordIdInput<T> union type.
See also:
- CerialId — Full API reference for the
CerialIdwrapper class - Record Type Inference — How FK fields automatically inherit ID types from target models