SurrealDB, the BSL licence and browser access
SurrealDB is a database written in Rust that serves documents, graphs, relational tables and key-value pairs from one engine, and a client can connect to it straight from the browser over WebSocket. The engine ships under Business Source License 1.1, a licence that is not open, and every deployment decision starts there.
What SurrealDB does and does not do
The engine exposes one query language, SurrealQL, and one set of schema statements: DEFINE TABLE, DEFINE FIELD, DEFINE INDEX, DEFINE ACCESS, DEFINE USER, DEFINE SEQUENCE. Records carry identifiers built from a table name and a key, links between them are written with RELATE, and change subscriptions come from LIVE SELECT. Authentication sits in the same process, so there is no separate authorisation layer to write.
You run the database as a server or embed it in a Rust application. Storage is picked with compile features: in the surrealdb crate at version 3.2.4 those are kv-mem, kv-rocksdb, kv-surrealkv, kv-indxdb and kv-tikv. The default feature set is protocol-ws plus rustls, while protocol-http, scripting, ml, jwks and http are switched on separately.
[dependencies]
surrealdb = { version = "3.2.4", default-features = false, features = ["protocol-http", "rustls"] }What SurrealDB does not do matters just as much. It does not replace PostgreSQL where a mature query planner and twenty years of surrounding tools decide the outcome. It is not a dedicated vector database in the sense of Qdrant, even though it carries vector indexes. It is not a finished backend in the style of PocketBase either, because an admin panel and access rules are two different things.
The Business Source License 1.1, read literally
The LICENSE file at the root of the surrealdb/surrealdb repository holds the full text of Business Source License 1.1 with its parameter header. The licensor is SurrealDB Ltd., the licensed work is described as SurrealDB 3.0 with copyright held by SurrealDB Limited from 2025, the Change Date falls on 1 January 2030, and the Change License is Apache License 2.0. The licence text itself states plainly that it is not an open source licence.
The rights BSL grants unconditionally are narrower than a quick reading suggests: copying, modifying, creating derivative works, redistributing and non-production use. Production use is permitted only within the scope set by the Additional Use Grant parameter, and that parameter is the heart of the matter.
The Additional Use Grant in this repository says you may use the licensed work provided you do not use it as a Database Service. The definition follows in the same paragraph: any product, service, platform or commercial offering in which SurrealDB provides database functionality to third parties, other than your own employees and contractors working on your behalf, in a way that lets those third parties create, manage or control schemas or tables.
Translated into concrete cases it reads like this. A SaaS application where you define the schema and customers only enter data sits inside the permitted scope. Internal analytics for your own staff sits inside it too, since employees are excluded from the third-party wording by definition. Selling SurrealDB hosting to customers does not. A low-code platform or a backend-as-a-service where tenants define their own tables does not either, and that is precisely the class of product for which SurrealDB looks most tempting. Anyone building one needs a separate commercial licence from the vendor.
The transformation date works per version. The licence says rights under the Change License take effect on the Change Date or on the fourth anniversary of the first public distribution of a given version, whichever comes first. Version 3.2.4 was published on 3 August 2026, so its fourth anniversary falls on 3 August 2030, while the Change Date falls on 1 January 2030, and that is the earlier of the two. This particular version therefore becomes Apache 2.0 code on the first of January 2030. Older lines carry their own parameters: the licence file under tag v2.3.7 describes the licensed work as SurrealDB 2.0 and gives a Change Date of 17 September 2029. There is no single date for the whole project.
Three further clauses complete the picture. The licence must be displayed conspicuously on every copy, original and modified. A breach terminates your rights to the current version and to every other version of the licensed work. The licence grants no rights to the vendor's trademarks or logos beyond the use it requires itself.
Three licence sources and what does not line up
A dependency audit checks three places, because they drift apart.
| Source | What it shows for SurrealDB |
|---|---|
| File in the repository | LICENSE at the root, full BSL 1.1 text |
| Package registry | crates.io reports non-standard for surrealdb, surrealdb-core, surrealdb-types |
| Published artifact | the 3.2.4 .crate package contains a LICENSE file with the BSL 1.1 text |
The first source is unambiguous. I also checked spelling variants and files that describe a licensing arrangement: LICENSE.md, LICENSING.md, NOTICE and COPYING at the root of the main branch all return 404, so no extra document changes the picture.
The second source looks suspicious but has a mundane explanation. The Cargo.toml of the published package carries no SPDX identifier in the license field, only license-file = "LICENSE". The crates.io registry then has no name to display and writes non-standard. Dependency scanners that trust the registry field will therefore show a meaningless string for SurrealDB instead of BSL. If you keep a licence inventory at work, enter Business Source License 1.1 by hand together with the Change Date, because no automation will do it for you.
The third source confirms the declaration. The unpacked surrealdb package at version 3.2.4 contains a LICENSE file with the BSL 1.1 text, a src directory with seventy-three .rs files, benches and tests directories, build.rs, Cargo.lock and README.md. The code is in the package, the licence file is in the package, and its content matches what sits in the repository.
On the JavaScript side the same test comes out worse. The npm package surrealdb at version 2.0.8 declares Apache-2.0 in package.json, while its archive holds exactly seven files and none of them is a licence file.
npm pack surrealdb@2.0.8
tar tzf surrealdb-2.0.8.tgz
# package/package.json
# package/README.md
# package/dist/surrealdb.cjs, .d.ts, .mjs, surrealdb.server.cjs, surrealdb.server.mjsThe Apache 2.0 text lives in the surrealdb/surrealdb.js repository but never reaches the artifact. If your compliance process requires a licence file inside the distributed package, this one lacks it. The Python package has a different gap: the PyPI metadata for version 2.0.0 carries neither a license field nor a licence classifier, while the LICENSE file in the surrealdb/surrealdb.py repository is Apache 2.0.
The clients carry a different licence than the server
This distinction decides what actually enters your product.
| Component | Version | Licence |
|---|---|---|
Server and the surrealdb crate | 3.2.4 | BSL 1.1 |
Crates surrealdb-core, surrealdb-types | 3.2.4 | BSL 1.1 |
npm package surrealdb | 2.0.8 | Apache 2.0 |
PyPI package surrealdb | 2.0.0 | Apache 2.0 per the repository |
The Rust development kit is not an exception to BSL, it is part of it. The surrealdb crate at 3.2.4 depends on surrealdb-core, surrealdb-types and surrealdb-types-derive, all pinned to the same 3.2.4 version, and each ships the same BSL file inside the package. I unpacked surrealdb-types at 3.2.4 and the licence text is identical, with the same Change Date. A Rust client is therefore BSL code, not a permissive library.
The JavaScript and Python kits are Apache 2.0, a permissive licence. The code you ship to the browser is free of BSL restrictions. The restriction applies to the server you operate.
The npm package has a thin dependency layer and the metadata shows it: one production dependency, @surrealdb/sqon at the exact version 0.1.0, plus peer dependencies typescript in the range ^5.0.0 || ^6.0.0 and tslib in the range ^2.6.3. There is no engines field, so the package declares no minimum Node version. An exactly pinned zero-line dependency means a fix in @surrealdb/sqon requires a new release of the main package.
Versions, or what is stable today
Version numbers need careful reading here, because the newest releases on the list are pre-release.
curl -s -H "User-Agent: check" https://crates.io/api/v1/crates/surrealdb | \
python3 -c "import json,sys; c=json.load(sys.stdin)['crate']; print(c['max_stable_version'], c['newest_version'])"
# 3.2.4 3.3.0-beta.3The newest stable version is 3.2.4, published on 3 August 2026. The 3.3.0 line exists only as a beta: 3.3.0-beta.1 from 14 August, 3.3.0-beta.2 from 18 August and 3.3.0-beta.3 from 20 August 2026. The earlier stable cadence was dense: 3.2.0 on 2 July, 3.2.1 on 10 July, 3.2.2 and 3.2.3 on the same day of 21 July, 3.2.4 on 3 August. None of the fourteen newest versions in the registry is marked as yanked.
Dates in the GitHub releases feed drift from the registry, because the timestamp there describes creation or editing of the release note rather than package publication. The entry for release 3.2.4 carries a timestamp from 18 August 2026, while the registry package dates from 3 August. When planning a rollout, take the registry date.
Compatibility across versions is the second reason to watch this line closely. Vendor documentation describes automatic data migrations as a mechanism available only from 3.3.0. The datastore then records the version that last opened it and applies pending migrations before the first query, recorded in a ledger covering the whole cluster. The consequence runs one way: a downgrade to an older release is refused if the datastore has run a migration the older build does not know, and going back then requires an export through surreal export and a reimport into a datastore created by the older version.
The first of those migrations repairs a defect worth knowing before you deploy 3.2.x. On versions from 3.0 to 3.2 the key layout for DEFINE SEQUENCE overlaps tables whose names begin with sq. A database holding such a table stops answering INFO FOR DB, cannot be dropped with REMOVE DATABASE and so stays undroppable, and an export that includes sequences fails. No sequence has to exist for this to happen. The fix is in 3.3.0, which is in beta today.
Development kit numbering does not track the server. The server sits at 3.2.4, the npm package at 2.0.8 from 21 July 2026, and the stable PyPI package at 2.0.0 from 23 April 2026, with pre-release 3.0.0b8 builds also present in the registry. I found no server compatibility table in the npm package README, so treat pairing a given kit with a given server as something to test rather than assume.
Browser access and permissions in SurrealQL
The distinguishing feature of SurrealDB is that the browser connects to the database without a backend of your own. It looks like this:
import { Surreal } from "surrealdb"
const db = new Surreal()
await db.connect("wss://my-instance.aws-euw1.surreal.cloud")
await db.use({ namespace: "test", database: "test" })
await db.signin({ username: "root", password: "root" })That last call in this form is exactly the mistake that ends in a leak, and the reason lies in the identity split built into the engine. SurrealDB knows system users, defined through DEFINE USER at root, namespace or database level, and record users, defined through DEFINE ACCESS with type RECORD. Table permissions apply to the second group only. In the 3.2.4 execution code the check for a system user reduces to whether the actor holds a role allowing reads or edits and whether the target namespace and database fall inside the actor's level. The PERMISSIONS clause is not consulted at all. Root credentials in the browser therefore grant full read and write access regardless of what you wrote next to your tables.
The default setting is closed on its own. The 3.2.4 parser assigns NONE for all four operations to a table defined without a PERMISSIONS clause, so a record user sees nothing until you write a rule. The leak does not come from the default value, it comes from PERMISSIONS FULL typed during development and forgotten, or from an administrative key in the frontend bundle.
DEFINE ACCESS user ON DATABASE TYPE RECORD
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
DURATION FOR TOKEN 15m, FOR SESSION 12h;
DEFINE TABLE note SCHEMAFULL
PERMISSIONS
FOR select, update, delete WHERE owner = $auth.id
FOR create WHERE $auth.id != NONE;The $auth parameter is a protected name inside the engine, alongside $access, $token and $session, and it points at the record the session was issued for. DURATION takes separate values for the token and for the session, and for grants as well with access types that issue them. The DEFINE ACCESS statement supports the OVERWRITE and IF NOT EXISTS variants, which matters during schema migrations.
The blast radius of a mistake is total, because the client sends arbitrary SurrealQL. The db.query method takes a string, so there is no server-side allowlist of queries to serve as a second line of defence. Anybody who opens developer tools sees the WebSocket frames and replays them with the same token. Against a table set to PERMISSIONS FULL that means reading every record and being able to delete them. One more edge case exists: when server authentication is disabled and the session is anonymous, the permission check does not run at all. That mode is convenient locally and a disaster when exposed on a public address.
The access model here is closer to PocketBase and Supabase than to a classic database, but the difference matters. In Supabase the rules are written as access policies inside a relational database, in PocketBase as rule expressions on a collection, and in SurrealDB as clauses in the table definition. In all three cases an empty or overly broad rule is the only thing standing between your data and the internet.
Surreal Cloud pricing
The vendor pricing page renders plan text without JavaScript, so the numbers can be read from the raw HTML. The interactive calculator only works once scripts run and its static text does not state the specification of the paid configurations, so I am not guessing at them.
| Plan | Price | What it includes |
|---|---|---|
| Start | 0 USD per hour, then from 0.021 USD per hour | one free instance, 1 GB of storage and 1 GB of monthly egress at no charge |
| Scale | 0.192 USD per node per hour | multiple availability-zone deployment, horizontal scaling |
| Enterprise | custom quote | self-hosted deployment, audit, support |
The free configuration in the calculator is 0.25 vCPU and 1 GB of memory at 0.000 USD per hour. The arithmetic for the paid plans works out like this: 0.021 USD per hour across 730 hours per month gives roughly 15.33 USD per month for the cheapest paid instance, and 0.192 USD per node per hour gives roughly 140.16 USD per month per node, so about 420.48 USD per month for three nodes before you add storage and egress. The Agent Memory add-on carries separate self-serve plans from 29 USD per month with a one-week trial.
Two items on the feature list for the Start and Scale plans are marked with an asterisk described as coming soon, database branching among them. Treat them as an announcement rather than something a rollout plan can rest on.
SurrealDB against the alternatives
| Option | Data model | Engine licence | Client in the browser |
|---|---|---|---|
| SurrealDB 3.2.4 | document, graph, relational, key-value | BSL 1.1, Apache 2.0 from 2030-01-01 | yes, over WebSocket |
| PostgreSQL | relational plus extensions | permissive | no, through a middle layer |
| MongoDB Community | document | SSPL version 1 | not directly |
| Neo4j | native graph, Cypher | community or commercial | not directly |
| PocketBase | relational on SQLite | MIT | yes, over REST |
| Supabase | relational on PostgreSQL | permissive | yes, over REST and realtime |
SurrealDB wins where the same dataset has to be read once as a document and once as a graph, and where you want one process instead of three. A query that walks links, filters on document fields and ranks by vector similarity is a single query in SurrealQL, while a stack built from PostgreSQL, Neo4j and Qdrant means three queries plus code that merges the results, plus keeping three copies of the data in sync.
It loses where risk counts for more than convenience. The licence rules out a whole class of products. The stable line has no automatic migrations yet. The operational tooling ecosystem is young next to what PostgreSQL or even Redis offers. If the main argument is a fast start with client-side access, PocketBase and Supabase do the same job under licences nobody needs to explain to a lawyer.
Common mistakes
Calling SurrealDB an open source project. BSL is not an open licence and says so about itself in plain words. If the word open appears in a board presentation, somebody will decide on a false premise.
Skipping the Additional Use Grant when building a multi-tenant product. The restriction is not about selling software in general, it is about giving third parties the ability to create and manage schemas and tables. A product where tenants design their own data models falls inside that restriction.
Copying the non-standard value from crates.io into a licence inventory. That is an artefact of a missing SPDX identifier, not a licence name. The entry should read Business Source License 1.1 together with the Change Date.
Shipping system user credentials to the browser. A root account ignores PERMISSIONS clauses, so no table rule protects the data from anybody who reads the key out of the frontend bundle.
Leaving PERMISSIONS FULL in place after development. The default for a table without that clause is closed, so a broad rule is always somebody's deliberate entry that was later forgotten.
Assuming the development kit version number matches the server version. The npm package 2.0.8 talks to the 3.2.4 server, and those numbers are unrelated.
Creating a table whose name begins with sq on versions 3.0 through 3.2. The result is a database that cannot be dropped or described through INFO FOR DB.
FAQ
Is SurrealDB open source software?
No. The engine ships under Business Source License 1.1, and that licence text states it is not an open source licence, promising only that the licensed work will become one later. The JavaScript and Python development kits are a separate matter and carry the Apache 2.0 licence.
Can I use SurrealDB in a commercial product?
Yes, as long as the product is not a database service in the sense of the Additional Use Grant. An application where you set the schema and customers enter data sits inside the permitted scope. A platform where customers create and manage their own tables requires a separate agreement with the vendor.
When does the code become available under Apache 2.0?
The Change Date in the current licence file is 1 January 2030 and the target licence is Apache 2.0. The date counts separately for each version and takes effect no later than the fourth anniversary of that version's first public distribution. The 2.x line carries a Change Date of 17 September 2029 in its own file.
Which version should I deploy today?
The stable one is 3.2.4 from 3 August 2026. The 3.3.0 line is three pre-release builds so far and brings automatic data migrations plus the fix for the defect with table names beginning with sq. Running a beta in production for the sake of one fix trades one risk for another.
Is browser access safe?
It is safe when the browser signs in as a record user defined through DEFINE ACCESS of type RECORD, and every table carries a PERMISSIONS clause referring to $auth. System accounts bypass those clauses, so an administrative key in frontend code grants full access to the database.
Do I have to reload data when upgrading?
Moving up to 3.3.0, no, because the datastore applies pending migrations itself before the first query. Moving back to an older release, yes: the server refuses to open a datastore that has run a migration it does not know, and you have to export the data and load it into a datastore created by the older version.