Skip to content

Real-time

Live queries

Live queries are SurrealDB's way to push changes to a client. Running a LIVE SELECT opens a subscription that sends notifications whenever matching records appear, change, or disappear, without needing to directly poll the database to do so.

That fits interactive UIs, dashboards, and small collaboration features. For replaying history or catching up a batch pipeline, use changefeeds and SHOW CHANGES instead.

The full grammar and edge cases live under LIVE SELECT in the reference. Below is how to think about subscriptions and what to watch for in production.

With LIVE SELECT *, every notification carries the full record: the new state for a create or an update, and the record as it was before deletion for a delete. The table must already exist, for example through a DEFINE TABLE statement or an earlier write, or the statement fails with The table 'person' does not exist. You receive a UUID as soon as the live query is registered. This UUID can later be passed in to a KILL statement when you want to stop.

LIVE SELECT * FROM person;
Output
u'b1f1d115-ad0f-460d-8cbf-dbc7ce48851c'

Message layout on the wire is described in the live query / WebSocket protocol section.

If you prefer patches instead of whole documents on update, use DIFF. Updates arrive as JSON Patch-style arrays, which can be smaller and easier to merge on the client when records are large.

LIVE SELECT DIFF FROM person;
Output
u'b87cbb0d-ca15-4f0a-8f86-caa680672aa5'

You can narrow the subscription the same way you would a normal SELECT: only records that match your predicate participate.

LIVE SELECT * FROM person WHERE age > 18;

The filter is not a query that runs again over the table. Each time a write changes a record, SurrealDB checks that one record against the live query: the WHERE clause decides whether the subscriber is notified, and the projection shapes what it receives. While it does so, these parameters describe the change:

ParameterValue
$event'CREATE', 'UPDATE' or 'DELETE'
$beforeThe record before the write, or NONE for a created record
$after, $valueThe record after the write, or NONE for a deleted record
$auth, $session, $token, $accessThe subscriber's own session, not the session that made the write

A filter on $event subscribes to some kinds of change only, such as new records:

LIVE SELECT * FROM person WHERE $event = 'CREATE';

A filter on $before also notifies when a record stops matching. The following live query notifies when a task becomes open, and again when an open task changes to another status:

LIVE SELECT * FROM task WHERE status = 'open' OR $before.status = 'open';

A deleted record is checked as it was before the delete, so a filter on its fields still matches it. These are the same parameters that a DEFINE EVENT statement reads, so a condition written for an event usually works as a live query filter as well. The LIVE SELECT reference has more examples.

Notifications reflect committed work, so a live event will not take place if a transaction is rolled back.

Under heavy concurrency, the system makes a best effort to preserve sensible ordering, but you should not assume a total order across all writers that matches commit order in every edge case.

A notification carries the record that changed, and each subscriber gets its own copy of it, reduced to the fields that subscriber may select. $value, $after and $before are bound to that same reduced copy, so none of them holds a field the notification itself would not carry.

This matters because a live query's projection and WHERE clause are both written by the subscriber, and either can read one of those parameters. A field the subscriber cannot select reads as NONE in both:

-- Reads a restricted field into the notification, under a name of the caller's choosing
LIVE SELECT $after.hidden AS leaked FROM test;

-- Reads nothing into the notification, but decides whether one is sent at all
LIVE SELECT * FROM test WHERE $after.hidden = 1;

The two would leak differently, which is why the parameters are bound to the reduced copy rather than the projection being restricted. A projection carries the value out, and renaming it with AS would take it past the check that drops restricted fields by name. A WHERE clause carries nothing out, so the record stays clean, and the subscriber would instead learn a value it cannot select from which changes produced a notification and which did not.

On the delete side $before covers both, since it is the only parameter still holding the record as it was.

From v3.0.0 onwards you can use session parameters in live queries, such as by binding names with LET before you open the subscription.

LET $table = 'measurement';
LET $location = 'Tallinn';
LIVE SELECT * FROM type::table($table) WHERE location == $location;

Was this page helpful?