Released Oct 28, 2026
4.0.0-beta.1
PRE-RELEASEThis 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:
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 · Sized number fields · 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:
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 · 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:
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
🗺 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:
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.
💾 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:
skv migrate path/to/rocksdb-database path/to/surrealkv-database
surreal db start --user root --pass secret surrealkv://path/to/surrealkv-databaseSurrealKV also takes compaction requests from ALTER ... COMPACT now, which only RocksDB answered before.
Moving a RocksDB database to SurrealKV · 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/cookieputs the token in anHttpOnlycookie that page scripts never see, with a rotating refresh cookie for record access definedWITH REFRESH. (learn more)APIs with caller permissions:
DEFINE API ... SECURITY INVOKERruns an endpoint with the caller's own permissions, so it returns exactly what that user could read with a direct query. (learn more)Stored back-references:
MATERIALIZE INTOkeeps 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)Unique nulls:
UNIQUE NULLS NOT DISTINCTtreatsNULLas a value, so only one record can leave a unique fieldNULL. (learn more)Keeping the rest: Destructuring learned
..., which keeps every field you did not name, soperson.{ address: address.{ city }, ... }trims the address and leaves the rest alone. (learn more)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)Shaped results: Clients can ask for results that send each row's shape once and then only the values. (learn more)
Finer capabilities:
--allow-net "*.example.com"covers every subdomain, and--allow-mcpand--deny-mcppick out individual MCP tools. (learn more)Rust and browser SDKs: The Rust SDK can call
DEFINE APIendpoints withSurreal::api(), and the browser build shrank by 1.9 MB. (learn more)
Upgrade notes
The 3.x to 4.0 upgrade guide 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)
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)
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. Runskv migratebeforehand to keep the original directory. (learn more)Clusters: Upgrade every node to the latest 3.3 release first, and hold off on
LIVEqueries and DiskANN writes until every node runs 4.0. A TiKV datastore switches to the 4.0 format only once every node is started withSURREAL_FEATURE_LEVEL=4.0. (learn more)Index rebuilds: Rebuild HNSW indexes that use
JACCARDorPEARSONand full-text indexes whose records were updated and later deleted, and redefine any DiskANNTYPE I8orTYPE U8index above 33025 dimensions. (learn more)Configuration: Move
SURREAL_KNN_PREFILTER_EF_BOOSTtoSURREAL_KNN_PREFILTER_HNSW_EF_BOOSTif you tuned it for HNSW, and removegqlfrom--allow-experimental, which now refuses it. (learn more)Rust SDK: Replace the
kv-rocksdbandkv-indxdbfeatures withkv-surrealkv, and importAolMode,SnapshotModeandSyncModefromsurrealdb::opt. (learn more)Behaviour changes: Check your queries against the behaviour changes in the guide. The ones most likely to matter are exact float and decimal comparison,
SPLITnaming the result's field, KNN operators inUPDATEandDELETE, and writes that create a table. (learn more)
All changes
Queries
Destructuring accepts a trailing
..., which keeps every field the pattern did not name, on objects and on record links.UPDATEandDELETEuse indexes for conditions on bound parameters, such asDELETE t WHERE time < $cut, and evaluate aWHEREon stored values before decoding records.Aggregates run by record users no longer derive
COMPUTEDfields 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()andarray::max()work asGROUP BYaggregates, andarray::group()returns the unique values of a group.A
GROUP BYprojection 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 inGROUP BYfail withNested aggregate functions are not supported.SPLITnames a field of the result, under its alias where it has one, soSELECT tags AS t FROM person SPLIT tagsfails withMissing split idiom `"tags"` in statement selection.SELECT * FROM ONLY [item:404]returnsNONEwhen the record does not exist, where 3.3 failed withExpected a single result output when using the ONLY keyword.Nested arrays in
FROMare flattened, soSELECT 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
[]orNONE.A float equals a decimal only when the decimal is exactly the float's value, so
0.1f = 0.1decisfalse.An array slice past the end, such as
[1, 2][0..10], returns the elements it covers instead ofNONE.LETcannot set$this,$self,$parent,$value,$input,$before,$afteror$eventinside a block, loop, condition, function or closure, which fails with'parent' is a protected variable and cannot be set. ALETused as the value of an expression, including as a branch ofIF ... THEN ... END, fails withLET statements can only appear at the top level of a query or inside a block expression.A top-level
RETURNno 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 aRangeobject, and a returnedRangeconverts back.
Transactions
BEGIN READ ONLYstarts 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, andSTRICTfails 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, andSURREAL_READ_SNAPSHOT_FALLBACKsets the behaviour when no snapshot qualifies.BEGIN ISOLATION LEVEL SNAPSHOT | SERIALIZABLEselects the isolation level. The in-memory engine and SurrealDS offerSERIALIZABLE, and SurrealKV and TiKV refuse it withThe isolation level SERIALIZABLE is not available on this datastore; it offers SNAPSHOT. It cannot be combined with aMAX STALENESSabove zero.The
beginRPC method takesread_onlyandisolationoptions, HTTP/sqltakesread_only_max_stalenessandread_only_strict, and the Rust SDK hasBegin::isolation(),Begin::client()andTransaction::client().A failing statement cancels its transaction, and
LETvalues set in a cancelled transaction are discarded.A
DEFINE APIhandler or middleware that fails rolls back everything it wrote, including events, index entries and sequence draws, and so does a failedsurrealdb.querycall that a script catches.
Schema and writes
Sized number types
i8,u8,i16,u16,i32,u32,u64,f16andf32are available in fields, casts and literals (255u8,1.5f32), withtype::conversion andtype::is_checks for each.i64andf64are aliases ofintandfloat. 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
idfield's type, as inTYPE [city: string, time: datetime]. Each name reads as a field, conditions on the names become key-range scans, andORDER BYon 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 ... REFERENCEandALTER FIELDon a populated field write reference keys for existing records.COMPUTED,PERMISSIONS,ASSERT,VALUEandDEFAULTclauses may not have side effects, and the check follows custom functions they call.A
CREATE,UPSERT,INSERTorRELATEthat implicitly defines a table needs permission to runDEFINE TABLE. A databaseVIEWERnow getsIAM 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>needsiditself defined first. A view may not read its own table, and changing a table betweenNORMALandRELATIONchecks its records.A whole-record
PATCHreplace with a value that is not an object fails withCannot use 5 in a CONTENT clauseand leaves the record unchanged.Integer overflow in
math::sumis an error, and a decimal literal with more than 28 decimal places is a parse error.
Indexes
DEFINE INDEX ... SPATIALindexes any geometry type on a grid of cells, servingINSIDE,CONTAINS,INTERSECTS,CONTAINSANY,CONTAINSALL,ANYINSIDE,ALLINSIDEandgeo::distance(...) < r, alone or combined with other indexes, and pre-filters KNN searches by area.UNIQUE NULLS NOT DISTINCTmakesNULLtake part in uniqueness.NONEis still not compared.A
DEFINE INDEXorREBUILD INDEXwithoutCONCURRENTLYcommits its definition before it builds and returns once the index is online. A build that fails removes the definition afterwards, and aUNIQUEindex whose build failed refuses writes. A blockingDEFINE INDEXafter writes to the same table in the same transaction is refused.An unchanged
DEFINE INDEX OVERWRITEkeeps its index instead of rebuilding it.Standard and
UNIQUEindex builds scan in parallel batches, set bySURREAL_INDEX_BUILD_PARALLELISM, and DiskANN indexes build in parallel.count()overIN [...]orORconditions 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 ... LIMITand ranges from index order.A
UNIQUEindex overs.*on a set field uses the whole set as its key, ands.*over a set returns a set.
Vector and full-text search
A KNN operator in the
WHEREof anUPDATEorDELETEneeds a vector index on the field and is answered from it. AnUPSERTwith 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
JACCARDorPEARSONrank by distance.DiskANN
TYPE I8andTYPE U8indexes are limited toDIMENSION 33025.Pre-filtered KNN is tuned by
SURREAL_KNN_PREFILTER_HNSW_EF_BOOSTandSURREAL_KNN_PREFILTER_EXACT_COST.SURREAL_KNN_PREFILTER_EF_BOOSTnow 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://andindxdb://paths open with SurrealKV, which converts existing data on first start, andskv migrateconverts 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-authwith an explicit--allow-originlist enablesPOST /signin/cookie,/signup/cookie,/refresh/cookieand/signout, which keep the access token, and a refresh token for record access definedWITH REFRESH, inHttpOnlycookies checked against the request's origin.DEFINE API ... SECURITY INVOKERruns 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-sessionshas no effect.Clients can opt in to
application/vnd.surrealdb.shaped, or theshapedWebSocket 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 (tags0,9and13).--allow-netand--deny-netaccept wildcard subdomains such as*.example.com, and a*.rule now matches subdomains.--allow-mcpand--deny-mcpallow or deny individual MCP tools.The server logs its capability summary at startup, and warns when guest access or scripting is on.
--planner-strategyis deprecated,SURREAL_MAX_CONCURRENT_TASKSis removed, and--allow-experimental gqlis refused.surreal upgradeno longer downloads a release, because the Surreal CLI installs and upgrades the database, andsurreal --versionprints a shorter line.
Security and access
Bearer tokens are checked against their grant, so tokens issued before the upgrade are refused.
$auth.idreturns the signed-in record id without reading the record.
Import and export
POST /importanswers422when part of an import fails.DEFINE ACCESSwith 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 FIELDwhoseDEFAULTdoes not fit itsTYPE.In the 4.0 Docker image,
/dataand/logsare owned by uid and gid65532, so a new named volume is writable without running as root.
SDKs and embedded
The Rust SDK adds
Surreal::api()forDEFINE APIendpoints, honours a custom TLS configuration ongrpcs://and refuses one ongrpc://, makes cloning a WebSocket client cheap, and round-trips integers abovei64::MAXas decimals.surrealdb::opt::SyncMode,AolModeandSnapshotModeset the durability of local engines.Config::datastore_optionsets storage options by their server names, path parameters reach the storage engine,Surreal::shutdownwaits until storage is released, and embedded engines can allow localfile:bucket folders.The
kv-rocksdbandkv-indxdbfeatures are removed,parseis no longer a default feature,unstable_from_datastoreneeds theunstablefeature, 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 BYgiving different answers for equivalent constant expressions, and aggregates over the wrong kind of value returning0or-Infinityinstead of a result or an error.Fixed
UPSERT ... WHERE ... RETURN NONEcreating an extra empty record when the condition matched existing rows.Fixed
UPDATEandDELETE ... WHERE <field> IN [...]on a compositeUNIQUEindex changing no rows.Fixed range conditions on the leading column of a composite
UNIQUEindex returning the wrong rows.Fixed indexed reads returning duplicate rows and a wrong
count()when records stored before aDEFINE FIELD ... TYPEstill held arrays.Fixed
CONTAINSandINSIDEover an index onfield.*missing records whose value is a string, object, range or geometry.Fixed a
.*index on asetfield returning no rows forCONTAINS,CONTAINSANY,CONTAINSALLandIN.Fixed
CONTAINSALLover a list that mixes floats and decimals rejecting matching rows unpredictably.Fixed
ORDER BY ... LIMITover 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
FROMexpression 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 firsttable:key.Fixed closures ignoring outer parameters used only in
STARTorTIMEOUT.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 itsLIMITcounting entries from other tables.Fixed
VERSIONreads 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
SCHEMAFULLtables accepting undeclared nested keys under a union with a literal.Fixed unions with an
object, literal oranymember refusing valid values for their elements.Fixed
ALTER FIELDaccepting a type or aREFERENCEthatDEFINE FIELDrefuses, 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 INDEXonSCHEMAFULLtables.Fixed a
COMPUTEDfield whose subquery selects a field of the same name failing as a cyclic dependency.Fixed
UPSERTof a named record andINSERT ... ON DUPLICATE KEY UPDATEevaluating their clauses against an empty record.Fixed an
UPSERTthat conflicted with a record its own event created silently returning[].Fixed
math::sumwrapping on integer overflow,math::meanlosing precision, andmath::productandmath::spreadwrapping.Fixed views with
math::sum,math::min,math::maxormath::meanrefusing writes of records that lack the field.Fixed
DELETEon aDROPtable leaving index entries andREFERENCEkeys behind.Fixed redefining a view disconnecting the views that read it,
DEFINE TABLE OVERWRITEdropping a view's own fields, and a view that sets its ownidbeing keyed inconsistently.Fixed foreign views seeing
NONEfor their source table'sCOMPUTEDfields.Fixed
REMOVE TABLEon an edge table leaving its edges reachable from their endpoints.Fixed a repeated clause in
DEFINE TABLEsilently replacing the earlier one, and two index kinds in oneDEFINE INDEXsilently 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.xdefinitions not being checked against the record key, andidreadingNONEinCREATE ... SETwhen the id is known.Fixed a sequence at the top of the
i64range wrapping round instead of failing, andsequence::nextvalpanicking there.Fixed
time::from_uuidreturning 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
UNIQUEindex whose concurrent build failed accepting duplicates.Fixed a
DELETEfrom a snapshot taken before a full-text index build finished leaving a stale entry.Fixed
DEFINE INDEXafterREMOVE INDEXfailing 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 underOR, or in a write'sWHERE, matching any of the words instead of all of them.Fixed
MATCHESreturning 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
ngramanalyzer missing a word longer than its maximum gram size, andsearch::highlightcovering 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
JACCARDandPEARSONvector 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 importlosingINSERTbatches 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 exportto standard output mixing log lines into the dump, andsurreal exportfailing against an HTTP/2 endpoint such as SurrealDB Cloud.Fixed
--import-fileretrying a file that does not parse for a minute and skipping a file withoutUSE.
Server, HTTP and RPC
Fixed one writer's live-query notifications arriving out of commit order.
Fixed a
LIVE SELECTregistered during an in-flight write missing that write.Fixed writes by an
ASYNCevent never reaching live-query subscribers.Fixed concurrent
LIVE SELECTandKILLon one table failing with a transaction conflict.Fixed clients dropping
KILLEDandERRORlive notifications, which left the stream open and silent.Fixed live notifications stopping after
REMOVE TABLEunder therouterlive 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
/sqlquery 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 mcpwriting log lines to standard output, and MCP tool schemas that some model providers reject.Fixed JWTs whose
nbfis slightly ahead of the server clock being rejected, and HTTP clients with an expired token being unable to sign in.Fixed database-level
BEARERgrant tokens being refused for every operation.Fixed a batch stopped by
--query-timeoutnever appearing in the slow query log.Fixed
surreal sqlexiting with0after a failed statement in piped input.Fixed a SurrealKV commit with
sync=everybeing 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_ALIASon nested fields being ignored, and a dropped subscription leaving itsLIVEquery behind.
SDKs and embedded
Fixed a
KILLstatement 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 ofTransactionConflict.Fixed live query streams from a cloned client ending early, and every dropped authenticated HTTP client leaking a server session.
Fixed
any::connectcreating 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 withoutValuein scope.Fixed
REMOVE MODELdeleting 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
Unavailableafter 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.
Upgrade a Cloud instance to v4.0.0-beta.1 in four steps:
- Open SurrealDB Studio
- Select your organisation and instance
- On the dashboard, select “Upgrade”
- The instance updates and restarts automatically