SurrealDB has been built from the ground up to be the ultimate database for developers who want to build tomorrow's applications. On this page you can view the release notes for the different historic versions of SurrealDB, and for imminent future releases.

If you have an idea for SurrealDB, then we would love to hear from you.

Follow us on Github
Join our Discord community
Back to top

Release v1.2.1

Released on February 16, 2024

Bug fixes:

  • Fix an issue with WHERE clause on queries resolving record links or graph edges.
  • Fix MATH::SQRT_2 not parsing.
  • Fix a panic in span rendering.
  • Fix CLI output not displaying properly sometimes.

Release v1.2.0

Released on February 13, 2024


  • Bump MSRV to 1.75.
  • In-memory index store.
  • Show execution time in CLI.
  • knn brute force.
  • Implement support for remote JSON Web Key Sets.
  • Add support for LIVE SELECT in the SDK and CLI.
  • Add IF EXISTS to REMOVE TABLE statement.
  • Add READONLY keyword to DEFINE FIELD statement.
  • Add alias -V and --version flags for surreal version command.
  • Define types for subfields when defining array fields.
  • Add string::semver::compare, string::semver::major, string::semver::minor, string::semver::patch, string::semver::inc::major, string::semver::inc::minor, string::semver::inc::patch, string::semver::set::major, string::semver::set::minor, string::semver::set::patch methods.

Bug fixes:

  • Make record id string parsing never fail early.
  • Prevent overflow in math::power().
  • Fix error message pointing to wrong character.
  • Respect alias for dynamic field queries with type::field.
  • Remove min/1000 in tx:delp.
  • Replace custom JWT parser causing decoding issues.
  • Issue with scoring on complex queries.
  • Limit recursion depth when parsing nested RELATE statements.
  • Fix a bug where a non-empty list parser would parse empty lists.
  • Ensure an attempt to set a protected variable returns an error.
  • Fix duration addition in timeout causing overflow.
  • Panic invoking parameters and functions without a database.
  • Fix a bug where the kind parser would eat whitespace.
  • Fix WebSocket notification sending format.
  • Implement missing errors for missing clauses on DEFINE-statements.
  • Ensure advanced DEFINE PARAM parameters are computed correctly.
  • Ensure path idioms are correct when looping over.
  • Fix json failing to parse with trailing whitespace.
  • Fix the five second delay in Wasm after initial connection.
  • Add context to live query notifications.
  • Fix the modulo operator on sql2 and parser1.
  • Improve the js-surql value conversion for numbers.
  • Implement revision types for client/server communication.
  • Fix builtin error pointing to the wrong part.
  • Fix a panic when invalid builtin function names are passed to the executor.

Performance improvements:

  • Ensure compression is only enabled when response is a certain size.
  • In MTree large vector improvement.

Use JWKS to dynamically configure your token definitions

-- Specify the namespace and database for the token
USE NS abcum DB app_vitalsense;

-- Set the name of the token
DEFINE TOKEN token_name
  -- Use this token provider for database authorization
  -- Specify the JWKS specification used to verify the token
  -- Specify the URL where the JWKS object can be found
  VALUE ""

Define a READONLY field

DEFINE FIELD created ON resource VALUE time::now() READONLY;

Define types for subfields when defining array fields

DEFINE FIELD foo ON bar TYPE array<number>
-- Where <number> is a subfield of the array type.
-- It now defines the number subfield automatically instead of having to run this manually: 
DEFINE FIELD foo.* ON bar TYPE number

Release v1.1.1

Released on January 16, 2024

Bug fixes:

  • Fix WebSocket notification sending format.
  • Fix missing custom claims from token parameter.
  • Fix URL encoding in JS functions.
  • Fix panic when invoking parameters and functions without a database.

Release v1.1.0

Released on January 9, 2024


  • The type::is::record() function now accepts a second optional table argument, validating the record being - stored on the passed table.
  • Add time::micros(), time::millis() and time::from::nanos functions.
  • Add type::is::none() function.
  • Add object::entries(), object::from_entries(), object::len(), object::keys- () and object::values() functions.
  • Clean paths in the start command and honour ~.
  • CLI: Split results by comment.
  • Add surreal sql welcome message.
  • Add SURREAL_ROCKSDB_KEEP_LOG_FILE_NUM environment variable (default 20).
  • Support auth levels for basic auth (behind feature flag)
  • Add remainder/modulo operator.
  • Implement string prefixes: s, r, d and u.
  • Add ability to cast string to a Thing/Record ID.
  • Analyzers to support functions.
  • Support of subfields for embedding indexing.
  • Add live query API to Rust SDK.
  • Add Query::with_stats() to return query statistics along with the results.
  • Permissions are now always displayed for visiblity
  • Add a --beta flag to surreal upgrade to make installing the latest beta release easier.

