---
title: "Release 4.0 | SurrealDB"
description: "This release introduces the first beta of SurrealDB 4.0. Numbers keep their size and one storage engine runs everywhere. We would love to hear how it goes. The…"
url: https://surrealdb.com/releases/4.0
---

# SurrealDB 4.0

[Latest beta: 4.0.0-beta.1](https://surrealdb.com/releases/4.0#release-4-0-0-beta-1) [All releases](https://surrealdb.com/releases)

**Releases.** 1 beta on the 4.0 line, with the release date and full notes for each below.

**Latest.** 4.0.0-beta.1, released Oct 28, 2026, is the newest beta on this line and the first set of notes below.

**Pre-release.** A beta line: try it out, and pin a stable release for production while the line is still in beta.

1. ![Babcock](https://surrealdb.com/assets/static/babcock.lo4rnVg1.svg)
2. ![ING](https://surrealdb.com/assets/static/ing.X3I6S3_V.svg)
3. ![British Airways](https://surrealdb.com/assets/static/british-airways.KEsZiwV-.svg)
4. ![Nvidia](https://surrealdb.com/assets/static/nvidia.DaIEuMil.svg)
5. ![Apple](https://surrealdb.com/assets/static/apple.D5pq4flY.svg)
6. ![SpaceX](https://surrealdb.com/assets/static/spacex.CQJEk-IL.svg)
7. ![Samsung](https://surrealdb.com/assets/static/samsung.CH-vQgnb.svg)
8. ![adidas](https://surrealdb.com/assets/static/adidas.DdTC5qhk.svg)
9. ![Tencent](https://surrealdb.com/assets/static/tencent.paQmLxyy.svg)
10. ![Alibaba](https://surrealdb.com/assets/static/alibaba.B16idgfM.svg)
11. ![PolyAI](https://surrealdb.com/assets/static/poly-ai.c3w_fAg6.svg)
12. ![Later](https://surrealdb.com/assets/static/later.Ds736jFO.svg)
13. ![Verizon](https://surrealdb.com/assets/static/verizon.BI7CajdX.svg)
14. ![Liberty Mutual](https://surrealdb.com/assets/static/liberty-mutual.B7qOU1pd.svg)
15. ![Walmart](https://surrealdb.com/assets/static/walmart.BjDg_Sr8.svg)
16. ![Carrier](https://surrealdb.com/assets/static/carrier.D21gC6NX.svg)
17. ![Saks Fifth Avenue](https://surrealdb.com/assets/static/saks-fifth-avenue.COIDpLSb.svg)
18. ![San Francisco Compute Company](https://surrealdb.com/assets/static/sfcc.B7jlImq4.svg)
19. ![Shield AI](https://surrealdb.com/assets/static/shield-ai.pINZ0KJr.svg)
20. ![Wix](https://surrealdb.com/assets/static/wix.DvHhmoBi.svg)

Back to top

[Version 4.0.0-beta.1](https://surrealdb.com/releases/4.0#release-4-0-0-beta-1)

[🔢 Numbers that keep their size](https://surrealdb.com/releases/4.0#4-0-0-beta-1--numbers-that-keep-their-size) [🧾 Transactions that ask for what they need](https://surrealdb.com/releases/4.0#4-0-0-beta-1--transactions-that-ask-for-what-they-need) [⏱ Time series ids you can query by name](https://surrealdb.com/releases/4.0#4-0-0-beta-1--time-series-ids-you-can-query-by-name) [🗺 An index for places](https://surrealdb.com/releases/4.0#4-0-0-beta-1--an-index-for-places) [💾 SurrealKV everywhere](https://surrealdb.com/releases/4.0#4-0-0-beta-1--surrealkv-everywhere) [Queries](https://surrealdb.com/releases/4.0#4-0-0-beta-1-queries) [Transactions](https://surrealdb.com/releases/4.0#4-0-0-beta-1-transactions) [Schema and writes](https://surrealdb.com/releases/4.0#4-0-0-beta-1-schema-and-writes) [Indexes](https://surrealdb.com/releases/4.0#4-0-0-beta-1-indexes) [Vector and full-text search](https://surrealdb.com/releases/4.0#4-0-0-beta-1-vector-and-full-text-search) [Storage](https://surrealdb.com/releases/4.0#4-0-0-beta-1-storage) [Server, HTTP and RPC](https://surrealdb.com/releases/4.0#4-0-0-beta-1-server-http-and-rpc) [Security and access](https://surrealdb.com/releases/4.0#4-0-0-beta-1-security-and-access) [Import and export](https://surrealdb.com/releases/4.0#4-0-0-beta-1-import-and-export) [SDKs and embedded](https://surrealdb.com/releases/4.0#4-0-0-beta-1-sdks-and-embedded) [Queries](https://surrealdb.com/releases/4.0#4-0-0-beta-1-queries-1) [Schema and writes](https://surrealdb.com/releases/4.0#4-0-0-beta-1-schema-and-writes-1) [Indexes](https://surrealdb.com/releases/4.0#4-0-0-beta-1-indexes-1) [Vector and full-text search](https://surrealdb.com/releases/4.0#4-0-0-beta-1-vector-and-full-text-search-1) [Import and export](https://surrealdb.com/releases/4.0#4-0-0-beta-1-import-and-export-1) [Server, HTTP and RPC](https://surrealdb.com/releases/4.0#4-0-0-beta-1-server-http-and-rpc-1) [GraphQL](https://surrealdb.com/releases/4.0#4-0-0-beta-1-graphql) [SDKs and embedded](https://surrealdb.com/releases/4.0#4-0-0-beta-1-sdks-and-embedded-1) [SurrealDS (Enterprise)](https://surrealdb.com/releases/4.0#4-0-0-beta-1-surrealds-enterprise)

[Home](https://surrealdb.com/)

[Releases](https://surrealdb.com/releases)

4.0

This release introduces the first beta of SurrealDB 4.0. Numbers keep their size and one storage engine runs everywhere. We would love to hear how it goes. The…

Released Oct 28, 2026

## 4.0.0-beta.1

PRE-RELEASE

This release introduces the first beta of SurrealDB 4.0. Numbers keep their size and one storage engine runs everywhere. We would love to hear how it goes. The upgrade is one-way, so read the upgrade notes before trying it on data you care about.

## Highlights

### 🔢 Numbers that keep their size

Until now, every whole number in SurrealDB was a 64-bit `int` and every fraction a 64-bit `float`, so a schema had no way to say that a sensor id fits in two bytes or that an embedding only needs 32-bit precision. 4.0 brings sized numbers, from `i8` and `u8` up to `u64`, plus `f16` and `f32`. A field declared with one stores its values at that size and turns away any value that does not fit:

```surql
DEFINE FIELD sensor ON reading TYPE u16;
DEFINE FIELD level ON reading TYPE f32;

CREATE reading:1 SET sensor = 65535, level = 1.25;
-- [{ id: reading:1, level: 1.25f32, sensor: 65535u16 }]

CREATE reading:2 SET sensor = 65536, level = 1;
-- "Couldn't coerce value for field `sensor` of `reading:2`: Expected `u16` but found `65536`"
```

Literals can carry their size as well, as in `255u8` or `1.5f32`, and arithmetic on them fails rather than wrapping round, so `255u8 + 1u8` is an error instead of a quiet `0`. SDKs that speak the new CBOR v2 protocol get the exact width back, and 3.x SDKs carry on over the original protocol.

[Sized numbers](https://surrealdb.com/docs/reference/query-language/language-primitives/data-types/numbers#sized-numbers) · [Sized number fields](https://surrealdb.com/docs/reference/query-language/statements/define/field#sized-number-types) · [CBOR v2](https://surrealdb.com/docs/reference/rest-api/cbor-protocol#sized-numbers-cbor-v2)

### 🧾 Transactions that ask for what they need

Snapshot isolation, the default, lets two transactions each read something the other is about to change. Two receptionists booking the last free Monday slot can both see an empty calendar, and both bookings commit. `BEGIN` learned to ask for a stronger guarantee:

```surql
BEGIN ISOLATION LEVEL SERIALIZABLE;

LET $booked = SELECT VALUE id FROM booking WHERE day = 'monday';
IF $booked.is_empty() {
    CREATE booking SET doctor = doctor:ana, day = 'monday';
};

COMMIT;
```

When two of these run at once, the second `COMMIT` now fails with a conflict you can retry. The in-memory engine and SurrealDS offer the level, while SurrealKV and TiKV refuse it outright rather than quietly running at a weaker one. Reads got their own option too: `BEGIN READ ONLY` turns away every write, and on SurrealDS, `MAX STALENESS 5s` lets a report read from a snapshot up to five seconds old.

[Serializable isolation](https://surrealdb.com/docs/learn/querying/concepts-and-guides/transactions#serializable-isolation) · [Read-only transactions](https://surrealdb.com/docs/reference/query-language/statements/begin#read-only-transactions)

### ⏱ Time series ids you can query by name

Array record ids such as `reading:['London', d'2026-10-01T09:00:00Z']` keep each city's readings together in time order, which makes them a natural fit for time series. Reading a slice of one, though, meant writing a record range by position. You can now name the parts of the id once and query them like ordinary fields:

```surql
DEFINE FIELD id ON reading TYPE [city: string, time: datetime];

SELECT time, temp FROM reading
WHERE city = 'London' AND time >= d'2026-10-01' AND time < d'2026-10-08';
```

The `WHERE` becomes a scan of the keys between those two points, so the query reads one city's week and nothing else. `ORDER BY time DESC LIMIT 10` reads backwards from the newest reading and stops after ten, which keeps "the latest readings for London" cheap at any table size.

[Naming the components of an array id](https://surrealdb.com/docs/reference/query-language/statements/define/field#naming-the-components-of-an-array-id)

### 🗺 An index for places

Questions like "which cities are within 400 km of London?" used to check the geometry of every record in the table. The new `SPATIAL` index stores geometries on a grid over the globe, so a query reads only the cells around the area it asks about:

```surql
DEFINE INDEX city_location ON city FIELDS location SPATIAL;

SELECT VALUE name FROM city
WHERE geo::distance(location, (-0.1276, 51.5072)) < 400000;
```

It serves `INSIDE`, `INTERSECTS`, `CONTAINS` and the other geometry operators as well as a radius written with `geo::distance`, and it works alongside other indexes, so `status = 'open' AND location INSIDE $area` uses both. A nearest-neighbour vector search can be narrowed to an area the same way.

[Spatial indexes](https://surrealdb.com/docs/reference/query-language/statements/define/indexes#spatial-index)

### 💾 SurrealKV everywhere

On-disk storage used to come in several engines, with RocksDB on servers and IndexedDB in the browser. From 4.0, SurrealKV 1.0 is the one engine on a server, in an embedded SDK and in the browser, where it keeps the database in the Origin Private File System. Existing data comes along on its own: a `rocksdb://` or `indxdb://` path still opens, and SurrealKV converts what it finds there the first time it starts. To keep the original directory untouched, convert it into a new one ahead of time:

```bash
skv migrate path/to/rocksdb-database path/to/surrealkv-database
surreal db start --user root --pass secret surrealkv://path/to/surrealkv-database
```

SurrealKV also takes compaction requests from `ALTER ... COMPACT` now, which only RocksDB answered before.

[Moving a RocksDB database to SurrealKV](https://surrealdb.com/docs/manage/self-hosted/deployment-models#moving-a-rocksdb-database-to-surrealkv) · [RocksDB and IndexedDB are removed](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#rocksdb-and-indexeddb-are-removed)

## Also new

- **Cookie sign-in:** Browser apps can let SurrealDB hold their session for them. With `--cookie-auth`, signing in through `/signin/cookie` puts the token in an `HttpOnly` cookie that page scripts never see, with a rotating refresh cookie for record access defined `WITH REFRESH`. ([learn more](https://surrealdb.com/docs/reference/rest-api/http-protocol#cookie-authentication))
- **APIs with caller permissions:** `DEFINE API ... SECURITY INVOKER` runs an endpoint with the caller's own permissions, so it returns exactly what that user could read with a direct query. ([learn more](https://surrealdb.com/docs/reference/query-language/statements/define/api#running-the-body-with-the-callers-permissions))
- **Stored back-references:** `MATERIALIZE INTO` keeps a list of the records that point at a record on the record itself, so an author's posts are one field read away. ([learn more](https://surrealdb.com/docs/reference/query-language/statements/define/field#keeping-a-list-of-references-with-materialize-into))
- **Unique nulls:** `UNIQUE NULLS NOT DISTINCT` treats `NULL` as a value, so only one record can leave a unique field `NULL`. ([learn more](https://surrealdb.com/docs/reference/query-language/statements/define/indexes#treating-null-as-a-value-with-nulls-not-distinct))
- **Keeping the rest:** Destructuring learned `...`, which keeps every field you did not name, so `person.{ address: address.{ city }, ... }` trims the address and leaves the rest alone. ([learn more](https://surrealdb.com/docs/reference/query-language/language-primitives/idioms#keeping-the-other-fields))
- **Automatic retries:** A statement outside a transaction that loses a write conflict now runs again by itself, unless it reaches outside the database with a call such as `http::get`. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#statements-that-lose-a-write-conflict-run-again))
- **Shaped results:** Clients can ask for results that send each row's shape once and then only the values. ([learn more](https://surrealdb.com/docs/reference/rest-api/rpc-protocol#shaped-results))
- **Finer capabilities:** `--allow-net "*.example.com"` covers every subdomain, and `--allow-mcp` and `--deny-mcp` pick out individual MCP tools. ([learn more](https://surrealdb.com/docs/learn/security/authorization/capabilities#mcp-tools))
- **Rust and browser SDKs:** The Rust SDK can call `DEFINE API` endpoints with `Surreal::api()`, and the browser build shrank by 1.9 MB. ([learn more](https://surrealdb.com/docs/reference/rust/methods/api))

## Upgrade notes

The [3.x to 4.0 upgrade guide](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x) covers each of these in detail.

- **One-way upgrade:** Take an export before upgrading, because once 4.0 has opened a datastore, no 3.x release starts on it. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#before-you-upgrade))
- **SDKs first:** Upgrade every SDK before the server, because 4.0 refuses the text forms of datetimes, UUIDs and durations that some earlier Python, PHP and Go SDKs send, and 3.x Rust SDKs refuse a 4.0 server. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#clients-and-sdks))
- **Storage conversion:** Leave room on disk for a second copy of a RocksDB or SurrealKV datastore, and give health checks extra time on the first start, because the conversion runs before the server answers `/health`. Run `skv migrate` beforehand to keep the original directory. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#rocksdb-and-indexeddb-are-removed))
- **Clusters:** Upgrade every node to the latest 3.3 release first, and hold off on `LIVE` queries and DiskANN writes until every node runs 4.0. A TiKV datastore switches to the 4.0 format only once every node is started with `SURREAL_FEATURE_LEVEL=4.0`. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#clusters))
- **Index rebuilds:** Rebuild HNSW indexes that use `JACCARD` or `PEARSON` and full-text indexes whose records were updated and later deleted, and redefine any DiskANN `TYPE I8` or `TYPE U8` index above 33025 dimensions. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#indexes-to-rebuild))
- **Configuration:** Move `SURREAL_KNN_PREFILTER_EF_BOOST` to `SURREAL_KNN_PREFILTER_HNSW_EF_BOOST` if you tuned it for HNSW, and remove `gql` from `--allow-experimental`, which now refuses it. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#configuration))
- **Rust SDK:** Replace the `kv-rocksdb` and `kv-indxdb` features with `kv-surrealkv`, and import `AolMode`, `SnapshotMode` and `SyncMode` from `surrealdb::opt`. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#rust-sdk))
- **Behaviour changes:** Check your queries against the behaviour changes in the guide. The ones most likely to matter are exact float and decimal comparison, `SPLIT` naming the result's field, KNN operators in `UPDATE` and `DELETE`, and writes that create a table. ([learn more](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/3x-to-4x#behaviour-changes))

## All changes

### Queries

- Destructuring accepts a trailing `...`, which keeps every field the pattern did not name, on objects and on record links.
- `UPDATE` and `DELETE` use indexes for conditions on bound parameters, such as `DELETE t WHERE time < $cut`, and evaluate a `WHERE` on stored values before decoding records.
- Aggregates run by record users no longer derive `COMPUTED` fields that the query does not read.
- A standalone statement that loses a retryable write conflict is planned and run again, up to 10 attempts in all within its timeout. Statements with an external effect, such as an `http::*` call, are not retried.
- `array::min()` and `array::max()` work as `GROUP BY` aggregates, and `array::group()` returns the unique values of a group.
- A `GROUP BY` projection that does not read the record is evaluated once per group, so a constant expression gives one value.
- A group key or aggregate argument that ran a subquery, block, custom function or script and then failed returns the error instead of `NONE`. Nested aggregates in `GROUP BY` fail with `Nested aggregate functions are not supported`.
- `SPLIT` names a field of the result, under its alias where it has one, so `SELECT tags AS t FROM person SPLIT tags` fails with `` Missing split idiom `"tags"` in statement selection ``.
- `SELECT * FROM ONLY [item:404]` returns `NONE` when the record does not exist, where 3.3 failed with `Expected a single result output when using the ONLY keyword`.
- Nested arrays in `FROM` are flattened, so `SELECT VALUE $this FROM [[1, 2], [3]]` returns `[1, 2, 3]`.
- A record id on a table that does not exist is an error rather than `[]` or `NONE`.
- A float equals a decimal only when the decimal is exactly the float's value, so `0.1f = 0.1dec` is `false`.
- An array slice past the end, such as `[1, 2][0..10]`, returns the elements it covers instead of `NONE`.
- `LET` cannot set `$this`, `$self`, `$parent`, `$value`, `$input`, `$before`, `$after` or `$event` inside a block, loop, condition, function or closure, which fails with `'parent' is a protected variable and cannot be set`. A `LET` used as the value of an expression, including as a branch of `IF ... THEN ... END`, fails with `LET statements can only appear at the top level of a query or inside a block expression`.
- A top-level `RETURN` no longer skips the statements after it.
- GeoJSON objects from parameters, HTTP responses or JSON fields are accepted wherever a geometry is expected, including in the geometry operators.
- A range passed to a `function()` script arrives as a `Range` object, and a returned `Range` converts back.

### Transactions

- `BEGIN READ ONLY` starts a transaction that refuses writes and returns a description of its snapshot. `MAX STALENESS <duration>` lets it read a published snapshot no older than the bound, and `STRICT` fails it when none is recent enough. Only SurrealDS publishes snapshots; every community engine reads the current state, which meets any bound. `SURREAL_READ_SNAPSHOT_MAX_STALENESS_SECS` (3600 by default) caps the bound, and `SURREAL_READ_SNAPSHOT_FALLBACK` sets the behaviour when no snapshot qualifies.
- `BEGIN ISOLATION LEVEL SNAPSHOT | SERIALIZABLE` selects the isolation level. The in-memory engine and SurrealDS offer `SERIALIZABLE`, and SurrealKV and TiKV refuse it with `The isolation level SERIALIZABLE is not available on this datastore; it offers SNAPSHOT`. It cannot be combined with a `MAX STALENESS` above zero.
- The `begin` RPC method takes `read_only` and `isolation` options, HTTP `/sql` takes `read_only_max_staleness` and `read_only_strict`, and the Rust SDK has `Begin::isolation()`, `Begin::client()` and `Transaction::client()`.
- A failing statement cancels its transaction, and `LET` values set in a cancelled transaction are discarded.
- A `DEFINE API` handler or middleware that fails rolls back everything it wrote, including events, index entries and sequence draws, and so does a failed `surrealdb.query` call that a script catches.

### Schema and writes

- Sized number types `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `u64`, `f16` and `f32` are available in fields, casts and literals (`255u8`, `1.5f32`), with `type::` conversion and `type::is_` checks for each. `i64` and `f64` are aliases of `int` and `float`. Arithmetic on two values of the same sized type fails on overflow, and mixed types widen to the smallest type that holds both.
- The elements of an array record id can be named in the `id` field's type, as in `TYPE [city: string, time: datetime]`. Each name reads as a field, conditions on the names become key-range scans, and `ORDER BY` on a following component reads in key order.
- `REFERENCE ... MATERIALIZE INTO <table>.<field> [ WHERE <condition> ]` stores on each referenced record a sorted list of the records that reference it, kept current in the same transaction.
- `DEFINE FIELD OVERWRITE ... REFERENCE` and `ALTER FIELD` on a populated field write reference keys for existing records.
- `COMPUTED`, `PERMISSIONS`, `ASSERT`, `VALUE` and `DEFAULT` clauses may not have side effects, and the check follows custom functions they call.
- A `CREATE`, `UPSERT`, `INSERT` or `RELATE` that implicitly defines a table needs permission to run `DEFINE TABLE`. A database `VIEWER` now gets `IAM error: Not enough permissions to perform this action`. Record users are unaffected.
- A nested field needs a parent whose type can hold it, and `DEFINE FIELD id.<part>` needs `id` itself defined first. A view may not read its own table, and changing a table between `NORMAL` and `RELATION` checks its records.
- A whole-record `PATCH` replace with a value that is not an object fails with `Cannot use 5 in a CONTENT clause` and leaves the record unchanged.
- Integer overflow in `math::sum` is an error, and a decimal literal with more than 28 decimal places is a parse error.

### Indexes

- `DEFINE INDEX ... SPATIAL` indexes any geometry type on a grid of cells, serving `INSIDE`, `CONTAINS`, `INTERSECTS`, `CONTAINSANY`, `CONTAINSALL`, `ANYINSIDE`, `ALLINSIDE` and `geo::distance(...) < r`, alone or combined with other indexes, and pre-filters KNN searches by area.
- `UNIQUE NULLS NOT DISTINCT` makes `NULL` take part in uniqueness. `NONE` is still not compared.
- A `DEFINE INDEX` or `REBUILD INDEX` without `CONCURRENTLY` commits its definition before it builds and returns once the index is online. A build that fails removes the definition afterwards, and a `UNIQUE` index whose build failed refuses writes. A blocking `DEFINE INDEX` after writes to the same table in the same transaction is refused.
- An unchanged `DEFINE INDEX OVERWRITE` keeps its index instead of rebuilding it.
- Standard and `UNIQUE` index builds scan in parallel batches, set by `SURREAL_INDEX_BUILD_PARALLELISM`, and DiskANN indexes build in parallel.
- `count()` over `IN [...]` or `OR` conditions on a B-tree index is answered from the index without reading records.
- An index records whether a column has ever held an array, so a column that never has can serve `ORDER BY ... LIMIT` and ranges from index order.
- A `UNIQUE` index over `s.*` on a set field uses the whole set as its key, and `s.*` over a set returns a set.

### Vector and full-text search

- A KNN operator in the `WHERE` of an `UPDATE` or `DELETE` needs a vector index on the field and is answered from it. An `UPSERT` with a KNN operator always fails.
- The KNN query vector cannot depend on the record being scanned, which fails with `The KNN query vector cannot depend on the record being scanned`.
- HNSW indexes using `JACCARD` or `PEARSON` rank by distance.
- DiskANN `TYPE I8` and `TYPE U8` indexes are limited to `DIMENSION 33025`.
- Pre-filtered KNN is tuned by `SURREAL_KNN_PREFILTER_HNSW_EF_BOOST` and `SURREAL_KNN_PREFILTER_EXACT_COST`. `SURREAL_KNN_PREFILTER_EF_BOOST` now applies to DiskANN only.
- Vector measures over eight or more floats sum their terms in a different order, so results can differ in their last digits.

### Storage

- SurrealKV 1.0 is the only on-disk engine, with a new on-disk format. The RocksDB and IndexedDB engines are removed: `rocksdb://` and `indxdb://` paths open with SurrealKV, which converts existing data on first start, and `skv migrate` converts a RocksDB directory into a new one. `SURREAL_ROCKSDB_*` settings are ignored with a warning.
- In the browser, the Wasm engine connects with `surrealkv://name`, stores data in the Origin Private File System from a dedicated worker, and copies in an IndexedDB database of the same name on first open.
- SurrealKV supports `ALTER ... COMPACT`. Its grouped-commit settings have no effect.
- The first 4.0 start applies a data migration and, on the in-memory engine and SurrealKV, switches the datastore to the 4.0 record format, after which no 3.x release can open it. TiKV keeps the 3.x format until every node runs 4.0 with `SURREAL_FEATURE_LEVEL=4.0`.

### Server, HTTP and RPC

- `--cookie-auth` with an explicit `--allow-origin` list enables `POST /signin/cookie`, `/signup/cookie`, `/refresh/cookie` and `/signout`, which keep the access token, and a refresh token for record access defined `WITH REFRESH`, in `HttpOnly` cookies checked against the request's origin.
- `DEFINE API ... SECURITY INVOKER` runs an endpoint's handlers and middleware with the caller's permissions.
- HTTP RPC sessions are stored in the datastore, so any node of a cluster serves them, and `--durable-sessions` has no effect.
- Clients can opt in to `application/vnd.surrealdb.shaped`, or the `shaped` WebSocket subprotocol, which sends each row shape once and then only values.
- A CBOR request the server cannot decode is answered with the decoder's reason instead of `Parse error`. The server refuses the text forms of datetimes, UUIDs and durations (tags `0`, `9` and `13`).
- `--allow-net` and `--deny-net` accept wildcard subdomains such as `*.example.com`, and a `*.` rule now matches subdomains.
- `--allow-mcp` and `--deny-mcp` allow or deny individual MCP tools.
- The server logs its capability summary at startup, and warns when guest access or scripting is on.
- `--planner-strategy` is deprecated, `SURREAL_MAX_CONCURRENT_TASKS` is removed, and `--allow-experimental gql` is refused.
- `surreal upgrade` no longer downloads a release, because the Surreal CLI installs and upgrades the database, and `surreal --version` prints a shorter line.

### Security and access

- Bearer tokens are checked against their grant, so tokens issued before the upgrade are refused.
- `$auth.id` returns the signed-in record id without reading the record.

### Import and export

- `POST /import` answers `422` when part of an import fails.
- `DEFINE ACCESS` with a signing key is not restored from a dump, and table values in a 3.x dump are lost on import.
- An import drops a UTF-8 byte order mark, refuses UTF-16 with an error naming the encoding, and refuses a `DEFINE FIELD` whose `DEFAULT` does not fit its `TYPE`.
- In the 4.0 Docker image, `/data` and `/logs` are owned by uid and gid `65532`, so a new named volume is writable without running as root.

### SDKs and embedded

- The Rust SDK adds `Surreal::api()` for `DEFINE API` endpoints, honours a custom TLS configuration on `grpcs://` and refuses one on `grpc://`, makes cloning a WebSocket client cheap, and round-trips integers above `i64::MAX` as decimals.
- `surrealdb::opt::SyncMode`, `AolMode` and `SnapshotMode` set the durability of local engines.
- `Config::datastore_option` sets storage options by their server names, path parameters reach the storage engine, `Surreal::shutdown` waits until storage is released, and embedded engines can allow local `file:` bucket folders.
- The `kv-rocksdb` and `kv-indxdb` features are removed, `parse` is no longer a default feature, `unstable_from_datastore` needs the `unstable` feature, and a dropped transaction rolls back.
- The WebAssembly build is 1.9 MB smaller.

## Bug fixes

Fixes already released in 3.3.1 to 3.3.5 are included and not repeated here.

### Queries

- Fixed `GROUP BY` giving different answers for equivalent constant expressions, and aggregates over the wrong kind of value returning `0` or `-Infinity` instead of a result or an error.
- Fixed `UPSERT ... WHERE ... RETURN NONE` creating an extra empty record when the condition matched existing rows.
- Fixed `UPDATE` and `DELETE ... WHERE <field> IN [...]` on a composite `UNIQUE` index changing no rows.
- Fixed range conditions on the leading column of a composite `UNIQUE` index returning the wrong rows.
- Fixed indexed reads returning duplicate rows and a wrong `count()` when records stored before a `DEFINE FIELD ... TYPE` still held arrays.
- Fixed `CONTAINS` and `INSIDE` over an index on `field.*` missing records whose value is a string, object, range or geometry.
- Fixed a `.*` index on a `set` field returning no rows for `CONTAINS`, `CONTAINSANY`, `CONTAINSALL` and `IN`.
- Fixed `CONTAINSALL` over a list that mixes floats and decimals rejecting matching rows unpredictably.
- Fixed `ORDER BY ... LIMIT` over an index on a column that may hold arrays returning the wrong rows or a record twice.
- Fixed index-ordered reads and ranges over datetimes returning rows out of order or leaving some out.
- Fixed a closed range after an equality on a compound index using only one bound, and an equality against an array or object record id scanning the table.
- Fixed a `FROM` expression that yields a record range reading nothing.
- Fixed a record id read from a string, such as `type::record("value:focus:outline-none")`, keeping only the first `table:key`.
- Fixed closures ignoring outer parameters used only in `START` or `TIMEOUT`.
- Fixed `record::exists()` deriving every computed field of the record, so an erroring computed field made it fail.
- Fixed `SHOW CHANGES FOR TABLE ... SINCE <datetime>` returning `[]` on the memory and SurrealKV engines, and its `LIMIT` counting entries from other tables.
- Fixed `VERSION` reads on a versioned SurrealKV store returning records deleted before that version.

### Schema and writes

- Fixed fields typed as sets of arrays, sets or objects storing a wrapped value or failing to read back.
- Fixed a field typed `array | array<int>` making its table unreadable, including on databases upgraded from 3.0 to 3.2.
- Fixed `SCHEMAFULL` tables accepting undeclared nested keys under a union with a literal.
- Fixed unions with an `object`, literal or `any` member refusing valid values for their elements.
- Fixed `ALTER FIELD` accepting a type or a `REFERENCE` that `DEFINE FIELD` refuses, which made exports fail to import.
- Fixed fields named with a keyword or needing backticks failing to define beside an implied element definition, and failing in `DEFINE INDEX` on `SCHEMAFULL` tables.
- Fixed a `COMPUTED` field whose subquery selects a field of the same name failing as a cyclic dependency.
- Fixed `UPSERT` of a named record and `INSERT ... ON DUPLICATE KEY UPDATE` evaluating their clauses against an empty record.
- Fixed an `UPSERT` that conflicted with a record its own event created silently returning `[]`.
- Fixed `math::sum` wrapping on integer overflow, `math::mean` losing precision, and `math::product` and `math::spread` wrapping.
- Fixed views with `math::sum`, `math::min`, `math::max` or `math::mean` refusing writes of records that lack the field.
- Fixed `DELETE` on a `DROP` table leaving index entries and `REFERENCE` keys behind.
- Fixed redefining a view disconnecting the views that read it, `DEFINE TABLE OVERWRITE` dropping a view's own fields, and a view that sets its own `id` being keyed inconsistently.
- Fixed foreign views seeing `NONE` for their source table's `COMPUTED` fields.
- Fixed `REMOVE TABLE` on an edge table leaving its edges reachable from their endpoints.
- Fixed a repeated clause in `DEFINE TABLE` silently replacing the earlier one, and two index kinds in one `DEFINE INDEX` silently keeping the last.
- Fixed a decimal literal with more than 28 decimal places being rounded or becoming `0`.
- Fixed a literal array or object type validating its members without converting them.
- Fixed JSON Patch paths being split on `.`, so a key that contains a dot could not be patched.
- Fixed `id.x` definitions not being checked against the record key, and `id` reading `NONE` in `CREATE ... SET` when the id is known.
- Fixed a sequence at the top of the `i64` range wrapping round instead of failing, and `sequence::nextval` panicking there.
- Fixed `time::from_uuid` returning a wrong year for UUIDs dated before 1970.
- Fixed a cold sequence allocator blocking every other sequence-backed allocation on the node while it loaded.

### Indexes

- Fixed an index rebuilt in a transaction that failed to commit reporting ready while returning nothing.
- Fixed index builds that could miss writes from older transactions, or apply a write twice during `REBUILD INDEX`.
- Fixed a `UNIQUE` index whose concurrent build failed accepting duplicates.
- Fixed a `DELETE` from a snapshot taken before a full-text index build finished leaving a stale entry.
- Fixed `DEFINE INDEX` after `REMOVE INDEX` failing while document ids were still being reclaimed.

### Vector and full-text search

- Fixed indexed reads and vector searches disagreeing with table scans on equal numbers of different kinds.
- Fixed a multi-word `@@` match used as a filter under `OR`, or in a write's `WHERE`, matching any of the words instead of all of them.
- Fixed `MATCHES` returning nothing over record ids, record ranges and graph hops.
- Fixed BM25 scoring a term that appears in about half the documents or more as `0`.
- Fixed an `ngram` analyzer missing a word longer than its maximum gram size, and `search::highlight` covering only the last of several predicates that share one reference.
- Fixed a full-text index keeping a deleted record counted under a term it kept through an update, which lowered BM25 scores. Existing indexes need `REBUILD INDEX`.
- Fixed a KNN query whose vector is only known at run time returning no rows or searching with the wrong vector.
- Fixed `JACCARD` and `PEARSON` vector searches ranking the least similar records first, and integer vector distances overflowing.
- Fixed KNN queries returning no rows while a vector index is still being built `CONCURRENTLY`.
- Fixed a DiskANN search stalling compaction and later searches under write load.

### Import and export

- Fixed `surreal import` losing `INSERT` batches with a write conflict on a table whose DiskANN index is defined before its data.
- Fixed exports of union types that imply element definitions failing to import.
- Fixed exports writing a view before its source table.
- Fixed table values in a record exporting as a bare name that imported as `NONE`.
- Fixed `surreal export` to standard output mixing log lines into the dump, and `surreal export` failing against an HTTP/2 endpoint such as SurrealDB Cloud.
- Fixed `--import-file` retrying a file that does not parse for a minute and skipping a file without `USE`.

### Server, HTTP and RPC

- Fixed one writer's live-query notifications arriving out of commit order.
- Fixed a `LIVE SELECT` registered during an in-flight write missing that write.
- Fixed writes by an `ASYNC` event never reaching live-query subscribers.
- Fixed concurrent `LIVE SELECT` and `KILL` on one table failing with a transaction conflict.
- Fixed clients dropping `KILLED` and `ERROR` live notifications, which left the stream open and silent.
- Fixed live notifications stopping after `REMOVE TABLE` under the `router` live query engine.
- Fixed a WebSocket request that fails to decode being answered without its `id`, which left SDK calls waiting.
- Fixed JSON RPC strings that begin with a UUID or a datetime being turned into that value.
- Fixed JSON values in `/sql` query parameters being bound as strings.
- Fixed a RocksDB commit stall freezing the whole server.
- Fixed memory-sized defaults ignoring a cgroup limit set below the root, such as a systemd `MemoryMax`.
- Fixed `surreal mcp` writing log lines to standard output, and MCP tool schemas that some model providers reject.
- Fixed JWTs whose `nbf` is slightly ahead of the server clock being rejected, and HTTP clients with an expired token being unable to sign in.
- Fixed database-level `BEARER` grant tokens being refused for every operation.
- Fixed a batch stopped by `--query-timeout` never appearing in the slow query log.
- Fixed `surreal sql` exiting with `0` after a failed statement in piped input.
- Fixed a SurrealKV commit with `sync=every` being visible to other transactions before it was durable.
- Fixed a datastore reading every process on the host at startup.

### GraphQL

- Fixed required literal-union fields being exposed as nullable, tables without fields producing an empty update input, `GRAPHQL_ALIAS` on nested fields being ignored, and a dropped subscription leaving its `LIVE` query behind.

### SDKs and embedded

- Fixed a `KILL` statement being left out of query results in the Rust SDK, which shifted later statement indexes.
- Fixed `tx.commit()` on an embedded engine returning an internal error instead of `TransactionConflict`.
- Fixed live query streams from a cloned client ending early, and every dropped authenticated HTTP client leaking a server session.
- Fixed `any::connect` creating a directory with percent-encoding left in the path.
- Fixed `#[derive(SurrealValue)]` reading a unit variant only in its object form, and `#[surreal(crate = ..., tag = ...)]` failing to compile without `Value` in scope.
- Fixed `REMOVE MODEL` deleting the model file before the transaction committed, and a refused model import leaving its file behind.

### SurrealDS (Enterprise)

- Fixed a SurrealDS 1.0 node refusing every later transaction as `Unavailable` after a write conflict, until the process restarted.
- Fixed a single-node SurrealDS 1.0 database refusing all writes after one failed disk write while still reporting ready.
- Fixed SurrealDB hanging at startup when a single-node SurrealDS 1.0 log was damaged.
- Fixed reads on a SurrealDS 1.0 replica failing under sustained writes.

Upgrade or install

## Get SurrealDB v4.0.0-beta.1

Upgrade a Cloud instance from SurrealDB Studio, or copy the command for your platform.

SurrealDB Cloud

macOS

Linux

Windows

Docker

Already installed

Upgrade a Cloud instance to v4.0.0-beta.1 in four steps:

1. Open [SurrealDB Studio](https://studio.surrealdb.com/current/instances)
2. Select your organisation and instance
3. On the dashboard, select “Upgrade”
4. The instance updates and restarts automatically

[Full install guide](https://surrealdb.com/surrealdb/install)

Every release line

### [All releases](https://surrealdb.com/releases)

No newer release line

Older release line

### [3.3](https://surrealdb.com/releases/3.3)

Updated Oct 13, 2026

GET STARTED

## Start building with SurrealDB. One data layer for AI, from object storage to agent memory.

[Start free with SurrealDB](https://studio.surrealdb.com/current/instances/deploy) [Learn more](https://surrealdb.com/docs/what-is-surrealdb)

![ISO 27001](https://surrealdb.com/assets/static/iso.BzQy4FFy.svg)

![AICPA SOC 2 Type 2](https://surrealdb.com/assets/static/aicpa-soc.CE_YjqhK.svg)

![Cyber Essentials](https://surrealdb.com/assets/static/cyber-essentials.CtbmbYfL.svg)

![GDPR](https://surrealdb.com/assets/static/gdpr.IadR7loS.svg)

![Cyber Essentials Plus](https://surrealdb.com/assets/static/cyber-essentials-plus.Cl5P05xe.svg)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://surrealdb.com"},{"@type":"ListItem","position":2,"name":"Releases","item":"https://surrealdb.com/releases"},{"@type":"ListItem","position":3,"name":"4.0","item":"https://surrealdb.com/releases/4.0"}]}
```

```json
{"@context":"https://schema.org","@type":"TechArticle","headline":"SurrealDB 4.0 release notes","description":"This release introduces the first beta of SurrealDB 4.0. Numbers keep their size and one storage engine runs everywhere. We would love to hear how it goes. The…","datePublished":"2026-10-28T00:00:00.000Z","dateModified":"2026-10-28T00:00:00.000Z","url":"https://surrealdb.com/releases/4.0","mainEntityOfPage":{"@type":"WebPage","@id":"https://surrealdb.com/releases/4.0"},"author":{"@type":"Organization","name":"SurrealDB","url":"https://surrealdb.com"},"publisher":{"@type":"Organization","name":"SurrealDB","logo":{"@type":"ImageObject","url":"https://surrealdb.com/assets/static/logo.BG7_TG2b.svg"}},"about":{"@type":"SoftwareApplication","name":"SurrealDB","applicationCategory":"DeveloperApplication","operatingSystem":"Linux, macOS, Windows","softwareVersion":"4.0"}}
```

```json
{"@context":"https://schema.org","@type":"Organization","@id":"https://surrealdb.com/#organization","name":"SurrealDB","url":"https://surrealdb.com","logo":"https://surrealdb.com/assets/static/logo.BG7_TG2b.svg","description":"SurrealDB is the context and memory layer for AI agents. A multi-model database for documents, graphs, vectors, and time-series.","foundingDate":"2022","legalName":"SurrealDB Ltd","identifier":{"@type":"PropertyValue","propertyID":"GB-COH","value":"13615201"},"address":{"@type":"PostalAddress","streetAddress":"3rd Floor, 1 Ashley Road","addressLocality":"Altrincham","addressRegion":"Cheshire","postalCode":"WA14 2DT","addressCountry":"GB"},"contactPoint":[{"@type":"ContactPoint","contactType":"customer support","email":"support@surrealdb.com","url":"https://surrealdb.com/contact","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"sales","email":"info@surrealdb.com","url":"https://surrealdb.com/contact","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"security","email":"security@surrealdb.com","url":"https://surrealdb.com/.well-known/security.txt","availableLanguage":"English"},{"@type":"ContactPoint","contactType":"legal","email":"legal@surrealdb.com","url":"https://surrealdb.com/legal","availableLanguage":"English"}],"hasCertification":[{"@type":"Certification","name":"SOC 2 Type 2"},{"@type":"Certification","name":"GDPR"},{"@type":"Certification","name":"Cyber Essentials Plus"},{"@type":"Certification","name":"ISO 27001"}],"owns":[{"@type":"SoftwareApplication","name":"SurrealDB","url":"https://surrealdb.com/surrealdb"},{"@type":"SoftwareApplication","name":"Agent Memory","url":"https://surrealdb.com/agent-memory"}],"knowsAbout":["multi-model databases","document databases","graph databases","vector search","time-series databases","SurrealQL","Agent Memory","real-time databases","embedded databases","AI agent database","graph ontology","distributed database","knowledge graphs","distributed transaction protocols","highly-scalable databases"],"sameAs":["https://www.wikidata.org/wiki/Q124316308","https://github.com/surrealdb/surrealdb","https://twitter.com/surrealdb","https://www.youtube.com/@surrealdb","https://www.linkedin.com/company/surrealdb","https://discord.gg/surrealdb","https://www.reddit.com/r/surrealdb","https://www.instagram.com/surrealdb","https://medium.com/surrealdb","https://dev.to/surrealdb"]}
```
