Skip to content

API Reference

/

Data types

RecordId

The RecordId class provides type-safe record identifiers in SurrealDB. Each record ID consists of a table name and an ID value, represented as table:id in SurrealQL.

Import:

import { RecordId } from 'surrealdb';

Source: value/record-id.ts

  • Tb extends string - The table name (string literal type for type safety)

  • Id - The ID value type (string, number, object, etc.)

  • T - The type of the records in the table (optional, defaults to unknown). It is taken from the Table the record ID is created from. See Typed record IDs.

Note

T is a compile-time annotation only. It is not validated against the data returned by the server.

Create a new record identifier.

Syntax
new RecordId(table, id)

Parameter

Type

Description

tableTb | Table<Tb, T>

The table name, either as a string or a

Table

instance.

idId

The record ID value (string, number, object, array, or RecordId).

// String IDs
const user = new RecordId('users', 'john');
const post = new RecordId('posts', '123');

// Numeric IDs
const product = new RecordId('products', 42);

// Complex/structured IDs
const metric = new RecordId('metrics', { 
    service: 'api', 
    timestamp: 1234567890 
});

// Array IDs
const compound = new RecordId('items', ['type-a', 123]);

// Nested RecordId
const nested = new RecordId('links', new RecordId('users', 'alice'));

// Type-safe with generics
const typedUser = new RecordId<'users', string>('users', 'john');

A record ID created from a typed table keeps the table's record type. The methods that take a record ID then infer their result type, so you don't need to pass the record type on each call.

interface User {
    name: string;
    email: string;
}

const users = Table.of<User>()('users');
const id = new RecordId(users, 'john');   // RecordId<"users", string, User>

const user = await db.select(id);         // User | undefined (with `id`)
await db.update(id).merge({ name: 'John' });
await db.delete(id);

select, create, update, upsert and delete all infer the record type this way.

There are several ways to create a record ID, and they differ in the type they produce:

FormResulting type
new RecordId('users', 'john')RecordId<'users', string, unknown>
new RecordId<'users', string>('users', 'john')RecordId<'users', string, unknown>
new RecordId(Table.of<User>()('users'), 'john')RecordId<'users', string, User>
const id: RecordId<'users', string, User> = new RecordId('users', 'john')RecordId<'users', string, User>
new RecordId<'users', string, User>('users', 'john')RecordId<'users', string, User>
new RecordId(new Table<User>('users'), 'john')RecordId<string, string, User>
new RecordId(new Table('users'), 1)RecordId<'users', number, unknown>

There is no new RecordId<User>(...) shorthand, because the first type parameter is the table name. Take the record type from a typed table, or annotate the variable. A record ID built from a table declared as new Table<User>('users') keeps the record type, but its table name is only typed as string.

A typed record ID can be used where an untyped one is expected, but not where a different record type or table name is expected:

const a: RecordId = id;                                  // OK
const b: RecordId<'users', string, Post> = id;           // Error: wrong record type
const c: RecordId<'user', string, User> = new RecordId('users', 'john');   // Error: 'users' is not 'user'

If you pass an explicit type on the call, such as db.select<Post>(id), that type takes precedence over the record ID's own. Prefer letting the type come from the record ID.

The table name component, returned as a Table instance.

Type: Table<Tb, T>

const userId = new RecordId('users', 'john');
console.log(userId.table);      // Table { name: 'users' }
console.log(userId.table.name); // 'users'

The ID value component.

Type: Id

const userId = new RecordId('users', 'john');
console.log(userId.id); // 'john'

const productId = new RecordId('products', 42);
console.log(productId.id); // 42

Convert the record ID to its string representation.

Syntax
recordId.toString()

string - String representation in format table:id

const userId = new RecordId('users', 'john');
console.log(userId.toString()); // 'users:john'

const productId = new RecordId('products', 42);
console.log(productId.toString()); // 'products:42'

const complex = new RecordId('items', { type: 'widget', id: 5 });
console.log(complex.toString()); // 'items:{ type: "widget", id: 5 }'

Serialise the record ID for JSON.

Syntax
recordId.toJSON()

string - JSON-safe string representation

const userId = new RecordId('users', 'john');
console.log(JSON.stringify(userId)); // '"users:john"'

Check if two record IDs are equal.

Syntax
recordId.equals(other)

Parameter

Type

Description

otherunknown

Value to compare.

boolean - True if equal

const a = new RecordId('users', 'john');
const b = new RecordId('users', 'john');
const c = new RecordId('users', 'jane');

console.log(a.equals(b)); // true
console.log(a.equals(c)); // false
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Create record with specific ID
const user = await db.create(new RecordId('users', 'john'))
    .content({
        name: 'John Doe',
        email: 'john@example.com'
    });

// Select by record ID
const retrieved = await db.select(new RecordId('users', 'john'));

// Update by record ID
await db.update(new RecordId('users', 'john'))
    .merge({ status: 'active' });

// Delete by record ID
await db.delete(new RecordId('users', 'john'));
interface User {
    id: RecordId<'users', string>;
    name: string;
    email: string;
}