Bug fixes:

  • Fix stack overflow in graph traversal.
  • Bugfix - parse error for invalid leading whitespace.
  • Fix memory leak caused by OTEL callbacks.
  • Fix wrong function name export and function name parsing.
  • The position of the LIMIT and ORDER clauses are now interchangable.
  • Fix index plan for idiom param value.
  • Fix bug where error offset could underflow.
  • Query results should be consistent and representative.
  • Indexes used with the operators CONTAINS [ ANY | ALL ].
  • Forward custom thrown errors in SIGNIN and SIGNUP queries.
  • Fix ORDER BY RAND() failing to parse when selecting specific fields.
  • Fix identifiers which look like numbers failing to parse.
  • Change math::median indexing for even length arrays.
  • Pass IP & Origin onto session used by scope queries.
  • Fix possible corruption of MTree and incomplete knn.
  • Allow array::flatten() to be used as an aggregate function.
  • Make SELECT ONLY deterministic.
  • Optional function arguments should be optional.
  • Default table permissions should be NONE
  • Bugfix: Fix inconsistant record parsing
  • Fix time regression in surrealdb.wasm binaries.
  • Fix computing futures in query conditions.
  • Fix issue with scoring on complex queries.
  • Fix ML support on Windows and enable the feature in Windows binaries.
  • Replace the custom JWT parser causing decoding issues.
  • Ensure compression is only enabled when response is a certain size.
  • Respect alias for dynamic field queries with type::field().
  • Prevent overflow in math::power().
  • Fix error message pointing to wrong character.
  • Expand logic for static value validation to improve DEFAULT clause handling.
  • Fallback to a string when record ID parsing fails.
  • Ensure an attempt to set a protected variable returns an error.
  • Fix duration addition in timeout causing overflow.
  • Fix a bug where a non-empty list parser would parse empty lists.
  • Limit recursion depth when parsing nested RELATE statements.
  • Ensure REMOVE statement does not only remove the first 1000 keys.
  • Fix BTree deletion bug.
  • Replace close method on live::Stream with a Drop trait implementation.

Performance improvements:

  • Enable compression on the HTTP connector.
  • Make REMOVE [ TABLE | DATABASE | NAMESPACE ] faster for TiKV and FoundationDB.
  • Repetitive expressions and idioms are not anymore re-evaluated.
  • Improve performance of CREATE statements, and record insertion.
  • Use specific memory allocators depending on OS.
  • Fix memory leak in Websocket implementation.

Get realtime updates in your Rust application with the Live Query API

v1.1.0 introduces a new Live Query API to the Rust SDK, for powerful realtime updates in your Rust applications.

// Select the namespace/database to use

// Listen to all updates on a table
let mut stream ="person").live().await?;

// Listen to updates on a range of records
let mut stream ="person").range("jane".."john").live().await?;

// Listen to updates on a specific record
let mut stream ="person", "h5wxrf2ewk8xjxosxtyc")).live().await?;

