---
title: "Release 3.3 | SurrealDB"
description: "SurrealDB 3.3.0 lets any Postgres client query SurrealDB, streams results as they are produced, and makes graph traversals and filtered searches much faster.…"
url: https://surrealdb.com/releases/3.3
---

# SurrealDB 3.3

[Latest patch: 3.3.0](https://surrealdb.com/releases/3.3#release-3-3-0) [All releases](https://surrealdb.com/releases)

**Patches.** 5 patch releases on the 3.3 line.

**Latest.** 3.3.0, released Sep 24, 2026.

**Stable.** Ready for production, with every patch's notes below.

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 3.3.0](https://surrealdb.com/releases/3.3#release-3-3-0)

[🐘 Connect with any Postgres client](https://surrealdb.com/releases/3.3#3-3-0--connect-with-any-postgres-client) [📡 Results that stream as they are produced](https://surrealdb.com/releases/3.3#3-3-0--results-that-stream-as-they-are-produced) [🕸 Faster graph traversals, and edges that carry their own data](https://surrealdb.com/releases/3.3#3-3-0--faster-graph-traversals-and-edges-that-carry-their-own-data) [🎯 Indexes that combine, and vector search that filters first](https://surrealdb.com/releases/3.3#3-3-0--indexes-that-combine-and-vector-search-that-filters-first) [🔒 Read-modify-write without losing an update](https://surrealdb.com/releases/3.3#3-3-0--read-modify-write-without-losing-an-update) [🪪 Permissions resolved once per session, and stricter JWT checks](https://surrealdb.com/releases/3.3#3-3-0--permissions-resolved-once-per-session-and-stricter-jwt-checks) [⚡ Faster full-text indexing, and search in Chinese, Japanese and Korean](https://surrealdb.com/releases/3.3#3-3-0--faster-full-text-indexing-and-search-in-chinese-japanese-and-korean) [🪣 Store files in S3, Google Cloud Storage or Azure](https://surrealdb.com/releases/3.3#3-3-0--store-files-in-s3-google-cloud-storage-or-azure) [🔑 Pull SurrealDB Enterprise from Docker Hub (Enterprise)](https://surrealdb.com/releases/3.3#3-3-0--pull-surrealdb-enterprise-from-docker-hub-enterprise) [Query results](https://surrealdb.com/releases/3.3#3-3-0-query-results) [Writes and schema](https://surrealdb.com/releases/3.3#3-3-0-writes-and-schema) [Indexes](https://surrealdb.com/releases/3.3#3-3-0-indexes) [Import and export](https://surrealdb.com/releases/3.3#3-3-0-import-and-export) [Server, RPC and live queries](https://surrealdb.com/releases/3.3#3-3-0-server-rpc-and-live-queries) [Storage](https://surrealdb.com/releases/3.3#3-3-0-storage) [SDKs and embedded engines](https://surrealdb.com/releases/3.3#3-3-0-sdks-and-embedded-engines) [SurrealDS (Enterprise)](https://surrealdb.com/releases/3.3#3-3-0-surrealds-enterprise)

[Version 3.3.0-beta.4](https://surrealdb.com/releases/3.3#release-3-3-0-beta-4)

[Version 3.3.0-beta.3](https://surrealdb.com/releases/3.3#release-3-3-0-beta-3)

[Version 3.3.0-beta.2](https://surrealdb.com/releases/3.3#release-3-3-0-beta-2)

[Version 3.3.0-beta.1](https://surrealdb.com/releases/3.3#release-3-3-0-beta-1)

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

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

3.3

SurrealDB 3.3.0 lets any Postgres client query SurrealDB, streams results as they are produced, and makes graph traversals and filtered searches much faster.…

Released Sep 24, 2026

## 3.3.0

LATEST STABLE

SurrealDB 3.3.0 lets any Postgres client query SurrealDB, streams results as they are produced, and makes graph traversals and filtered searches much faster. Indexes now combine as bitmaps, so a filtered or vector query reads far fewer records, and graph edges can carry their own data in the adjacency structure.

It also adds locked reads with `SELECT ... FOR UPDATE`, per-session permission context and JWT audience checks on `DEFINE ACCESS`, S3, GCS and Azure storage for file buckets, and word segmentation for Chinese, Japanese and Korean full-text search. ISO GQL (Cypher Query Language) is now available without an experimental flag.

A 3.2 datastore upgrades in place, and 3.3.0 applies its data migrations automatically on first start. Read the [3.2 to 3.3 upgrade guide](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33) before upgrading a cluster or a deployment with vector indexes. Fixes already released in `v3.2.1` to `v3.2.4` are included and not repeated here.

Items tagged `(Enterprise)` on a heading or `[Enterprise]` on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

## Highlights

### 🐘 Connect with any Postgres client

Any Postgres client or driver, including `psql`, JDBC, npgsql and tokio-postgres, can now connect to SurrealDB and run SurrealQL or ISO GQL (Cypher Query Language) with typed results, so existing Postgres tooling works without a SurrealDB SDK. Turn the listener on with `--postgres-bind`. Connections support TLS and SCRAM-SHA-256 authentication, and `DEFINE USER` now stores a SCRAM verifier alongside the password hash, so a user whose password is set on 3.3 signs in from a Postgres client without sending it in cleartext.

[Read the Postgres wire protocol docs](https://surrealdb.com/docs/reference/rest-api/postgres-protocol) · [SCRAM credentials](https://surrealdb.com/docs/reference/query-language/statements/define/user#scram-credentials-for-postgres-clients)

### 📡 Results that stream as they are produced

Query results now stream to the client row by row instead of arriving as one buffered response, so the first rows of a large `SELECT` reach an application while the rest are still being produced. Streaming works over the new gRPC transport (`grpc://` and `grpcs://`), through the new `query_stream` WebSocket RPC method, and in the embedded Node.js and browser engines. The `surreal sql` shell also connects over gRPC.

[gRPC transport](https://surrealdb.com/docs/reference/rest-api/rpc-protocol#grpc-transport) · [The `query_stream` method](https://surrealdb.com/docs/reference/rest-api/rpc-protocol#query_stream) · [Stream results in Rust](https://surrealdb.com/docs/reference/rust/methods/query#stream-items)

### 🕸 Faster graph traversals, and edges that carry their own data

Graph adjacency is now stored in compact blocks, so traversals over high-degree vertices and from many starting records run substantially faster, and `count(->edge)` is answered without loading the edges. Three new schema options keep more of a traversal inside the adjacency structure:

- `LIGHTWEIGHT` relations store nothing beyond `in` and `out`, which suits pure adjacency such as follows or likes.
- `INLINE` fields keep an edge property in the adjacency entry, so `->(likes WHERE score > 5)` is answered without reading edge records.
- `INLINE EDGES` and `INLINE REFERENCES` cache a vertex's edges and back-links, so a low-degree lookup needs one read instead of a range scan.

[Lightweight relations](https://surrealdb.com/docs/reference/query-language/statements/define/table#using-lightweight-for-relations-without-properties) · [Inline fields](https://surrealdb.com/docs/reference/query-language/statements/define/field#using-inline-to-filter-a-traversal-during-the-scan) · [Inline edge caches](https://surrealdb.com/docs/reference/query-language/statements/define/table#using-inline-edges-and-inline-references-to-cache-adjacency)

### 🎯 Indexes that combine, and vector search that filters first

Index-backed conditions joined with `AND`, `OR` and `NOT` now combine as bitmaps, so a query that filters on several indexed fields reads only the records that match all of them, and a `count()` with `GROUP ALL` over indexed conditions can be answered without reading any records. Vector search uses the same bitmaps: `WHERE` conditions covered by an index become an allow-list before the HNSW or DiskANN search runs, so a filtered similarity search returns `k` matching results without over-fetching. `EXPLAIN ANALYZE` shows which plan and filter tier a query used.

[How SurrealDB combines indexes](https://surrealdb.com/docs/learn/querying/concepts-and-guides/query-optimisation#how-surrealdb-combines-indexes) · [Pre-filtered vector search](https://surrealdb.com/docs/learn/data-models/vector-search/similarity-search#pre-filtering-with-an-index) · [Reading the plan](https://surrealdb.com/docs/reference/query-language/clauses/explain#bitmap-index-fusion)

### 🔒 Read-modify-write without losing an update

`SELECT ... FOR UPDATE` registers the records it reads for conflict detection, so a transaction that reads a record and then writes based on it fails to commit if another transaction changed that record in between, instead of silently overwriting the change. It works on every community storage backend.

[Read the `FOR UPDATE` docs](https://surrealdb.com/docs/reference/query-language/statements/select#the-for-update-clause)

### 🪪 Permissions resolved once per session, and stricter JWT checks

`DEFINE ACCESS ... CONTEXT` computes a value once when a record user signs in and exposes it as `$session.data`, so a row permission such as `WHERE org IN $session.data` no longer runs a subquery for every row. `DEFINE ACCESS ... AUDIENCE` rejects a JWT whose `aud` claim names a different service, even when the same identity provider issued it.

[Access `CONTEXT`](https://surrealdb.com/docs/reference/query-language/statements/define/access#with-context-clause) · [JWT `AUDIENCE`](https://surrealdb.com/docs/reference/query-language/statements/define/access/jwt#audience-validation)

### ⚡ Faster full-text indexing, and search in Chinese, Japanese and Korean

Full-text indexes now batch their changes per transaction and key postings by document, so indexed writes run 2.3 to 2.6 times faster and updates touch fewer keys. The new `SEGMENT` tokenizer splits Chinese, Japanese and Korean text into words, so a search for a single word matches inside a sentence.

[Read the `SEGMENT` tokenizer docs](https://surrealdb.com/docs/reference/query-language/statements/define/analyzer#segmentlanguage)

### 🪣 Store files in S3, Google Cloud Storage or Azure

`DEFINE BUCKET` in the open-source engine can now use AWS S3 and S3-compatible stores such as MinIO and Cloudflare R2, Google Cloud Storage, or Azure Blob Storage, so files managed with the `file::*` functions can live in object storage.

[Cloud object storage backends](https://surrealdb.com/docs/reference/query-language/statements/define/bucket#cloud-object-storage-backends)

### 🔑 Pull SurrealDB Enterprise from Docker Hub (Enterprise)

SurrealDB Enterprise is published as a public Docker Hub image, `surrealdb/surrealdb-enterprise`, so a deployment can pull it without a registry credential to provision or rotate. The image needs a licence key to run, supplied in `SURREAL_LICENSE_KEY` or as a file named by `SURREAL_LICENSE_KEY_FILE`. Each node validates its licence when it starts and every 30 minutes after that, and keeps serving for 24 hours if the licence service cannot be reached.

[Licence keys](https://surrealdb.com/docs/manage/enterprise#licence-key) · [Run SurrealDB Enterprise in Docker](https://surrealdb.com/docs/manage/self-hosted/docker#running-surrealdb-enterprise)

## Improvements

- `[Upgrades]` **Upgrades apply their own data migrations.** The datastore records its version and runs any pending data migrations on first start, so a minor upgrade fixes stored data in place without an export and import. The first migration fixes databases whose table names begin with `sq`, which broke `INFO FOR DB`, export and `REMOVE DATABASE`. [Automatic data migrations](https://surrealdb.com/docs/manage/self-hosted/upgrades-and-patching#automatic-data-migrations)
- `[Query]` **ISO GQL (Cypher Query Language) is available by default.** GQL queries run over `/gql`, the `gql` RPC method, MCP and the Postgres listener without `--allow-experimental`. [GQL entry surfaces](https://surrealdb.com/docs/learn/security/authorization/capabilities#gql-entry-surfaces)
- `[Query]` **`ORDER BY` accepts a field outside the projection**, so a query can sort by a field without returning it. [ORDER BY](https://surrealdb.com/docs/reference/query-language/clauses/order)
- `[Functions]` **New vector functions.** `vector::sum` adds vectors element-wise, as a function or as a `GROUP BY` aggregate, so a centroid needs no manual fold. `vector::distance::mahalanobis` and `vector::similarity::spearman` now return results. [Vector functions](https://surrealdb.com/docs/reference/query-language/functions/database-functions/vector#vectorsum)
- `[API]` **Per-endpoint body limits.** The `api::req::max_body` middleware answers HTTP 413 before a large body is decoded. [api::max_body](https://surrealdb.com/docs/reference/query-language/functions/database-functions/api#apireqmax_body)
- `[HTTP]` **Query timeouts are enforced at the transport.** When `--query-timeout` is set, HTTP and RPC requests also stop at that wall-clock limit, including a query that never reaches a checkpoint, and an HTTP timeout returns `504 Gateway Timeout`. [Request timeouts](https://surrealdb.com/docs/reference/rest-api/http-protocol#request-timeouts)
- `[HTTP]` **Optional sign-in rate limiting.** `/signin` and `/signup` can limit attempts per client address and answer `429` with `Retry-After`, which slows password guessing. [Rate limiting settings](https://surrealdb.com/docs/reference/cli/surrealdb-cli/environment-variables#authentication-rate-limiting-environment-variables)
- `[MCP]` **Stateless MCP.** The MCP server also serves the stateless `2026-07-28` protocol revision, and tools take optional `namespace` and `database` arguments, so a client can scope each call without a session. [MCP protocol versions](https://surrealdb.com/docs/build/ai-agents/mcp/embedded#protocol-versions)
- `[Server]` **Health probes answer during a slow start.** When startup work takes longer than a few seconds, the HTTP listener binds first, so `/health` responds and query endpoints return `503` with `Retry-After` while `/ready` reports not ready, instead of refusing connections. [Startup and health checks](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33#startup-and-health-checks)
- `[Index]` **`INFO FOR INDEX` reports a compaction backlog**, so an index that is ready but still catching up on compaction is visible without reading the logs. [INFO FOR INDEX](https://surrealdb.com/docs/reference/query-language/statements/info#index-information)
- `[Index]` **Fewer commits for bulk inserts and index builds.** Internal document ids are reserved in windows rather than committed one record at a time, which removes one commit per record on indexed writes and index builds.
- `[Index]` **Faster `COUNT` indexes on write-heavy tables.** Count changes are aggregated per transaction and the compactor runs as soon as work arrives.
- `[Vector]` **Bounded memory for vector search during bulk ingest.** A kNN search now bounds how much not-yet-compacted index data it loads, so searches during a large ingest no longer copy the whole backlog into memory. The in-memory HNSW graph also survives a failed compaction.
- `[Storage]` **Removed data is reclaimed in bounded, resumable batches.** Dropping a large namespace, database, table or index no longer builds one unbounded transaction, and reclaim continues after a restart.
- `[Memory]` **Accurate memory accounting.** `SURREAL_MEMORY_THRESHOLD` now compares against the memory the allocator actually holds, so an instance no longer refuses queries with memory to spare, and four new gauges show how close an instance is to its threshold. [Metrics](https://surrealdb.com/docs/manage/observability/metrics)
- `[Import/Export]` **Faster imports and restores.** Literal values skip the evaluator, exported `INSERT` batches are sized by the keys each record writes, and `CREATE |table:N|` streams its ids, so restores of large or heavily indexed tables run up to about twice as fast.
- `[Query]` **Index matching through record traversals.** A condition such as `WHERE createdAt <= $scan.task.finishedAt` now uses a matching index instead of scanning the table.
- `[Embedded]` **The embedded JavaScript engines run the current engine.** The Node.js and browser engines are now built and released with the server, as `@surrealdb/node-native` and `@surrealdb/wasm-native`, so they carry every fix in this release. Each Node.js platform binary is its own package, so an install downloads only the one it needs.
- `[Embedded]` **More control for embedders.** An embedded datastore can swap its capabilities at runtime without a rebuild, and can back buckets with a custom object store such as a Cloudflare R2 binding.
- `[Storage]` **Storage runtime updates.** The blocking-work pools (`affinitypool` 0.8) are faster and fix a pool deadlock, the memory backend releases savepoint memory as it goes, and TiKV errors are logged with their gRPC status.
- `[Enterprise]` **Enterprise buckets use the same object-store backends as community**, so Google Cloud Storage and Azure work alongside S3.
- `[Enterprise]` **Locked reads on the single-node engine.** `SELECT ... FOR UPDATE` works on the SurrealDB Enterprise single-node engine.

## Bug fixes

### Query results

- Fixed full-text `@@` returning `true` for every row on the default read path, so `WHERE title @@ 'word'` matched rows that did not contain the word.
- Fixed a full-text match through a record link (`WHERE author.name @@ 'x'`) returning every row when no join index existed.
- Fixed an approximate kNN query (`<|k, ef|>`) on a field with no vector index returning every row.
- Fixed a kNN query combined with a unique-index equality, or with a full-text match, returning no rows.
- Fixed HNSW and DiskANN searches returning fewer than `k` rows when a record id held a number inside an array or object, such as `chunk:[42, 'intro']`. An index built before 3.3 picks up the fix when it is rebuilt.
- Fixed a full-text index giving one entry to two records whose ids differ only in a nested number's type, such as `c:[1]` and `c:[1dec]`, which left one of them out of `@@` results. A full-text index built before 3.3 picks up the fix when it is rebuilt.
- Fixed equal numbers of different types (`1`, `1.0`, `1dec`) forming separate `GROUP BY` groups, being returned twice by `RETURN DISTINCT`, and dropping rows from `IN`, `INSIDE`, `CONTAINSANY` and `CONTAINSALL`.
- Fixed `CONTAINSANY` conditions being dropped from a multi-value index query, which returned rows the `WHERE` clause excluded.
- Fixed rows with equal `ORDER BY` keys coming back in an unstable order, so paging with `LIMIT` and `START` could repeat or skip rows.
- Fixed `ORDER BY` on a permission-restricted field combined with `LIMIT` returning the wrong rows.
- Fixed `SELECT count()` returning `0` when a table permission read another table, while `SELECT` returned the rows.
- Fixed `rand::*` in a `WHERE` clause being evaluated once for the whole query, so `WHERE rand::float() < 0.5` kept all rows or none.
- Fixed an inline subquery changing shape with its row count, so `FOR $r IN (SELECT ...)` over an empty result failed and a single row arrived unwrapped.
- Fixed a stored function or field expression losing its parentheses, so `$x / ($a * $b)` came back as `($x / $a) * $b` and computed a different value.
- Fixed a `PERMISSIONS` rule that reads the row through `$parent` hiding every row from record users.
- Fixed `math::variance` and `math::stddev` returning population rather than sample figures in `GROUP BY` queries and table views.
- Fixed `x[WHERE ...]` on a value that is not an array returning a one-element array instead of `NONE`.

### Writes and schema

- Fixed JSON Patch pointers through an array index, such as `/details/0/isbn`, reporting success without applying the change.
- Fixed a failed statement leaving behind the records and index writes of a synchronous event it triggered. On TiKV the fix also roughly halves write round trips for multi-record `INSERT` and `UPSERT`.
- Fixed `sequence::nextval()` reissuing values already in use after an export and import, and `DEFINE SEQUENCE OVERWRITE` needing a restart to take effect.
- Fixed a statement that writes inside a `LIMIT`, `START`, `FETCH` or similar clause running on a read-only transaction and failing partway.
- Fixed a selective `RETURN` on `CREATE` or `DELETE` failing with `Cannot perform multiplication with 'none' and 'int'`.
- Fixed `+=` on a `set` field skipping coercion and de-duplication.
- Fixed a field named with a keyword, such as `function` or `select`, breaking its table's schema.
- Fixed the in-memory backend refusing to create a record that was deleted earlier in the same transaction.
- Fixed a closure stored in a variable or object field failing when its body queried the database, and a closure field named like a built-in method (for example `get`) failing on the default read path.
- Fixed `ACCESS ... REVOKE` and `PURGE` against a misspelled access method returning an empty success, and `PURGE` creating a missing namespace.

### Indexes

- Fixed an index rebuild publishing an index as online but empty, after which queries against it returned nothing.
- Fixed `REBUILD INDEX` on an HNSW or DiskANN index written by 3.1.0 to 3.2.0 spinning at full CPU forever.
- Fixed `REBUILD INDEX` on a vector index failing with a write conflict against background compaction.
- Fixed writes that were in flight during `DEFINE INDEX OVERWRITE` skipping the replacement index.
- Fixed full-text compaction occasionally keeping a term a document no longer contains, which produced false `@@` matches.
- Fixed a cancelled `DEFINE INDEX ... CONCURRENTLY` leaving build state behind.

### Import and export

- Fixed a database with an `ENFORCED` relation table dropping every edge when its own export was restored. Existing export files restore correctly.

### Server, RPC and live queries

- Fixed a query and a concurrent `use`, `set` or `signin` on the same connection deadlocking the session over WebSocket, HTTP and gRPC.
- Fixed a WebSocket client that stops reading notifications driving the server out of memory. Replies and notifications now have separate bounded queues, and a client that stops reading is closed with a reason.
- Fixed a live query ended by `KILL` or `REMOVE TABLE` staying registered, with its `surrealdb.live_query.active` gauge, for the life of the connection.
- Fixed the WebSocket close handshake, so clients see a clean close instead of a connection reset.
- Fixed write transactions leaking on error paths in the `use` RPC method, the async event queue and `DEFINE API` handlers.
- Fixed `/signin` and `/signup` each applying the other's body-size limit.
- Fixed archived-node cleanup delaying the HTTP listener for minutes after a restart storm, and cleanup retiring a node that had just re-registered.
- Fixed two first uses of the same bucket deadlocking the server, which happened at startup when two Surrealism modules lived in one bucket.
- Fixed a zero interval for a scheduled maintenance job, such as `--changefeed-gc-interval 0` or `--reclaim-interval 0`, crashing the server at startup. A zero interval now disables that job.
- Fixed `surreal mcp` and embedded datastores running no background maintenance, so their index delta logs grew without bound.

### Storage

- Fixed a SurrealKV node killed during compaction failing to start again (surrealkv 0.21.3).

### SDKs and embedded engines

- Fixed a cloned Rust SDK WebSocket session sending its first request before its replayed `signin` and `use` took effect.
- Fixed a dropped live-query stream staying registered on the embedded engine.
- Fixed an empty `use()` on the embedded engine clearing the session instead of applying `DEFINE CONFIG DEFAULT`.
- Fixed `RecordId::parse_simple` adding a layer of backticks on every round trip.
- Fixed `http::*` functions failing in WebAssembly builds, and the WebAssembly HTTP client sending no `User-Agent`.
- Fixed `fetch()` with `redirect: "error"` or `"manual"` refusing hosts the capabilities allow.
- Fixed some built-in functions behaving differently on the default read path: `array::every`, `array::some`, `array::includes` and `array::index_of` were missing, and extra arguments were ignored instead of rejected.

### SurrealDS (Enterprise)

- Fixed two concurrent read-modify-writes of one key both committing, which could lose an acknowledged write and let a `UNIQUE` index hold a duplicate.
- Fixed a read on the RocksDB store committing after missing a concurrent write to a key that a later range delete covered.
- Fixed recovery aborting a transaction that had already committed, after a replica learned of the commit through catch-up replay.
- Fixed a backup coordinator deciding a transaction differently from its original coordinator.
- Fixed replicas holding contradictory outcomes for one transaction refusing to recover or install a view. They now converge and log both outcomes.
- Fixed a transaction in flight across a membership change stalling reads on that node for several minutes. It now rejoins the new membership or fails with a retryable conflict.
- Fixed a pending view change wedging a node's startup warm-up, which made the node exit and restart into the same state.
- Fixed a failed statement leaving behind the records and index entries written by a synchronous event.
- Fixed an indeterminate commit being reported as a definite failure. It is now `CommitOutcomeUnknown`, which tells the client to read back rather than replay a write that may have landed.
- Fixed a lost write race on the single-node engine returning a datastore error instead of a retryable `TransactionConflict`, so SDK retry helpers replay it.

## Security

This release addresses a set of issues surfaced through SurrealDB's internal security review process and external reviewers. Each affected `v3.2.4` and earlier, and each fix ships in `v3.3.0` and is enabled by default.

- **Cross-tenant access through `USE` or the `Surreal-NS` / `Surreal-DB` headers** **Critical** - [GHSA-vx2p-7hhm-wv62](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-vx2p-7hhm-wv62), [GHSA-2v9j-645m-pcrq](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2v9j-645m-pcrq), [GHSA-w5v9-jc5f-5rmc](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-w5v9-jc5f-5rmc)
- **Cross-tenant record read by record users through `FETCH`** **Moderate** - [GHSA-3vw5-j55x-486h](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-3vw5-j55x-486h)
- **Cross-tenant GraphQL schema disclosure** **Moderate** - [GHSA-2rc3-x55m-fh7v](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2rc3-x55m-fh7v)
- **Field-level `SELECT` permission bypass through `PATCH` pointers** **High** - [GHSA-h2c3-549c-gjpr](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-h2c3-549c-gjpr)
- **Field-level `SELECT` permission bypass in `LIVE SELECT` through `$after` / `$before`** **High** - [GHSA-pm6c-q8rh-92m8](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-pm6c-q8rh-92m8), [GHSA-jm88-9wvx-6h4q](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-jm88-9wvx-6h4q)
- **MCP session takeover between users of one JWT or bearer access method** **High** - [GHSA-pgh9-49q4-hqcx](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-pgh9-49q4-hqcx)
- **Network capability bypass (SSRF) through IPv6 transition addresses** **High** - [GHSA-c9wc-jcr5-8974](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-c9wc-jcr5-8974)
- **Computed fields disclose `SELECT`-restricted values** **Moderate** - [GHSA-2jj6-v2fq-rwpp](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2jj6-v2fq-rwpp)
- **Indexes on restricted fields act as a value oracle** **Moderate** - [GHSA-x6mm-9295-rvph](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-x6mm-9295-rvph)
- **Write permissions read `COMPUTED` fields as `NONE`** **Moderate** - [GHSA-f6h4-5xf2-86q9](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-f6h4-5xf2-86q9)
- **Edge delete permission bypass through `REFERENCE ON DELETE CASCADE`** **Moderate** - [GHSA-4w65-7h3j-p29h](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-4w65-7h3j-p29h)
- **Unauthenticated `revoke` RPC invalidates refresh tokens** **Moderate** - [GHSA-2jc3-58q7-mm2j](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-2jc3-58q7-mm2j)
- **Port-specific `--deny-net` rules bypassed by host aliasing or implicit JWKS ports (SSRF)** **Moderate** - [GHSA-mhv5-88p6-w4c3](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-mhv5-88p6-w4c3), [GHSA-h3mp-7wcg-p6vj](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-h3mp-7wcg-p6vj)
- **Deeply nested stored values can crash the server** **Moderate** - [GHSA-rpmq-gq3f-h6jg](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-rpmq-gq3f-h6jg), [GHSA-6f54-4f8m-vqxp](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-6f54-4f8m-vqxp)
- **Surrealism module output can exhaust host memory** **Moderate** - [GHSA-jj89-wvrw-c4gp](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-jj89-wvrw-c4gp)
- **Non-UTF-8 `fetch()` response headers can crash the server** **Low** - [GHSA-84w9-87hm-mxqx](https://github.com/surrealdb/surrealdb/security/advisories/GHSA-84w9-87hm-mxqx)

The release also carries these security fixes, which have no separate advisory:

- `AUTHENTICATE` clauses on namespace, database and record access methods now run with that method's own privileges during token verification, instead of root privileges.
- A `COMPUTED` field now runs with its definer's rights, so a field defined by a lower-privileged user cannot read data with the privileges of whoever selects it.
- Functions denied with `--deny-funcs` no longer run while a query is planned.
- Record users stay inside their own namespace and database on every write.
- A `USE NS` or `USE DB` statement whose argument queries the database no longer crashes the server.
- The MCP `gql` tool is no longer marked read-only, so MCP clients do not auto-approve a GQL statement that writes.
- On the embedded engine, an expired session can no longer change session variables, and live queries end when the signed-in user changes.
- Dependency updates clear `RUSTSEC-2026-0222`, `RUSTSEC-2026-0223`, `RUSTSEC-2026-0268` and `RUSTSEC-2026-0269` (`wasmtime`, used by Surrealism) and `RUSTSEC-2026-0258` (`h2`), and the `-dev` Docker images are rebuilt on a base image with patched busybox and wget.
- `[Enterprise]` The QUIC transport between SurrealDS nodes moves to quinn-proto 0.11.18, which fixes three remote memory-exhaustion issues ([GHSA-4w2j-m93h-cj5j](https://github.com/quinn-rs/quinn/security/advisories/GHSA-4w2j-m93h-cj5j), [GHSA-2hv7-gw8g-gpq5](https://github.com/quinn-rs/quinn/security/advisories/GHSA-2hv7-gw8g-gpq5), [GHSA-hmxj-32vh-65vr](https://github.com/quinn-rs/quinn/security/advisories/GHSA-hmxj-32vh-65vr)) and a remote panic ([GHSA-465w-v9q3-7j98](https://github.com/quinn-rs/quinn/security/advisories/GHSA-465w-v9q3-7j98)).
- `[Enterprise]` The Enterprise image is built on a base image with patched busybox and wget.

## Upgrade notes

The [3.2 to 3.3 upgrade guide](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33) covers each of these in detail.

- **Take an export first if you may need to roll back.** A 3.2 binary still starts on a datastore that 3.3.0 has opened, but it reads the migrated data wrongly, so the way back to 3.2 is to restore an export taken before the upgrade. [Rolling back to 3.2](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33#rolling-back-to-32)
- **Rolling upgrades.** 3.2 and 3.3.0 nodes do not fully interoperate. Until every node runs 3.3.0, a sequence, schema change or new table created on an upgraded node is invisible to or unreadable by older nodes, and an older node fails kNN queries and writes against an HNSW or DiskANN index that an upgraded node has written to. Let index builds finish before the rollout starts. [Clusters and rolling upgrades](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33#clusters-and-rolling-upgrades)
- **Indexes.** Indexes built by 3.2 keep answering queries, and the upgrade requires no rebuild. A `REBUILD INDEX`, run whenever it suits the workload, lets an index take part in bitmap index fusion and pre-filtered vector search. It also applies the two index fixes for record ids that hold a nested number, listed under Query results. [Indexes](https://surrealdb.com/docs/build/migrating/from-old-surrealdb-versions/32-to-33#indexes)
- **In-memory backend.** `mem://` no longer supports `VERSION` queries and refuses `versioned=true` and a non-zero `retention` at startup. Use SurrealKV or RocksDB for versioned reads.
- **ISO GQL (Cypher Query Language) is on by default.** A deployment that relied on GQL being off closes it through the route and capability settings. [GQL entry surfaces](https://surrealdb.com/docs/learn/security/authorization/capabilities#gql-entry-surfaces)
- **`UPDATE` and `UPSERT` evaluate `WHERE` first.** The condition runs before the data clause, and both read one image of the record taken before the write. [Evaluation order](https://surrealdb.com/docs/reference/query-language/statements/update#evaluation-order)
- **Field clauses run in dependency order.** A field whose `DEFAULT`, `VALUE` or `COMPUTED` clause reads another field is evaluated after that field, where 3.2 used name order, so renaming a field can no longer break a working schema. `ASSERT` runs once every field holds its final value, and events, live queries and changefeeds receive computed fields in `$before` and `$after`. [Field evaluation order](https://surrealdb.com/docs/reference/query-language/statements/define/field#field-evaluation-order)
- **Permission and `COMPUTED` write rules.** `create`, `update` and `delete` permission clauses may perform writes, such as audit logging. A write reached from a `select` permission clause, or from a `COMPUTED` field through a function call, fails when it runs. A `COMPUTED` body that writes directly is refused at `DEFINE`.
- **Security-related behaviour changes.** Values nested deeper than 256 levels are rejected on write. Cross-tenant requests are refused with `NotAllowed` (HTTP 403). `@@` against a field the caller cannot read is refused. The `revoke` RPC method needs the refresh token itself. Audit subjects for JWT and bearer access methods include the token subject.
- **Functions.** The never-implemented `duration::set_*` functions and `object::matches` are now parse errors. Vector functions return errors instead of `NaN` for zero-length or degenerate input, and `vector::similarity::jaccard` uses set semantics. `time::nano` returns an error for a datetime outside the years 1677 to 2262 instead of `0`.
- **Configuration and metrics.** `SURREAL_FTS_DOC_IDS_BATCH_SIZE` is renamed `SURREAL_TABLE_DOC_IDS_BATCH_SIZE`. Rolled-back transactions are counted as `outcome="cancelled"` on `surrealdb_transaction_total`, so `outcome="success"` reads lower. `INFO FOR INDEX` always includes `compacting`. The storage thread pool no longer pins its workers to CPU cores.
- **Binary size.** Release binaries embed the Chinese, Japanese and Korean dictionaries for the `SEGMENT` tokenizer, which adds about 150 MB.
- **JavaScript SDK.** The embedded engines are published as `@surrealdb/node-native` and `@surrealdb/wasm-native`, and the `strict` connection option, which had no effect, is removed.
- **Rust SDK.** Query futures are `Send` but no longer `Sync`, engines implement the `SurrealEngine` trait, and `surrealdb-core` no longer re-exports `surrealdb-rpc`.
- **Surrealism (experimental).** `DEFINE MODULE` uses `FROM` instead of `AS`, requires `UNSIGNED`, and writes silo versions as `silo::org::pkg::<1.0.0>`. [DEFINE MODULE](https://surrealdb.com/docs/reference/query-language/statements/define/module#statement-syntax)
- `[Enterprise]` **A licence key is required.** A node with no valid licence refuses to start, and an expired licence cannot start a node. The first start after upgrading needs to reach `api.keygen.sh`, because a licence response cached by an earlier build is discarded. `v3.3.0-beta.4` cannot validate production licences, so upgrade beta.4 nodes before using one. [Licence keys](https://surrealdb.com/docs/manage/enterprise#licence-key)
- `[Enterprise]` **`SURREAL_REMOTE_BUCKETS_READONLY` is removed.** Remote buckets it made read-only become writable, so define each one with `READONLY` where that matters.

Upgrade or install

## Get SurrealDB v3.3.0

Pick how you want to install or upgrade. SurrealDB Studio can update connected instances in place, or choose a platform below to copy a CLI command for v3.3.0.

SurrealDB Cloud

macOS

Linux

Windows

Docker

Already installed

You can upgrade your SurrealDB Cloud instance to v3.3.0 effortlessly through SurrealDB Studio.

1. Open the [SurrealDB Studio](https://studio.surrealdb.com/current/instances)
2. Select your organisation and instance
3. On the dashboard, click on the "Upgrade" button
4. Your instance will be updated and restarted automatically

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

Released Sep 9, 2026

## 3.3.0-beta.4

PRE-RELEASE

The fourth 3.3 beta is the largest of the line: a reworked graph engine with new relation types and much faster traversals, access-control additions to `DEFINE ACCESS`, commit-time locked reads, and a public, licence-enforcing SurrealDB Enterprise image on Docker Hub, alongside a round of storage, index, vector, and networking fixes. Everything new in 3.3 from `v3.3.0-beta.1`, `v3.3.0-beta.2` and `v3.3.0-beta.3` is included and not repeated here.

Items tagged `(Enterprise)` on a heading or `[Enterprise]` on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

## Highlights

### 🕸 Faster traversals, and edges that carry their own data

The traversal storage and execution path has been rebuilt, and a relation table gains two ways to keep an edge's data in the adjacency structure itself, so a filtered traversal can answer without loading the edge records at all.

- **Traversals over hub vertices get substantially faster.** Adjacency is packed into blocks maintained by a background fold, multi-source fan-outs run concurrently and coalesce shared work, record resolves overlap cursor fetches, adjacency key scans use a zero-copy cursor path, and `ORDER BY id ... LIMIT` traversals stop early at the scan. The gain is largest over high-degree vertices and from many starting records, and `count(->edge)` degree counts are served straight from adjacency storage instead of materialising every edge.
- **`LIGHTWEIGHT` relations drop the storage cost of a pure adjacency edge.** `DEFINE TABLE follows TYPE RELATION IN person OUT person LIGHTWEIGHT` creates record-less edges storing nothing beyond `id`, `in` and `out`. `RELATE` is idempotent and returns an edge synthesised on the fly, with the canonical id `follows:[person:a, person:b]`; traversals, scans, `count()`, ordering, point reads and projections synthesise it likewise, and deleting a vertex cascades to its edges. `IN` / `OUT` are required and `ENFORCED` is implied. Such a relation cannot carry fields, indexes or events, take a data clause or a custom edge id, be `UPDATE`d or `INSERT`ed into, or be `SCHEMAFULL`, `CHANGEFEED` or `DROP`. They suit pure adjacency (follows, likes) where the edge needs no properties.
- **`INLINE` fields answer a filtered traversal without reading edge records.** `DEFINE FIELD score ON likes TYPE number INLINE` embeds the field value into the edge's adjacency entry, with `ALTER FIELD ... INLINE` / `... DROP INLINE` to change it later. A traversal whose predicate reads only inline fields - `->(likes WHERE score > 5)` - is then answered from the adjacency payload, byte-identical to the record path. Only top-level, non-`COMPUTED` fields on an existing `TYPE RELATION` table qualify (not `id` / `in` / `out`, and not on `LIGHTWEIGHT` relations). A payload past its size cap spills to the edge record, and a predicate touching any non-inline field falls back to the record path.
- **A per-vertex cache serves adjacency and back-links without a key scan.** `DEFINE TABLE` and `ALTER TABLE` accept `INLINE EDGES <n>` and `INLINE REFERENCES <n>` caps, 0 to 256, removed with `DROP INLINE EDGES` / `DROP INLINE REFERENCES`. These maintain a compact per-vertex sidecar cache of a table's edge adjacency and of its reverse-reference back-links, serving traversals and reverse-reference lookups without walking the full key range, with results identical to an uncached table. A zero cap disables the cache, and a vertex whose set outgrows the cap falls back to the key scan.

### 🪪 Row permissions that resolve once per session, and stricter JWT checks

- **`CONTEXT` computes a permission's payload once per session instead of once per row.** `DEFINE ACCESS ... TYPE RECORD` accepts an optional `CONTEXT ( <expr> )`, evaluated once at authentication time with `$auth` bound to the authenticated record and under that record's own permissions. Its whole result is frozen on the session and read back as `$session.data`, so a permission can filter on the precomputed payload - `PERMISSIONS FOR select WHERE org IN $session.data` - rather than re-running a subquery for every row. The snapshot lasts the life of the session, bounded by `DURATION FOR SESSION`, and is recomputed on re-authentication; `$auth` is untouched and keeps its usual per-row fetch semantics. The clause must be read-only and attaches only to a method that authenticates a record. `ALTER ACCESS ... CONTEXT { ... }` sets it on an existing method, `ALTER ACCESS ... DROP CONTEXT` removes it.
- **`AUDIENCE` rejects a token minted for a different service.** `DEFINE ACCESS ... TYPE JWT`, and record access configured `WITH JWT`, accepts an optional `AUDIENCE "aud1", "aud2"` clause. When present, a presented token's `aud` claim - a single string or an array - must contain at least one configured value, and the claim becomes mandatory, so a token omitting it is rejected. Methods defined without the clause continue to ignore `aud`, so existing definitions are unaffected. It works at root, namespace and database level, and the configured audiences are surfaced by `INFO`.

### 🔒 Read-modify-write without losing an update

`SELECT ... FOR UPDATE` takes a commit-time lock on the selected records, so a read-then-write sequence can no longer be overwritten by a concurrent writer between the two steps. The read runs on a writeable transaction and registers each fetched record for conflict detection; if another transaction writes one of them first, the enclosing transaction fails to commit. Targets must be specific record ids - a literal such as `person:1`, or a parameter or function resolving to one - so whole tables, ranges (`person:1..5`), count buckets (`|person:3|`) and subquery sources are rejected, as is combining the clause with `VERSION`, `GROUP BY`, `SPLIT`, or a `LIMIT` spanning more than one target. Any `SELECT ... FOR UPDATE`, including one nested inside a value expression, makes its enclosing statement write-classified. Supported on every storage backend: in-memory, RocksDB, SurrealKV, IndexedDB and TiKV.

### 🔑 Pull SurrealDB Enterprise straight from Docker Hub (Enterprise)

SurrealDB Enterprise is now distributed as a public Docker Hub image, `surrealdb/surrealdb-enterprise`, so a deployment can pull it with no registry credential to provision or rotate, and the image enforces a licence to run. Supply the key with `SURREAL_LICENSE_KEY` or `SURREAL_LICENSE_KEY_FILE`; it is validated online against keygen.sh - Ed25519-signed, cached on disk as a re-verified attestation, and revalidated in the background - with grace windows so a keygen outage does not take a running node down. A valid licence runs; an expired one runs on a grace window; a suspended, over-limit or missing licence refuses to start.

## Improvements

- `[Query]` **Disjunctive and reachability filters now run at index speed.** Top-level `OR` filters across indexes fuse into a single bitmap union, and graph-reachability sub-conditions inside a `WHERE` compile to bitmap branches, so both evaluate over index bitmaps rather than row by row.
- `[Query]` **A closure stored in a field can be called like a method.** `$obj.method()` now falls back to calling a closure held in the object's `method` field when no built-in method of that name applies; built-ins such as `len` still take precedence, and a name that is neither still errors. Such closures also now execute with the session's namespace/database context, so a body that reads or writes the database works on every planner strategy, and a write inside one is classified read-write so it persists.
- `[Vector]` **A compaction backlog no longer inflates the memory a KNN search needs.** HNSW and DiskANN searches bound how much pending, not-yet-compacted index data one search materialises, so the read path stays bounded however far behind compaction is. The in-memory HNSW graph is retained when a compaction pass fails, so vector search survives a failure, and a search that exceeds one materialisation batch emits a `WARN` naming the index.
- `[Storage]` **Reclaiming removed data no longer spikes memory, and picks up after a restart.** Data left behind by removals - dropped indexes and the per-record doc-ID mappings they own, dropped tables, and change-feed history - is now reclaimed by a background task in bounded, resumable batches rather than one large operation. A `REBUILD INDEX` issued while the table's shared doc-ID space is still being reclaimed is refused with a message to retry.
- `[Storage]` **Less write amplification on bulk inserts and index builds.** The internal document ids used by indexed writes, and the other internal surrogate keys the catalog assigns, are now committed once per reserved window instead of once per record. User-facing sequences (`sequence::nextval`) still hand out a dense, gap-free run of values while the node is running.
- `[Memory]` **The overload guard now refuses queries against an accurate memory figure.** Tracked bytes record the size class the RocksDB allocator actually reserves rather than the requested size, so `SURREAL_MEMORY_THRESHOLD` measures what the process really holds. Four process-memory gauges (`surrealdb_process_memory_allocated_bytes`, `_requested_bytes`, `_reserved_bytes`, `_threshold_bytes`) let operators watch how close an instance is to refusing queries.
- `[Server]` **Health checks answer immediately, however slow the startup.** The HTTP listener now binds before the datastore's startup transactions run, so `/health` responds straight away, with the remaining work behind a readiness gate during which requests receive 503 and `/ready` reports not-ready. The first archived-node cleanup and peer-expiry pass is likewise off the bind path, so an instance with many archived nodes still starts serving promptly.
- `[gRPC]` **Large records and transactions fit in a single gRPC message.** `SURREAL_GRPC_MAX_MESSAGE_SIZE` bounds a message in both directions and is the figure advertised during the capability handshake, so it is set in one place; it defaults to the HTTP `/rpc` body limit (`SURREAL_HTTP_MAX_RPC_BODY_SIZE`). Raise it for a workload such as bulk ingest of embeddings. Too small a value is raised to a safe floor and an unparsable one falls back to the default. Clients get a matching `GrpcConfig` (`surrealdb::opt::GrpcConfig::max_message_size`, via `Config::grpc(...)`), and one that raises its limit above the server's advertised figure has oversized requests refused up front.
- `[gRPC]` **A gRPC failure now names its cause.** Errors carry their structured kind and details, plus the backwards-compatible numeric code, so a fault such as an oversized message arrives as a status naming the limit rather than an opaque HTTP/2 stream reset.
- `[API]` **An oversized API body is rejected before it is decoded.** `DEFINE API` gains an `api::req::max_body(limit)` middleware that answers HTTP 413 on a raw body exceeding `limit`, given as a non-negative byte count or a size string such as `"1mb"` or `"512kb"`. Only raw `bytes` and `string` bodies are measured; place it ahead of `api::req::body(...)` so oversized payloads are never parsed.

## Bug fixes

- `[Query]` An ordinary `SELECT` used as an inline subquery value - most visibly as a `FOR` range, `FOR $r IN (SELECT ...)` - now keeps its array shape regardless of row count: zero rows give `[]`, one row gives `[x]`. The shape previously followed the row count, collapsing a single-row subquery to the bare element and an empty one to `NONE`.
- `[Query]` Parentheses around an equal-precedence sub-expression are now preserved when an expression is serialised back to SurrealQL text, so a `DEFINE FUNCTION` or `DEFINE FIELD` body computes the same result after being reloaded. Previously `RETURN $x / ($a * $b)` was re-emitted without them and reparsed as `($x / $a) * $b`.
- `[Query]` `SELECT count()` now agrees with `SELECT` on which records a user may see. The count fast paths lacked the read bypass the row scan uses, so a table permission whose predicate read another table could deny every row and return `0` while `SELECT` returned them.
- `[Query]` `LIMIT` / `START` are now kept above the sort when an `ORDER BY` is withheld from access-path selection because its sort field carries a field-level `SELECT` permission. The limit was previously pushed into the record-id-ordered scan, handing the sort a truncated prefix and returning rows out of order.
- `[Schema]` A field whose name is a reserved keyword (for example `function` or `select`) now round-trips through the schema catalog, surviving later `DEFINE FIELD` statements on the same table and reading its stored data back. Stored field paths are re-parsed with the idiom grammar rather than as a general expression, so a leading keyword reads as a field name.
- `[Schema]` A silo module defined by 3.2.4 or earlier is addressable by name again. 3.3 inserts `::` before the version segment of a module's derived key, so `silo::acme::widgets<1.2.3>` written by an earlier release no longer matched the `silo::acme::widgets::<1.2.3>` the current build derives: `REMOVE MODULE` and `ALTER MODULE` reported it missing, calls to its exports could not reach it, and `OVERWRITE` wrote a second row, all while `INFO FOR DB` listed it correctly. Upgrading moves every mismatched row to the key its own value derives, carrying permissions, comments and the `unsigned` flag across. Only `silo::` names were affected.
- `[Storage]` On the in-memory (`memory`) backend, a transaction or function can now delete a record and create the same record id again within that transaction; the second create previously failed with a "key already exists" error because the engine checked the base store rather than the transaction's pending changes.
- `[WebSocket]` Replies and live-query notifications now use separate outbound queues, each sized by `SURREAL_WEBSOCKET_RESPONSE_CHANNEL_SIZE` (minimum two), so a client that pipelines many requests without reading responses in lock-step is no longer mistaken for one that has stopped reading its notifications. A client that genuinely stops reading them is closed with a stated reason rather than buffered without bound.
- `[WebSocket]` Connection teardown now completes the WebSocket close handshake (RFC 6455 5.5.1) by closing the socket rather than only flushing it, so a client that starts a close, or one closed by the server, sees a clean close instead of a `ResetWithoutClosingHandshake` transport error.
- `[Scripting]` Reading response headers in a scripting `fetch` no longer aborts the process when a remote server returns a header value containing non-ASCII or non-UTF-8 bytes. The `Headers` accessors (`entries`, `values`, `get`, `getSetCookie`) decode such values lossily; valid ASCII and multi-byte UTF-8 values are returned unchanged.
- `[Index]` Removing, replacing (`DEFINE INDEX OVERWRITE`) or rebuilding an index while writes or a `CONCURRENTLY` build are in flight now retires the old generation cleanly: the replaced index is no longer queryable through its replacement, no stale generation returns wrong results, and the retired data is cleaned up. Retirement is fenced on the transaction that removes the definition rather than on a datastore close.
- `[Index]` An index that is being built or rebuilt is left to its dedicated builder, so background index compaction on any node no longer touches an index whose on-disk layout only that builder knows.
- `[Graph]` Fixed the lazy migration of legacy graph edges: re-relating an edge stored in the old layout could leave a stale reverse key behind, so a traversal over that vertex returned the edge twice and the leftover key was never reclaimed. Both legacy vertex-side keys are now removed before the new ones are written.

## Upgrade notes

- `[Index]` Full-text and vector (HNSW, DiskANN) indexes built under 3.2 are read in their original 3.2 on-disk layout and remain queryable after upgrading, with no forced rebuild. Each keeps its legacy per-index doc-ID layout until you explicitly run `REBUILD INDEX`, which migrates it to 3.3's shared doc-ID layout.
- `[Graph]` Existing graph edges are read and converted transparently as they are re-related or written, so no re-import or migration step is required. A background maintenance task - a "fold", by default every 5 seconds and tunable - packs adjacency into blocks for fast traversal.
- `[Enterprise]` Running SurrealDB Enterprise now requires a licence key (`SURREAL_LICENSE_KEY` or `SURREAL_LICENSE_KEY_FILE`); a node with no valid, unexpired, in-limit licence refuses to start. The node reaches keygen.sh to validate, with an on-disk attestation cache and grace windows covering transient keygen unavailability.

Newer patch available

## Upgrade to 3.3.0

You are viewing the 3.3.0-beta.4 changelog. A newer patch in this release line is available - we recommend running 3.3.0 for the latest fixes and improvements.

[View 3.3.0 release notes](https://surrealdb.com/releases/3.3#release-3-3-0)

Released Aug 20, 2026

## 3.3.0-beta.3

PRE-RELEASE

The third 3.3 beta moves the permission and `COMPUTED` write rules to runtime enforcement, and fixes the embedded JavaScript engine's storage lifetime and packaging found while wiring the SDK against the published `v3.3.0-beta.2` packages. Everything new in 3.3 from `v3.3.0-beta.1` and `v3.3.0-beta.2` is included and not repeated here.

Items tagged `[Enterprise]` on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

## Improvements

- `[Query]` **Permission and `COMPUTED` write rules move to runtime enforcement.** `v3.3.0-beta.1` added a definition-time check that tried to decide, when a schema object was defined, whether a `PERMISSIONS` guard or `COMPUTED` body could ever reach a data-modifying statement. Seeing through a function call means resolving the callee's stored body against the catalog, and that answer is not sound: whether a body reaches its write depends on which branch its arguments select, so a function that conditionally calls `CREATE` / `UPDATE` was refused for every caller, including the guards and `COMPUTED` fields that never take that branch. That check is dropped: a `PERMISSIONS` clause loses its definition-time write refusal entirely, and a `COMPUTED` body loses it for writes reached through a function call. A write written directly in a `COMPUTED` body is still refused at `DEFINE` / `ALTER` time. A `DEFINE` / `ALTER` that stores a writing `SELECT` guard, or a `COMPUTED` body that reaches a write through a function call, is now accepted, and fails at the point a read actually reaches the write, with the existing `A PERMISSIONS clause cannot contain a statement that modifies data` / `A COMPUTED clause cannot contain a statement that modifies data`. The runtime frame is exact regardless of call depth or indirection (`eval`, scripts, closures arriving as data), which the definition-time check could never be. `create` / `update` / `delete` permission clauses may carry side effects, since they are reached only from a statement that is already writing. This reverses the definition-time behaviour described under *Writing computed fields and functions are refused at definition time* in `v3.3.0-beta.1`.
- `[Embedded]` **Per-platform binary packages for `@surrealdb/node-native`.** The eight native binaries are no longer bundled inside the root package. Each is published as `@surrealdb/node-native-<platform>` carrying its own `os` / `cpu` / `libc`, and the root package lists them all under `optionalDependencies`, so an install downloads only the one binary its host can use rather than all eight, keeping a `@surrealdb/node` install under AWS Lambda's 250 MB unzipped limit.

## Bug fixes

- `[Embedded]` `free()` on `@surrealdb/node-native` now releases the datastore's storage before it resolves, so a file-backed datastore can be reopened on the same path after `close()` within one process. surrealkv releases its lockfile late in shutdown and `Drop` only spawned a detached close, so `free()` previously returned with the data directory still locked. The wasm engine has no file backends and is unaffected.
- `[Embedded]` The `surrealkv+versioned://` scheme, which has named no backend since versioning moved to the `?versioned=true` parameter, now reports the supported spelling instead of the generic "unable to load the specified datastore", which read like a build compiled without the backend.
- `[SDK]` Query-stream `begin` frames now carry the protocol revision, and the structural contract a client can rely on when decoding is written down: a `stream` tag is never repurposed, unknown tags and fields are ignorable, and `single` is always present on a `finished` frame (it was already on the wire; only the SDK type made it optional).

## Upgrade notes

- The `mutable_permissions` experimental capability is removed. It existed only to reopen the `create` / `update` / `delete` guards that the definition-time rule had shut, and was on for every server already. Remove any `--allow-experimental mutable_permissions` / `--deny-experimental mutable_permissions` flag and the SDK's `ExperimentalFeature::MutablePermissions` builder entry; there is no replacement, since those guards now permit side effects unconditionally.
- The `surrealdb_statement_mutable_permission_writes_total` metric is removed along with the capability. Drop any dashboard panel or alert that referenced it.

Newer patch available

## Upgrade to 3.3.0

You are viewing the 3.3.0-beta.3 changelog. A newer patch in this release line is available - we recommend running 3.3.0 for the latest fixes and improvements.

[View 3.3.0 release notes](https://surrealdb.com/releases/3.3#release-3-3-0)

Released Aug 18, 2026

## 3.3.0-beta.2

PRE-RELEASE

The second 3.3 beta is a focused round of startup, shutdown, and readiness hardening for clustered deployments, driven by incidents observed on production multi-node clusters. Everything new in 3.3 from `v3.3.0-beta.1` is included and not repeated here.

Items tagged `[Enterprise]` on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

## Improvements

- `[Server]` **Configurable readiness heartbeat window.** The `/ready` probe treats a node as unhealthy once its cluster heartbeat goes stale, and that window can now be set directly with `--readiness-heartbeat-max-age` / `SURREAL_READINESS_HEARTBEAT_MAX_AGE`. Previously it was always derived as three times the node-membership refresh interval (9s by default), which welds the probe to the very write path it measures. That is fine for a storage engine whose node-row write is local and sub-millisecond, but under a distributed engine the same write is a consensus transaction whose latency moves with cluster health, so a slow-but-healthy write path made every replica flip `NotReady` at once and presented partial degradation as total unavailability. The default is unchanged, so behaviour is identical unless you set the flag. Startup now also warns if the configured window reaches the interval at which peers archive an unresponsive node (30s), since a node reported ready after its peers have written it off keeps taking traffic while its cluster registration and live queries are collected underneath it.
- `[Server]` **Graceful shutdown of startup background work.** Deferred startup tasks - a startup import, root credential creation, the Surrealism eager module load, and the first node-maintenance pass - now register with the datastore, so `shutdown` waits for them (bounded by the existing 30-second maintenance shutdown timeout) instead of closing the storage engine while they are still writing. Cancellation is prompt: each task selects on the shutdown token, an aborted import deliberately leaves the node not ready rather than flipping a dying pod healthy, and unreached Surrealism modules simply load on first use.

## Bug fixes

- `[Server]` Archived-node cleanup no longer gates the HTTP listener. On a cluster recovering from a restart storm, the listener could take minutes to become reachable after the storage engine was ready: the bind waited behind sequential startup operations, and `remove_nodes` opened one write transaction per archived node with no bound on how much dead-member residue a restart storm left behind. The first expiry and cleanup pass is now spawned after this node registers itself, not awaited, so it cannot hold up the bind. The periodic maintenance scheduler is unchanged.
- `[Server]` The cluster heartbeat now survives a slow write. It previously made a single attempt per tick under a fixed 60s budget, so one slow write could lose readiness roughly 51s before the attempt itself failed. Each tick now spends its whole budget - bounded relative to the readiness window - on the write and retries a fast failure within the tick, so a write that is slow but working still lands in time to keep the node ready.
- `[Enterprise]` The read-catch-up completeness watermark is now persisted, so a restart no longer re-drains the committed log from zero. The floor was held in memory only, so every process start walked the entire committed history - one quorum round trip per 100 records - under a begin whose fixed 300s request timeout it eventually exceeded; the startup gate treated that as fatal and exited, and the next start walked from the same zero floor, crashlooping. The watermark now lives in the durable `meta` column family, is restored on open clamped to the durable committed frontier, and is established at startup under a begin bounded on progress rather than wall time. Measured on a 3-node cluster: a rollout that had been crashlooping against a 30-minute timeout completed in 5m04s, the startup gate went from 300s-then-exit to single-digit milliseconds, and watermark-caused readiness 503s went to zero.
- `[Enterprise]` A pending admission freeze no longer wedges startup. A guard-holding wait inside the read catch-up - the committed-manifest window pull, with 60s of patience per page in an unbounded page loop - could hold its guard across a `Normal`-leaving view transition, so the freeze never drained and the startup warm-up failed. Because that path exits the process on any error, the node killed itself and restarted into the same state. Those waits are now raced against the admission-freeze marker and bail retriably, and the warm-up absorbs the transients under a single request budget instead of surfacing them. Store replays and offloaded reads are deliberately not preempted, since dropping an offloaded closure would strand a durable write ahead of its post-apply bookkeeping.
- `[Enterprise]` A begin or read establishment refused because a view transition is pending is now reported as a retryable `TransactionConflict` rather than a hard datastore error. The commit path deliberately keeps its unknown-outcome classification, since a commit interrupted mid-round cannot know whether other replicas still hold tentative reservations.

## Upgrade notes

- `[Enterprise]` If you applied `SURREAL_DS_STARTUP_NORMAL_TIMEOUT=0` as a break-glass on `v3.3.0-beta.1`, unset it on this build. The startup warm-up now retries through view-transition churn, so `=0` only removes protection - it disables both enterprise startup gates and leaves `check_version` as the only one.
- `[Enterprise]` Keep `SURREAL_STARTUP_OPERATION_TIMEOUT=180` for now. The default is still 60s, which is tight for a consensus engine on a cold cluster.

Newer patch available

## Upgrade to 3.3.0

You are viewing the 3.3.0-beta.2 changelog. A newer patch in this release line is available - we recommend running 3.3.0 for the latest fixes and improvements.

[View 3.3.0 release notes](https://surrealdb.com/releases/3.3#release-3-3-0)

Released Aug 14, 2026

## 3.3.0-beta.1

PRE-RELEASE

This is the first beta in the 3.3 series. It opens the line with two new ways to connect - a Postgres wire-protocol listener that lets any Postgres client run SurrealQL or ISO GQL (Cypher Query Language), and a gRPC engine with end-to-end result streaming - and makes ISO GQL (Cypher Query Language) available by default. File buckets gain S3, GCS, and Azure object-storage backends, the Node.js and browser embedded engines are rebuilt against the current engine, and a deep round of index work lands: bitmap index fusion, pre-filtered vector search, and a much faster full-text write path. For SurrealDS (Enterprise), cold-start convergence is dramatically faster, recovery cost is bounded, and cluster storage can now be S3-backed.

The items below cover what is new in the 3.3 line. The fixes already released across the `v3.2.1`–`v3.2.4` patches are also included in this build but are not repeated here.

Items tagged `(Enterprise)` on a heading or `[Enterprise]` on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

## Highlights

### 🐘 Postgres wire protocol

SurrealDB can now speak the Postgres wire protocol (v3.0), so any Postgres client - `psql`, JDBC, npgsql, tokio-postgres, and the rest - can connect and run SurrealQL or ISO GQL (Cypher Query Language) with properly typed results.

- Opt-in via `--postgres-bind <addr>` / `SURREAL_POSTGRES_BIND`, and capability-gated through a new `postgres` route target. TLS is available through the standard `SSLRequest` upgrade, reusing `--web-crt` / `--web-key`.
- Both the simple and extended query protocols are supported (Parse/Bind/Describe/Execute/Close/Sync/Flush), with text and binary codecs for every mapped type including `numeric`. The namespace and database come from the startup `database` parameter as `ns/db`, with `USE` and `LET` persisting across queries; interactive `BEGIN`/`COMMIT`/`ROLLBACK` behaves like Postgres, including `25P02` aborted-transaction poisoning, and `CancelRequest` is supported.
- The query dialect is selectable at connect time (`options=-c dialect=gql`) or in-session (`SET dialect`), so a Postgres client can run ISO GQL (Cypher Query Language) directly.
- Authentication supports cleartext passwords over TLS and **SCRAM-SHA-256** (RFC 5802 / RFC 7677). `DEFINE USER ... PASSWORD` now derives SCRAM verifier material alongside the existing Argon2 hash, and a new additive `PASSSCRAM '<verifier>'` clause on `DEFINE USER` / `ALTER USER` imports a precomputed PostgreSQL-format verifier so exports round-trip losslessly. Existing users without SCRAM material keep working.
- Postgres positional parameters `$1..$n` are rewritten to `$_1..$_n`, and SurrealDB auto-commits each top-level statement - use an explicit `BEGIN ... COMMIT` for all-or-nothing behaviour. ANSI-SQL translation, `pg_catalog` emulation, MD5/COPY, and `LIVE` queries are out of scope for now.

### 🕸 ISO GQL (Cypher Query Language) enabled by default

ISO GQL (Cypher Query Language) querying - introduced experimentally in 3.2 - is now available by default, matching GraphQL. The `--allow-experimental gql` flag, the SDK experimental-feature builder, and the `gql` cargo feature all remain valid as harmless no-ops, so existing configurations keep working. GQL is reachable over the `/gql` HTTP route, the `gql` RPC method, MCP, and the new Postgres listener.

### 📡 A gRPC engine and end-to-end result streaming

The SDK's engines now sit behind one typed `SurrealEngine` interface, and a new native gRPC engine joins the family alongside major streaming work across every transport.

- New `grpc://` / `grpcs://` connection schemes, shipped enabled in the released `surreal` binary, so `surreal sql --endpoint grpc://host:port` works out of the box.
- Query results stream end to end over gRPC via `db.query(..).stream_items()` - the client no longer waits for the whole result set to be produced. Measured on a 10,000-row `SELECT`, the first rows reach the client 65 µs after execution begins, against ~20 ms to produce them all. Rows are provisional until their statement's terminal frame arrives, which is withheld until the outcome is final.
- The WebSocket RPC protocol gains a `query_stream` request answered by a sequence of frames (`begin`, per-statement `rows`/`value`/`finished`, then one `end`), carried in JSON, CBOR, and flatbuffers alike. This brings streaming to the places gRPC cannot reach: browsers/WASM and Cloudflare Workers.
- The embedded JavaScript engines stream now too: a `QueryStream` with `next()` in `@surrealdb/node-native` and a `ReadableStream` in `@surrealdb/wasm-native`, with bounded buffering so nothing runs ahead of the reader, and abandoned streams stopping their execution instead of orphaning a transaction.

### 🪣 Cloud object storage for buckets

File buckets (`DEFINE BUCKET` and the `file::*` functions) gain cloud object-storage backends in the open-source engine, built on the `object_store` crate.

- AWS S3 and S3-compatible stores (MinIO, Backblaze B2, Wasabi, Cloudflare R2) via `s3://`, `s3+http://`, `s3+https://`; Google Cloud Storage via `gs://` / `gcs://`; Azure Blob Storage via `az://` / `azure://`. The existing S3 URL format is preserved exactly, and a latent bug that dropped a custom endpoint's port is fixed.
- Bucket traffic is now metered: three authenticated-only OpenTelemetry instruments under `surrealdb.bucket` - `sent_bytes`, `received_bytes`, and `operations`.
- Embedders can install a custom object store behind buckets with `Builder::with_bucket_store_provider(...)` - the seam needed to back buckets with a platform store such as a Cloudflare Workers R2 binding.

### 🎯 Bitmap index fusion and pre-filtered vector search

The streaming planner can now compose index results as bitmaps over a shared per-table document-ID space, and vector search can use those bitmaps to filter before traversal.

- Index-backed predicates compose with AND/OR/NOT as compressed bitmaps, and a `COUNT` answerable from indexes alone never touches records. New plan nodes (`BitmapIndexScan`, `BitmapFullTextScan`, `BitmapAnd`, `BitmapOr`, `BitmapAndNot`) are visible in `EXPLAIN`, with per-node candidate cardinalities in `EXPLAIN ANALYZE`. Existing b-tree indexes keep working with no rebuild. Non-anchor range branches are capped by `SURREAL_BITMAP_BRANCH_BUDGET` (default 250,000 entries; `0` disables), and the full `WHERE` clause is always retained as a residual filter.
- **Pre-filtered vector search.** When a KNN plan is chosen, `WHERE` conjuncts that provably match an index bitmap are evaluated into an allow-list *before* HNSW/DiskANN traversal, replacing one record fetch per visited graph node. An adaptive triage picks a tier, reported in `EXPLAIN ANALYZE` as `prefilter_tier`: `exact` (small allow-lists skip the graph entirely for guaranteed-correct top-K, threshold `SURREAL_KNN_PREFILTER_EXACT_THRESHOLD`, default 2,000), `graph` (gated traversal with a boosted search width, up to `SURREAL_KNN_PREFILTER_EF_BOOST_THRESHOLD`, default 100,000), and `graph_unboosted` above that, with a `fallback` tier reported when the allow-list cannot be built within the bitmap branch budget. Enabled by default; `SURREAL_KNN_PREFILTER_ENABLED=false` turns it off.

### ⚡ Faster full-text and count indexes

The write path of the concurrent full-text index has been overhauled end to end.

- Full-text delta-log entries are batched per transaction instead of one key per (term, document), and postings are now keyed by document rather than by term which significantly reduces write amplification. Older indexes are read through a legacy fallback, so no migration is needed.
- COUNT-index deltas are aggregated per transaction, and the compactor is woken by the commit that queues its work rather than only by its timer.
- Exports now size each emitted `INSERT` statement by the KV keys a record will write given the table's index set, so restores of heavily indexed tables use appropriately sized transactions.

### ❄️ SurrealDS cold starts, recovery, and admission (Enterprise)

A major round of work on SurrealDS cluster formation, recovery cost, and overload behaviour.

- **Faster cold-start convergence.** Replicas leaving recovery at different times could stall cluster formation on retry timers. View-change retransmission, a formation-aware backoff, and concurrent outcome-donor drains cut measured convergence tails from 4.44 s to 0.02 s (n=3) and 14.75 s to 4.61 s (n=5), and the default-config worst case from 86.9 s to 13.7 s.
- **Bounded recovery drains.** The Phase-1 outcome drain previously re-paged a donor's entire outcome history on every entry into recovery - O(all transactions ever committed). Each replica now keeps a bounded in-memory write-order journal and a recovering replica asks each donor only for what it wrote since its last position, falling back to the full stream when needed. Configured via `SURREAL_DS_RECOVERY_OUTCOME_JOURNAL_ENTRIES` (default 262,144; `0` disables). Drain behaviour is observable on new `surrealdb.ds.recovery_outcome_drain*` counters.
- **Bounded transaction write sets.** A new operation-count bound on transaction write sets, `SURREAL_DS_TRANSACTION_WRITE_SET_LIMIT_OPS` (default 100,000; `0` disables), sits beside the existing 32 MiB byte cap: an over-bound transaction fails fast at the coordinator with `TransactionTooManyOperations` before any network traffic, with a warning fired once at half the bound. Keep the value uniform across the fleet.
- **Membership-epoch correctness.** A membership-changing leader now withholds serving until a quorum of the new voter set is confirmed to hold the decided configuration; a coordinator whose epoch was superseded mid-transaction abandons the round promptly instead of resending into a silent fence; and a voter fenced for being *ahead* of its own cluster now escalates to a view change instead of starving indefinitely. The fence itself is now observable via the `surrealdb.ds.epoch_fence_drops` counter with a `direction` label.

## Improvements

- **Mutable permission clauses, behind a capability.** The 3.1-era security block on writes in *all* permission clauses over-reached for `create`/`update`/`delete` clauses, where audit-logging side effects are a relied-upon pattern. Those are re-permitted behind a new transitional `mutable_permissions` experimental capability - allowed by default on the server (deny it with `--deny-experimental mutable_permissions`), off by default for embedders. `SELECT` permission clauses stay read-only. Usage is measurable via the new `surrealdb_statement_mutable_permission_writes_total` counter. Alongside this, function mutability is now resolved against the stored call graph, so a write hidden behind a user-defined function call can no longer slip past definition-time checks on `COMPUTED` fields and permission clauses.
- **Datastore versioning and startup migrations.** The datastore now records its semantic version and runs registered, ledger-tracked data migrations on startup, resuming interrupted runs, with a per-node version history. The first migration fixes a keyspace collision present since 3.0.0, where `DEFINE SEQUENCE` keys sorted inside the table-name band and any table whose name began with `sq` could break `INFO FOR DB`, sequence exports, and `REMOVE NAMESPACE` / `REMOVE DATABASE`. Note for rolling upgrades: a sequence created after the rollout begins is not visible to nodes still on the previous release until they upgrade.
- **Wall-clock query timeouts on the transports.** The HTTP (`/sql`, `/gql`, `/graphql`) and RPC (WebSocket and HTTP) surfaces now apply a hard wall-clock timeout reusing the configured `--query-timeout` value, on top of the existing cooperative deadline. Timed-out HTTP requests return `504 Gateway Timeout` with a proper error envelope. Off unless `--query-timeout` is set; transaction-control methods are exempt, while `signin`/`signup`/`authenticate` (which can run user-defined SurrealQL) are guarded.
- **Runtime-swappable capabilities for embedders.** `Datastore::set_capabilities(...)` atomically swaps the capability set with no datastore rebuild, aimed at embedders that cannot restart per instance (such as a wasm Cloudflare Durable Object serving many tenants). In-flight queries keep the snapshot they started with, and the outbound HTTP client is rebuilt in the same swap so a runtime network tightening is enforced on redirect hops and DNS resolution too.
- **Stateless MCP.** The first-party MCP server now serves the stateless `2026-07-28` protocol revision alongside the handshake-based revisions on the same `/mcp` endpoint. Every tool except `use` gains optional `namespace` and `database` arguments, with scope resolving from call arguments, then `surreal-ns` / `surreal-db` request headers, then session state, then server defaults. The advertised protocol revision is now pinned explicitly (upgrading the underlying SDK can no longer silently change it), and the `gql` tool's annotations no longer declare it read-only - those hints drive client-side auto-approval, and the GQL dialect can write.
- **`vector::sum`.** A new function, in both forms: as a grouped aggregate it folds a vector-valued expression across a group's rows in O(dimension) state regardless of row count, and as a scalar it sums an `array<array<number>>` directly - making an in-database weighted centroid composable from existing functions.
- **New vector functions, stricter numeric edge cases.** `vector::distance::mahalanobis(a, b, cov)` and `vector::similarity::spearman(a, b)` are implemented. A family of silent-NaN or sentinel results now error instead: `vector::divide` with a zero divisor element, `normalize`/`angle`/`project` at zero magnitude, and `pearson`/`spearman` on degenerate inputs; `vector::similarity::jaccard` now uses true set semantics, and `time::nano` errors outside the i64-nanosecond range instead of returning 0.
- **`ORDER BY` outside the projection.** `SELECT event, subject FROM audit_log ORDER BY at` now works - the sort field no longer has to appear in the projection.
- **Brute-force KNN with computed query vectors.** The streaming executor now supports `<|k, DIST|>` with a non-literal query vector (bind parameters, function calls, computed arrays), closing the last unimplemented case in the read-only planner.
- **Multi-part `MATCHES`.** A full-text `@@` reached through a record link to an index on another table (`t.name @@ 'x'`) now executes on the streaming engine.
- **Index matching through record traversals.** A `WHERE` clause matching a compound index through record-idiom traversals of a parameter (for example `WHERE createdAt <= $scan.task.finishedAt`) previously always full-table-scanned; row-independent traversals are now resolved at plan time so index analysis can match them, with strict provable-equivalence conditions.
- **Silo package resolution.** Surrealism silo packages now resolve over HTTPS from a configurable endpoint (default `https://silo.surrealdb.com`), with a 256 MiB cap and organisation- and package-name restrictions that block path traversal. The fetch needs no `allow_net` grant since the host comes from server configuration.
- **Import and scan performance.** Collection literals whose elements are already values now convert without the async evaluator (a 200K-record import profile had 52.9% of CPU under literal evaluation); mock targets (`|table:N|`) are drained id-by-id instead of pre-expanding a million-entry list; nine scan loops stopped copying whole cursor batches; and a delete's reference batch is decoded once instead of per entry.
- **Quieter, correct system metrics.** One system-metrics refresher now runs per process regardless of datastore count (embedders with several datastores got CPU figures from arbitrary sub-intervals), and the metrics cache is populated before the first query so `INFO FOR ROOT` can no longer report an all-zero `system` block.
- **Storage engine updates.** The memory backend adopts surrealmx 0.24's native savepoint release, removing an emulated savepoint stack; `affinitypool` 0.8 speeds up the blocking-work pools and fixes a pool deadlock; TiKV gRPC status failures are now logged with their code and message at debug level instead of being collapsed.
- `[Enterprise]` **Recovery and starvation observability.** The Phase-1 outcome drain's certification behaviour is surfaced on three new counters; the starved-begin metric is split by starvation class (`below_quorum`, `no_local_reply` - dashboards summing only `anterior_prepared` will now under-report); and the log volume of a membership reconfiguration is bounded and rate-paced.

## Bug fixes

- `[Server]` `USE NS <expr>` / `USE DB <expr>` with a subquery argument hit an internal panic; such statements now return an ordinary query error with the transaction rolled back.
- `[Query]` A statement's read-only classification had three holes (`SELECT` clauses like `LIMIT (CREATE ...)`, `RETURN ... FETCH`, and several idiom parts), so writing statements could run on read transactions and fail partway through.
- `[Query]` `rand::*` calls in a `WHERE` clause were folded into a single plan-time constant on the streaming engine, making the predicate all-or-nothing across rows; they now evaluate per row, as SQL engines do for volatile functions.
- `[Query]` Numeric hash/equality mismatches on the streaming engine: `GROUP BY` could split one value into two groups, and `IN` / `INSIDE` / `CONTAINSANY` / `CONTAINSALL` could silently drop rows when a column mixed `float` and `decimal` representations of the same number. Grouping and set-membership now key on ordering, matching `=`.
- `[Query]` `CONTAINSALL` with mixed-representation literals dropped rows on the index-overlap path; literal equivalence classes now each get exactly one bitmap bit. Separately, a `UnionIndexScan` could silently drop a `CONTAINSANY` / `ANYINSIDE` conjunct from its residual filter and return rows that did not match the original `WHERE` clause.
- `[Query]` `RETURN DISTINCT` emitted one value as two rows when `float` and `decimal` forms of the same number hashed differently; dedup and hash-joins now key on ordering, which also halves peak join build memory.
- `[Query]` `[WHERE ...]` applied to a non-collection returned a one-element array instead of `NONE`, and sets were not filtered element-wise. Separately, `ORDER BY` ties are now stable (input order as the final key), so a `LIMIT` cutting inside a tie group can no longer repeat or drop rows across pages.
- `[Query]` The streaming executor's `MATCHES` fallback returned `true` unconditionally (`WHERE 'abc' @@ 'zzz'` matched every row); and the legacy engine's cross-table `MATCHES` evaluated a tautology that matched every row. Roughly 490 unit tests were backfilled across the executor at the same time.
- `[Query]` `BREAK` / `CONTINUE` raised inside a `LET` binding's value were swallowed by the streaming executor instead of propagating to the enclosing loop.
- `[Query]` `PERMISSIONS` predicates reaching the row via `$parent` denied every row on the streaming engine, and `math::variance` / `math::stddev` returned population figures where sample figures were correct, in both ad-hoc `GROUP BY` and materialized views. The field-permission pass could also cut whole array elements out of results, causing silent data loss including an empty `RETURN DIFF` for non-owner sessions.
- `[Query]` A closure body's writes are now reflected in its access mode, so `array::map` with a writing closure can no longer be scheduled on read-only execution paths; likewise an unresolvable `mod::` function's write flag now defaults to writeable.
- `[Query]` 85 signature divergences between the streaming function registry and the legacy layer were aligned; six `api::*` middleware functions were missing from the streaming registry entirely, and several async builtins silently ignored extra arguments instead of erroring. `object::matches` - advertised by the parser but implemented in neither registry, so every call failed at runtime - is now rejected cleanly at parse time.
- `[Query]` Values now coerce into `set` types, so `SET field += value` on a `set<...>` field works, and deduplication is applied after a field's `VALUE` clause runs rather than before.
- `[Index]` Any index rebuild could publish a durably `Online` but empty index (the compactor's generation guard was wiped with the build subspace), after which every query against it silently returned nothing. Covers `DEFINE INDEX`, `DEFINE INDEX OVERWRITE`, and `REBUILD INDEX`.
- `[Index]` Rolling back a `DEFINE INDEX ... CONCURRENTLY` could leave durable build state behind, because cleanup deleted the build keys while the builder task was still writing.
- `[Index]` A full-text term's compacted document set could depend on how compaction rounds grouped its deltas, so a document could keep a term it had lost (a false `MATCHES` hit) with nothing to correct it later; compaction now carries a per-term residual. Full-text compaction rounds are also now bounded by a document budget rather than a key count.
- `[Index]` DiskANN KNN memory and latency grew without bound under sustained write load (OOM-cycling on capped pods); the pending-update set is now sharded so per-query work tracks the active backlog.
- `[RPC]` A single client could permanently deadlock its own session across 15 RPC methods over WebSocket, HTTP, or gRPC, when a buffered query overlapped a session-mutating call (`set`, `signin`, `use`, ...).
- `[RPC]` Three paths dropped writeable transactions without committing or cancelling them - worst was async event processing, where an undecodable queue entry leaked a transaction on every batch indefinitely. Present since 3.1.
- `[RPC]` Live-query registry leaks: an ended live query kept its registry entry and its `surrealdb.live_query.active` gauge increment for the life of the connection; `REMOVE TABLE`/`DATABASE`/`NAMESPACE` and principal revocation are now covered, and gRPC streams delete subscriptions the client never claimed.
- `[RPC]` Dropping a client-side item stream mid-query leaked its transaction and driving tasks for the life of the process; the execution now stops when the stream is dropped. The per-connection WebSocket stream cap is also now claimed atomically.
- `[SDK]` Cloning a `Surreal` handle on the WebSocket engine replayed session state fire-and-forget, so the clone's first request could execute before its own `signin` - reproduced at roughly 1% of iterations. Requests now park until the replayed setup is acknowledged.
- `[Embedded]` `db.use_defaults()` cleared the session instead of applying `DEFINE CONFIG DEFAULT`, so embedded consumers silently ignored configured default namespaces/databases.
- `[Embedded]` A query abandoned mid-batch left a half-written transaction in the session that a later `commit` would commit; abandoned queries now take their transaction with them. The session registry no longer auto-creates entries for already-ended sessions.
- `[Export]` A database with an `ENFORCED` relation table could not restore its own export - every edge was silently dropped while the restore reported success. The endpoint check is now deferred under `OPTION IMPORT`, fixing existing export files too.
- `[HTTP]` `/signin` and `/signup` each applied the other's body-size limit; each route now uses its own. On wasm, `http::*` calls failed capability checks outright and carried no `User-Agent`; both fixed. `fetch()` with `redirect: "error"` / `"manual"` could not fetch any URL because its client was built from the wrong rule set.
- `[KV]` Releasing a savepoint discarded the wrong scope's undo entries on every backend, so a record created by a statement that later failed could survive along with its index writes (reachable via synchronous `DEFINE EVENT`). Also fixed a TiKV regression that roughly doubled write-path RPCs for multi-record `INSERT`/`UPSERT` after any savepoint release.
- `[KV]` surrealkv is updated to 0.21.3, carrying two durability fixes - a missing fsync after compaction that could leave a SIGKILL'd pod unable to start, and a memtable rotation bug.
- `[KV]` The `DEFINE API` handler's transaction now applies the statement write-cardinality guard (`SURREAL_TRANSACTION_MAX_WRITE_KEYS`), which previously did not cover custom API handlers.
- `[Parser]` More legacy SurrealQL accepted by the new parser (`SET idiom` forms, function-call `sleep`, keyword record ids in `RELATE` targets), and `RecordId::parse_simple` no longer adds a layer of backticks on every round trip.
- `[Enterprise]` Recovery could abort a committed, durably applied transaction when a replica's last transaction-id-keyed evidence was reaped exactly as its applied gate was set; votes are now retained until a real id-keyed decision exists.
- `[Enterprise]` OCC read sets are now validated against the writes that can actually invalidate them, tightening isolation correctness.
- `[Enterprise]` An indeterminate commit is now reported as `CommitOutcomeUnknown` - telling the client to read back rather than replay - instead of a definite "nothing was written" error.
- `[Enterprise]` Both `Transactable` implementations discarded a released savepoint's undo state, mirroring the community savepoint fix.

## Breaking changes

### ISO GQL (Cypher Query Language) is enabled by default

The `gql` experimental capability gate is removed and ISO GQL (Cypher Query Language) querying is available by default. Deployments that relied on it being off should control access via the `gql` route target and the standard capability configuration. The old `--allow-experimental gql` spelling remains accepted as a no-op.

#### `UPDATE` / `UPSERT` evaluation semantics

`UPDATE` and `UPSERT` now evaluate the `WHERE` condition *before* the data clause, so a side-effecting data clause (for example `SET spawn = (CREATE log).id`) no longer runs for records the condition rejects - previously the side-effect count could depend on whether the planner picked an index. The condition and data clause now share a single pre-mutation snapshot, so reads never observe the statement's own writes, and field clauses evaluate in dependency order rather than alphabetically. Review statements that relied on assignment-visibility or alphabetical field ordering.

#### `DEFINE MODULE` syntax (Surrealism, experimental)

Three syntax changes to the experimental Surrealism module surface: the executable form now uses `FROM` instead of `AS` (`DEFINE MODULE mod::color FROM f"modules:/color.surli" UNSIGNED;`), a trailing `UNSIGNED` keyword is now required on both executable forms, and the silo version segment is now written `silo::{org}::{pkg}::<1.0.0>` (with `::` before the version). Stored definitions are unaffected and re-render in the new syntax.

#### Memory backend no longer supports time-travel

The memory backend's versioned-read support is removed: `VERSION` clauses return an unsupported-versioned-queries error, matching `indxdb`, and the backend now rejects `versioned` / `retention` connection parameters at startup instead of accepting them and failing later. Version coverage lives in the `rocksdb` and `surrealkv` backends, which retain native versioning.

#### `duration::set_*` functions removed

All seven `duration::set_*` functions were parser-advertised but never implemented - every call failed at runtime. They now produce a clean parse-time error.

#### Writing computed fields and functions are refused at definition time

A `COMPUTED` field or permission clause whose body writes through a user-defined function call was previously accepted (and wrote on every read); function mutability is now resolved against the stored call graph and such definitions are refused at `DEFINE`/`ALTER` time.

#### Rust SDK and embedder API changes

The SDK's engine layer is now the typed `SurrealEngine` trait (one method per operation) rather than a command enum over a channel; SDK futures remain `Send` but are no longer `Sync`. `surrealdb-core` no longer re-exports `surrealdb-rpc`, so Rust consumers must depend on it directly. `Datastore::transaction(..)` loses its ignored lock-type argument, and `Datastore::get_capabilities()` returns an owned `Arc<Capabilities>`.

#### JavaScript package split

`@surrealdb/node` is split: the native addon is published as `@surrealdb/node-native` and the SDK engine implementation moves to surrealdb.js. The `strict` connection option - advertised in the typings but silently ignored - is removed and is now a type error. The wasm module is renamed `surrealdb_wasm` and published as `@surrealdb/wasm-native`.

Newer patch available

## Upgrade to 3.3.0

You are viewing the 3.3.0-beta.1 changelog. A newer patch in this release line is available - we recommend running 3.3.0 for the latest fixes and improvements.

[View 3.3.0 release notes](https://surrealdb.com/releases/3.3#release-3-3-0)

No newer release line.

Older release line

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

Updated Oct 5, 2026

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

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":"3.3","item":"https://surrealdb.com/releases/3.3"}]}
```

```json
{"@context":"https://schema.org","@type":"TechArticle","headline":"SurrealDB 3.3 release notes","description":"SurrealDB 3.3.0 lets any Postgres client query SurrealDB, streams results as they are produced, and makes graph traversals and filtered searches much faster.…","datePublished":"2026-09-24T00:00:00.000Z","dateModified":"2026-09-24T00:00:00.000Z","url":"https://surrealdb.com/releases/3.3","mainEntityOfPage":{"@type":"WebPage","@id":"https://surrealdb.com/releases/3.3"},"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":"3.3"}}
```

```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","context layer","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"]}
```