// TypeScript enforces correct table name
const userId: RecordId<'users', string> = new RecordId('users', 'alice');

// This would be a type error:
// const wrong: RecordId<'posts', string> = new RecordId('users', 'alice');

const user = await db.select<User>(userId);
// Time-series data with structured IDs
const metricId = new RecordId('metrics', {
    service: 'api',
    host: 'server-01',
    timestamp: 1234567890
});

await db.create(metricId).content({
    cpu: 45.2,
    memory: 78.1
});

// Compound keys
const sessionId = new RecordId('sessions', {
    userId: 'john',
    deviceId: 'device-123'
});

Use StringRecordId to pass a record ID string directly without parsing it into table and ID components. The string is sent as-is to SurrealDB.

import { StringRecordId } from 'surrealdb';

// Use a record ID string directly
const userInput = 'users:john';
const userId = new StringRecordId(userInput);
const user = await db.select(userId);

// Use in queries
const recordId = new StringRecordId('users:alice');
const result = await db.select(recordId);
// Create relationship using record IDs
const from = new RecordId('users', 'john');
const to = new RecordId('posts', '123');

const edge = await db.relate(
    from,
    new Table('likes'),
    to,
    { timestamp: DateTime.now() }
);

console.log('Edge from:', edge.in);  // RecordId('users', 'john')
console.log('Edge to:', edge.out);   // RecordId('posts', '123')
import { Uuid } from 'surrealdb';

// Generate time-ordered IDs
const userId = new RecordId('users', Uuid.v7());

// Generate random IDs
const sessionId = new RecordId('sessions', Uuid.v4());

await db.create(userId).content({
    name: 'Alice',
    created: DateTime.now()
});
// Table names are validated automatically
try {
    const invalid = new RecordId('invalid-table!', 'id');
} catch (error) {
    console.error('Invalid table name');
}

The RecordIdRange class represents a range of record IDs for querying multiple records. It takes the same type parameters as RecordId: RecordIdRange<Tb, Id, T>, where Id is the type of the bounds and T is the record type, taken from the Table it is created from.

Bounds are specified using the BoundIncluded and BoundExcluded classes, or undefined for unbounded:

  • new BoundIncluded(value) - inclusive bound (>= or <=)

  • new BoundExcluded(value) - exclusive bound (> or <)

  • undefined - unbounded (no limit)

new RecordIdRange(table, begin, end)

Parameter

Type

Description

tableTb | Table<Tb, T>

The table name, either as a string or a

Table

instance.

beginBound<Id>

Start of the range. Use

BoundIncluded

,

BoundExcluded

, or

undefined

.

endBound<Id>

End of the range. Use

BoundIncluded

,

BoundExcluded

, or

undefined

.

import { RecordIdRange, BoundIncluded, BoundExcluded } from 'surrealdb';

// Select range of users from 'a' (inclusive) to 'f' (exclusive)
const range = new RecordIdRange(
    'users',
    new BoundIncluded('a'),
    new BoundExcluded('f'),
);
const users = await db.select(range);

// Numeric range: 1 (inclusive) to 100 (inclusive)
const numRange = new RecordIdRange(
    'items',
    new BoundIncluded(1),
    new BoundIncluded(100),
);
const items = await db.select(numRange);

// Unbounded start, exclusive end
const upToF = new RecordIdRange(
    'users',
    undefined,
    new BoundExcluded('f'),
);

// Inclusive start, unbounded end
const fromA = new RecordIdRange(
    'users',
    new BoundIncluded('a'),
    undefined,
);

// Delete range
await db.delete(new RecordIdRange(
    'logs',
    new BoundIncluded('2024-01-01'),
    new BoundExcluded('2024-02-01'),
));

A range created from a typed table keeps the record type, in the same way as a typed record ID:

const users = Table.of<User>()('users');

const range = new RecordIdRange(users, new BoundIncluded('a'), new BoundExcluded('f'));
// RecordIdRange<"users", string, User>

const some = await db.select(range);      // User[] (with `id`)
await db.delete(range);

select, update, upsert and delete infer the record type from a range. A range created from a plain table name, such as new RecordIdRange('users', ...), is RecordIdRange<'users', string, unknown>, and you can still pass the type on the call: db.select<User>(range).

// Good: Type-safe
type UserId = RecordId<'users', string>;
const userId: UserId = new RecordId('users', 'john');

// Better: Enforce at compile time
function getUser(id: RecordId<'users', string>) {
    return db.select(id);
}
// Good: Type-safe with validation
const user = await db.select(new RecordId('users', 'john'));

// Avoid: String-based (no validation)
const user = await db.query('SELECT * FROM users:john').collect();
// Good: Structured composite key
const id = new RecordId('events', {
    userId: 'john',
    timestamp: Date.now()
});

// Avoid: String concatenation
const id = new RecordId('events', `john-${Date.now()}`);
// Good: Safe construction
try {
    const id = new RecordId(tableName, idValue);
    const record = await db.select(id);
} catch (error) {
    console.error('Invalid record ID format');
}

Was this page helpful?