// The returned stream implements `futures::Stream` so we can
// use it with `futures::StreamExt`, for example.
while let Some(result) = {

// Handle the result of the live query notification
fn handle(result: Result<Notification<Person>>) {
    match result {
        Ok(notification) => println!("{notification:?}"),
        Err(error) => eprintln!("{error}"),

Object functions

It was previously impossible to iterate over objects, so we introduced some new functions to make working with object data structures easier.

LET $fruits = {
    apple: {
        name: "Apple",
        stock: 20,
    banana: {
        name: "Banana",
        stock: 40,

LET $num_fruit_types = object::len($fruits);
LET $num_fruit_total = math::sum((
    SELECT VALUE stock FROM object::values($fruits)

RETURN "We have " + <string> $num_fruit_types + " type of fruits.";
RETURN "We have " + <string> $num_fruit_total + " pieces of fruit in total.";

String prefixes

Strings can optimistically be parsed as Record IDs, Datetimes or as a UUID, if the content matches such a value. With string prefixes you get to decide what value a string holds.

-- Interpeted as a record ID, because of the structure with the semicolon:
RETURN "5:20";
-- Forcefully parsed as just a string
RETURN s"5:20";

-- This will be a record ID.
RETURN r"person:john";
-- This fails, as it's not a valid record ID.
RETURN r"I am not a record ID";

-- Example for a date and a UUID.
RETURN d"2023-11-28T11:41:20.262Z";
RETURN u"8c54161f-d4fe-4a74-9409-ed1e137040c1";

Deterministic SELECT ONLY

The ONLY clause is sometimes not deterministic. Selecting from an array, table or range with the ONLY clause now requires you to limit the result to 1.

-- Fails, not limited to 1 result and result can contain multiple outputs.
SELECT * FROM ONLY table_name;

-- Works! Resource gets returned, or NONE if no resource was found.
SELECT * FROM ONLY table_name LIMIT 1;

Optional function arguments

Optional function arguments on custom functions are now actually optional.

DEFINE FUNCTION fn::create::resource($required: string, $optional: option<string>) {
    // The $required argument is a string
    // The $optional argument is a string or NONE

// Previously you needed to pass NONE for optional arguments
fn::create::resource("Required argument", NONE);

// Now you simply omit the argument
fn::create::resource("Required argument");

Release v1.0.2

Released on December 21, 2023

Bug fixes:

  • Support connecting to beta servers from the Rust SDK.

Release v1.0.1

Released on December 14, 2023

Bug fixes:

  • Add a patch for GHSA-x5fr-7hhj-34j3 .
  • Tables defined without explicit permissions have NONE instead of FULL permissions.
  • Table permissions are always explicitly displayed with the INFO FOR DB statement.

Release v1.0.0

Released on September 13, 2023

After numerous beta releases crammed into just months of development, we are releasing SurrealDB v1.0.0! 🎉

Here follow some of our 1.0.0 highlights

Introducing new type validation methods in SurrealQL

v1.0.0 introduces new type validation methods. These new methods allow you to check which type any sort of value is on the go.

LET $value = "I am a string";


Guaranteed single item results with the new ONLY keyword

It was a previously difficult to select, create, update or delete just a single record, so we simplified it a bit.

// Will return an object, as we can guarantee just a single record is requested
SELECT * FROM ONLY person:tobie;

// This will throw an error, as multiple records are being created and returned
CREATE ONLY person:tobie, person:jaime;

Get realtime updates in your application with SurrealDB Live Queries

v1.0.0 introduces Live Queries. This powerful technology allows you to write applications where you can serve realtime updates to your frontend.

// You can initiate the live query in your application
LET $lq = LIVE SELECT * FROM person WHERE age > 18;
// And you can dispose the live query once it is no longer needed
KILL $lq;
// SurrealDB SDKs allow you to easily initiate live queries, and to process the received messages.
const lq = await'person', function({ action, result }) {
    if (action == "CREATE") {

// Once you no longer need the live feed of updates, you can dispose the live query
await db.kill(lq);

Efficiently index and search your data with SurrealDB Full Text Search

With Full Text Search, you can efficiently store and index data, and search through it.

// We can define an analyzer on our database,
// Then, define an index to dictate which content we want to index (yep, that easy)
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS snowball(english);

// We can then create some content
CREATE article SET content = "Join us at SurrealDB World, as we unveil our version 1.0.0 to the world!";
CREATE article SET content = "We will absolutely be at Surreal World!";

// And lastly, select some data from the article table.
// We use the special "@num@" operator, where we define a reference.
// We can then reference back in the search::* functions.
    search::score(1) AS score,
    search::offsets(1) AS offsets,
    search::highlight('<b>', '</b>', 1) AS highlighted
FROM article WHERE
    content @1@ 'world'

Allow or deny capabilities when starting your SurrealDB instance

All capabilities are disabled by default. This means that by default, you are not able to use any methods, embedded scripting functions, make outbound network calls, or access the database anonymously. Down below follows a set of examples to showcase how one can configure capabilities.

Capabilities are further documented in the Capabilities documentation.

# Allow all capabilities
user@localhost % surreal start --allow-all

# Allow all functions, except for custom functions
user@localhost % surreal start --allow-funcs --deny-funcs fn

# Allow all capabilities, but deny guest/anonymous access to the database
user@localhost % surreal start --allow-all --deny-guests

Revamped root users

It is now possible to define multiple root users in SurrealDB. This change did require some changes in the way that you start your database however.

With this change, you will now only initially have to provide the --user and --pass flags to create the initial root user, but once the first root user exists, they will no longer by utilized.

For more information, check out the Authentication guide, and the surreal start and DEFINE USER documentation.

# When you initially start the database, you can create the first root user.
user@localhost % surreal start --auth --user root --pass root file:database.db

# In the future, you don't need to specify the --user and --pass flags anymore
user@localhost % surreal start --auth file:database.db
// Afterwards, you are able to create multiple root users.

Strict typing in SurrealQL

v1.0.0 introduces a more strict and powerful typing system. It makes things more simple to understand, and it goes a long way in preventing all kinds of weird bugs in your schemas!

// Where you previously had to manually assert that a field does not contain NONE or NULL
DEFINE FIELD age ON person TYPE number ASSERT $value != NONE AND $value != NULL;

// You can now simply set the type and be ensured that only a number will be stored in that field.
DEFINE FIELD age ON person TYPE number;

// To make types optional, you can use the newly introduced "option<type>" type.
// This guarantees the field to be either empty (NONE), or a number.
DEFINE FIELD age ON person TYPE option<number>;

// We also introduces some new types!
DEFINE FIELD ratings 		ON movie 	TYPE array<number>;
DEFINE FIELD best_ratings 	ON movie 	TYPE array<number, 5>;
DEFINE FIELD unique_ratings ON movie 	TYPE set<number, 5>;

// And we made sure that the record type is also in-line with this new format
DEFINE FIELD author 		ON book 	TYPE record<person>;

// Lastly, it is now possible to set a union type.
DEFINE FIELD title 			ON article 	TYPE string | number;
// They can also be used in "nested" types
DEFINE FIELD description 	ON article 	TYPE option<string | number>;
DEFINE FIELD author 		ON article 	TYPE record<person | admin>;

Set a DEFAULT value on field definitions

// You previously needed to involve logic with the "VALUE" clause to set a default value.
DEFINE FIELD enabled ON user TYPE bool VALUE $value OR $before OR true;
// You can now simply use the DEFAULT clause for this.
DEFINE FIELD enabled ON user TYPE bool DEFAULT true;

// As an added bonus, when you only define a VALUE clause with a simple value,
// then that will also act as the default value.
DEFINE FIELD constant ON demo VALUE 123;
// This will act the same as the following
DEFINE FIELD constant ON demo VALUE 123 DEFAULT 123;

PERMISSIONS on global parameters and custom functions

Scope and anonymous users previously had access to every defined global parameter and function. You can now define these resources with a PERMISSIONS clause to protect them.

DEFINE PARAM $perms_none VALUE 'Nobody can see me' PERMISSIONS NONE;
DEFINE PARAM $perms_full VALUE 'Everybody can see me' PERMISSIONS FULL;
DEFINE PARAM $perms_scope VALUE 'Only admins can see me' PERMISSIONS WHERE $scope = 'admin';

DEFINE FUNCTION fn::perms::none() {
    RETURN 'Nobody can invoke me';

DEFINE FUNCTION fn::perms::full() {
    RETURN 'Everybody can invoke me';

DEFINE FUNCTION fn::perms::scope() {
    RETURN 'Only admins can invoke me';
} PERMISSIONS WHERE $scope = 'admin';

FOR, BREAK and CONTINUE statements

// Here, we loop through an array with three numbers.
// For any number under two, we skip the iteration
// For any number above two, we break the loop
// The result is that only when the number is two, we will create the "person:two" record.

FOR $num IN [1, 2, 3] {
    IF $num < 2 {

    IF $num > 2 {

    CREATE person:two;

// Here, we select the id for every person in the database, and we gift them a ticket to SurrealDB World.
FOR $person IN (SELECT VALUE id FROM person) {
        recipient: $person,
        type: "ticket",
        event: "SurrealDB World"

THROW statement

Did something unexpected happen, and do you want to throw an error to the client? Now you can!

LET $message = "Some error message";
THROW "Failed to perform action: " + $message;

COMMENT away on resource definitions!

// Give extra context about a certain resource
DEFINE TABLE user COMMENT "This table will store users!";

Less bulky IF ELSE statements

We found the IF ELSE statement to be a bit bulky at times. Now, when you use a block ({}) as the body of the statement, you can skip out on the THEN and END keywords!

// Previously
IF something = true THEN {
    RETURN 123;
} END;

// Now
IF something = true {
    RETURN 123;

More features for our embedded scripting functions

With fetch(), query(), value() and basically every SurrealQL function now being available within the embedded scripting functions, they are a very powerful extension to SurrealQL, and can be used to solve complex problems otherwise impossible!

Read more about them in the Embedded scripting functions documentation.

function() {
    const page = await fetch('');
    const people = await surrealdb.query('SELECT * FROM person');
    const auth = await surrealdb.value('auth');
    const uuid = surrealdb.functions.rand.uuid.v4();

Support for FLEXIBLE fields on SCHEMAFULL tables

SCHEMAFULL and SCHEMALESS functionality can now be used together, suitable for capturing schema-free log data.

// We want to define a SCHEMAFULL table
// But we want one field to allow any content
DEFINE FIELD settings on person FLEXIBLE TYPE object;
// We can then set the field value without a schema
CREATE person:test CONTENT {
    settings: {
        nested: {
            object: {
                thing: 'test'

Support code blocks and advanced expressions

It is now possible to run blocks of code, with support for an arbitrary number of statements, including LET and RETURN statements. This allows for writing advanced custom logic, and allowing for more complicated handling of data operations.

DEFINE FIELD average_sales ON metrics VALUE {
    LET $sales = (SELECT VALUE quantity FROM sales);
    LET $total = math::sum($sales);
    LET $count = count($sales);
    RETURN ($total / $count);

Define custom functions with DEFINE FUNCTION statements

SurrealDB now supports the ability to define global database-wide custom functions, which allow for complicated or repeated user-defined code, to be run seamlessly within any query across the database. Custom functions support typed arguments, and multiple nested queries with custom logic.

-- Define a global function which can be used in any query
DEFINE FUNCTION fn::get_person($first: string, $last: string, $birthday: string) {

    LET $person = SELECT * FROM person WHERE [first, last, birthday] = [$first, $last, $birthday];

    RETURN IF $person[0].id THEN
        CREATE person SET first = $first, last = $last, birthday = $birthday


-- Call the global custom function, receiving the returned result
LET $person = fn::get_person('Tobie', 'Morgan Hitchcock', '2022-09-21');

Release v0.3.0

Released on December 14, 2021


  • Enable query and session parameters to be defined on a JSON-RPC connection
  • Ensure subqueries can access encoding parent query and grand-parent queries
  • Add diff-match-patch functionality when updating document records
  • Separate authentication levels for Namespace and Database specific access
  • Authentication scope definition and setup, with user-defined authentication logic for each scope

Pre-defined aggregate analytics views

Aggregate views let you pre-compute analytics queries as data is written to SurrealDB. Similarly to an index, a table view lets you select, aggregate, group, and order data, with support for moving averages, time-based windowing, and attribute-based counting. Pre-defined aggregate views are efficient and performant, with only a single record modification being made for every write.

-- Drop all writes to the reading table. We don't need every reading.

-- Define a table as a view which aggregates data from the reading table
DEFINE TABLE temperatures_by_month AS
        count() AS total,
        time::month(recorded_at) AS month,
        math::mean(temperature) AS average_temp
    FROM reading
    GROUP BY city

-- Add a new temperature reading with some basic attributes
CREATE reading SET
    temperature = 27.4,
    recorded_at = time::now(),
    city = 'London',
    location = (-0.118092, 51.509865)

Release v0.2.0

Released on January 21, 2021


  • Parameters can be used to store values or result sets
  • Nested subquery functionality, with scoped parameters
  • Nested field query notation allowing nested arrays and objects to be queried
  • Mathematical operators for complex mathematical calculations in queries
  • Advanced functions for working with arrays, strings, time, validation, parsing, and counting

Release v0.1.0

Released on December 8, 2019


  • Multi-tenancy data separation, with namespaces and databases
  • Schemafull or schemaless tables with limitless document fields
  • Multi-table, multi-row, serialisable ACID transactions
  • Table fields, table change events, table indexes, and data constraints
  • Advanced data model including empty values, strings, numbers, objects, arrays, durations, and datetimes