Upgrading from 2.x to 3.x
This guide consolidates all breaking changes when upgrading from SurrealDB 2.x to 3.x, organised by severity level. If you are using Surrealist, you can use the migration diagnostics to automatically see your data. This will also provide you with a list of actions you need to take to migrate your data.
Migration diagnostics in Surrealist
Surrealist provides a built-in migration diagnostics tool that can be used to automatically see your data and provide you with a list of actions you need to take to migrate your data.
Note

Select your 2.x database and click on the Migration option in the sidebar. This will open the migration diagnostics tool. First you'll need to start the checks by clicking on the Start Checks button. This will return a migration report with a list of actions you need to take to migrate your data (If any).

After resolving the issue, click on the Mark as resolved button to mark the issue as resolved. This will remove the issue from the migration report.
Once all issues have been resolved, the migration diagnostics tool will allow you to export a V3 Compatible Export that can be imported into your updated SurrealDB 3.x instance.
V3 compatible export
Starting with SurrealDB 2.6.0, you can export your database in a format that is compatible with version 3.x. This export automatically performs several transformations to ensure your data and schema work correctly in version 3.x.
The V3 Compatible Export automatically handles the following transformations:
Function name updates: All deprecated function names are automatically renamed to their new versions.
duration::from::*→duration::from_*string::is::*→string::is_*type::is::*→type::is_*time::is::*→time::is_*time::from::*→time::from_*rand::guid()→rand::id()type::thing→type::record
Note
SEARCH ANALYZER→FULLTEXT ANALYZER: Index definitions usingSEARCH ANALYZERare automatically converted toFULLTEXT ANALYZER.Parameter declarations: Automatically adds
LETkeyword where required for parameter declarationsMTREE→HNSWconversion: Vector search indexes using the deprecatedMTREEtype are automatically converted to useHNSW.Future to COMPUTED field conversion: Where possible,
<future>fields are automatically converted toCOMPUTEDfields.
What requires manual migration
Some changes cannot be automatically converted and require manual intervention:
Futures stored in records (using
DEFAULT <future>orCREATE ... SET field = <future>)Nested fields with
<future>valuesQueries using both GROUP and SPLIT clauses
Code using removed operators (
~,!~,?~,*~)Stored closures in records
Record reference syntax
ANALYZEstatement usage
Using V3 compatible export
After completing the migration diagnostics in Surrealist and resolving all flagged issues, you can export your database using the v3 compatible export feature. This will generate a .surql file that can be safely imported into SurrealDB 3.x.
Next, in the Surrealist overview page, click on Deploy instance select the available plan and configure your instance (you can opt to upload a file to this instance with the v3 compatible export file).
At checkout, you will be prompted to enter your payment details (if you don't have a payment method on file) and indicate that the instance is used to migrate to SurrealDB 3.0. This will let us know to give you migration credits on your account.

Using the CLI
If you prefer to migrate using the command line rather than Surrealist, the SurrealDB 3.x binary includes a v2 subcommand that can connect to your 2.x database and produce a v3-compatible export. This is necessary because the 3.x binary cannot directly read 2.x data, and the 2.x binary does not support the v3-compatible export format.
Note
Important
Step 1: Export your v2 database
Use the surreal v2 export command with the --v3 flag to export your 2.x database in a format compatible with 3.x:
The --v3 flag ensures the export applies the same automatic transformations described in the V3 compatible export section above.
Step 2: Import into your v3 instance
Once the export is complete, import the file into your 3.x instance using the standard surreal import command:
For the full list of available options for each command, see the export command and import command documentation.
Severity Levels
In this section, we will explore the different severity levels of the migration report and the actions you need to take to migrate your data. These severity levels are as follows:
Will break: Almost guaranteed to change query semantics when porting to
3.x.Can break: Some use cases will remain the same, but likely to cause issues.
Unlikely break: Only affects edge cases or rare usage patterns.
Will break - critical changes
1. Futures replaced with COMPUTED Fields
Severity: Will break
What changed: The <future> type has been completely removed and replaced with COMPUTED fields.
Migration actions:
Use the migration tool to automatically convert futures where possible.
Manually replace
VALUE <future> { expression }withCOMPUTED expression.Restructure code for cases where automatic conversion isn't possible (nested fields,
DEFAULTfutures).
Before (2.x):
After (3.x):
Note
COMPUTED restrictions:
Can only be used in
DEFINE FIELDstatementsNo nested fields allowed inside or under
COMPUTEDfieldsCannot be used on ID fields
Cannot combine with:
VALUE,DEFAULT,READONLY,ASSERT,REFERENCE,FLEXIBLEOnly works on top-level fields, not nested fields
Example - nested field workaround:
2. Function name changes
Severity: Will break
Action: Update all function names according to the mapping table below.
Reason for Changes:
::is::and::from::→::is_and::from_(matches method syntax)thing→record(consistent terminology)rand::guid()→rand::id()(default record ID format)string::distance::osa_distance→string::distance::osa(remove redundancy)
Complete mapping table:
| New Function Name | Previous name |
|---|---|
duration::from_days | duration::from::days |
duration::from_hours | duration::from::hours |
duration::from_micros | duration::from::micros |
duration::from_millis | duration::from::millis |
duration::from_mins | duration::from::mins |
duration::from_nanos | duration::from::nanos |
duration::from_secs | duration::from::secs |
duration::from_weeks | duration::from::weeks |
geo::is_valid | geo::is::valid |
string::distance::osa | string::distance::osa_distance |
string::is_alphanum | string::is::alphanum |
string::is_alpha | string::is::alpha |
string::is_ascii | string::is::ascii |
string::is_datetime | string::is::datetime |
string::is_domain | string::is::domain |
string::is_email | string::is::email |
string::is_hexadecimal | string::is::hexadecimal |
string::is_ip | string::is::ip |
string::is_ipv4 | string::is::ipv4 |
string::is_ipv6 | string::is::ipv6 |
string::is_latitude | string::is::latitude |
string::is_longitude | string::is::longitude |
string::is_numeric | string::is::numeric |
string::is_record | string::is::record |
string::is_semver | string::is::semver |
string::is_url | string::is::url |
string::is_ulid | string::is::ulid |
string::is_uuid | string::is::uuid |
time::is_leap_year | time::is::leap_year |
time::from_nanos | time::from::nanos |
time::from_micros | time::from::micros |
time::from_millis | time::from::millis |
time::from_secs | time::from::secs |
time::from_ulid | time::from::ulid |
time::from_unix | time::from::unix |
time::from_uuid | time::from::uuid |
type::is_array | type::is::array |
type::is_bool | type::is::bool |
type::is_bytes | type::is::bytes |
type::is_collection | type::is::collection |
type::is_datetime | type::is::datetime |
type::is_decimal | type::is::decimal |
type::is_duration | type::is::duration |
type::is_float | type::is::float |
type::is_geometry | type::is::geometry |
type::is_int | type::is::int |
type::is_line | type::is::line |
type::is_none | type::is::none |
type::is_null | type::is::null |
type::is_multiline | type::is::multiline |
type::is_multipoint | type::is::multipoint |
type::is_multipolygon | type::is::multipolygon |
type::is_number | type::is::number |
type::is_object | type::is::object |
type::is_point | type::is::point |
type::is_polygon | type::is::polygon |
type::is_range | type::is::range |
type::is_record | type::is::record |
type::is_string | type::is::string |
type::is_uuid | type::is::uuid |
type::record | type::thing |
Learn more about the database functions in the SurrealQL functions documentation.
3. array::range argument changes
Severity: Will break
What changed: Arguments changed from (offset, count) to (start, end) or accepting a range.
Action: Change all array::range calls to use start/end bounds instead of offset/count.
Before (2.x):
After (3.x):
Migration formula:
Old:
array::range(offset, count)New:
array::range(offset, offset + count)
4. LET required for parameters
Severity: Will break
What changed: Parameter declarations now require LET keyword.
Action: Add LET before all parameter declarations.
Before (2.x):
After (3.x):
Error Message:
5. GROUP and SPLIT cannot be used together
Severity: Will break
What changed: Using both GROUP and SPLIT in the same query is no longer allowed.
Action: Remove SPLIT from any query which also had a GROUP clause, as its inclusion had no effect in 2.x. If the use of both a SPLIT and a GROUP is required, put one of the two clauses into a subquery.
Before (2.x):
After (3.x) - Option 1 (split then group):
After (3.x) - Option 2 (group then split):
6. Like operators removed
Severity: Will break
What changed: The ~, !~, ?~, *~ operators have been removed.
Action: Replace with string::distance or string::similarity functions.
Reason: Multiple similarity algorithms now available; users should choose their own cutoff point.
Before (2.x):
After (3.x):
Available Functions:
string::similarity::jaro()string::distance::osa()And other similarity/distance functions
7. SEARCH ANALYZER → FULLTEXT ANALYZER
Severity: Will break
Action: Replace all instances of SEARCH ANALYZER with FULLTEXT ANALYZER.
Before (2.x):
After (3.x):
8. Database-Level strictness
Severity: Will break (if using --strict flag)
What changed: Strictness moved from instance-level flag to database-level definition.
Action: Add STRICT to DEFINE DATABASE statements for databases that need strictness.
Before (2.x):
After (3.x):
Impact: Allows different databases on the same instance to have different strictness levels.
9. MTREE removal
Severity: Will break
What changed: MTREE vector search index was deprecated in 2.x and has been removed.
Action: Use HNSW instead of MTREE in index definitions.
Before (2.x):
After (3.x):
10. Stored closures
Severity: Will break
What changed: Closures can no longer be stored as part of a record.
Action: Use of closures stored inside a record will have to be removed, there is currently no new feature which can replace the stored closures.
Before (2.x):
After (3.x):
11. Usage of record references
Severity: Will break
What changed: Record references were an experimental feature in 2.x and in 3.x the syntax of record references has been significantly altered.
Action: Record references in 2.x will have to be updated manually to 3.x syntax.
12. Usage of ANALYZE statement
Severity: Will break
What changed: The ANALYZE statement which could provide some statistics about full text indexes has been removed.
Action: Use the ANALYZE stastement will have to be removed.
Can break - likely issues
13. All .* idiom behavior
Severity: Can break
What changed: The .* (all idiom) behavior changed for arrays and objects.
Breaks when: Used to dereference record IDs in arrays or get object values.
Before (2.x):
After (3.x):
Migration:
For arrays: Replace
.*.*with.*For objects: Replace
.*withobject::values()function
14. Field idiom followed by another idiom part
Severity: Can break
What changed: Field idioms on arrays now work on individual elements instead of the whole array.
Breaks when: Field idiom on array of objects is followed by another idiom part.
Before (2.x):
After (3.x):
Migration: Swap idiom parts if old behavior needed.
Old:
.field[0]New:
[0].field
15. Idiom fetching changes
Severity: Can break
What changed: Multiple improvements to idiom fetching behavior.
Quick Reference Table:
| Example | 2.x Output | 3.x Output |
|---|---|---|
[1, a:1].* | [1, a:1] | [1, { id: a:1 }] |
[1, a:1].*.* | [NONE, { id: a:1 }] | [NONE, { id: a:1 }] |
a:1.* | { id: a:1 } | { id: a:1 } |
{ key: 123 }.* | [123] | { key: 123 } |
a:1<-edge[0] | { id: edge:1 } | edge:1 |
[{ n: 1 }, { n: 2 }].n[0] | 1 | [NONE, NONE] |
Action: Review queries using these idioms and rewrite if necessary.
16. Optional parts syntax change
Severity: Can break
What changed: Optional operator changed from ? to .?
Action: Replace ? with .? after optional values.
Before (2.x):
After (3.x):
Reason: Distinguishes between ?? operator and optional chaining on option<option<value>>.
17. Parsing changes
Severity: Can break
Record ID parsing:
Unicode parsing:
Identifier escaping: Escaped identifiers now support escape sequences like \n, \u{AB1234}.
18. New set type behaviour
Severity: Can break
What changed: Set type now both deduplicates AND orders items, displays with {} instead of [].
Before (2.x):
After (3.x):
Migration Options:
Use new set type (recommended)
Maintain old behavior: Define as
arrayaddingVALUE $value.distinct()toDEFINE FIELDdefinition
19. Schema strictness changes
Severity: Can break
Non-existing tables:
SCHEMAFULL Tables:
20. Numeric record ID ordering
Severity: Can break
What changed: Numeric values in record now have different ordering and equality when used in keys. Previously, a:[1], a:[1f] and a:[1dec] were all different record IDs and could have different records.
Numeric values in record IDs are now ordered by their numeric value, meaning the a:[1], a:[1f] and a:[1dec] are the same key. Furthermore, a:[0f] is now ordered before a:[1].
Breaks when: Code depends on different numeric types resulting in different record IDs.
Before (2.x):
After (3.x):
Unlikely break - edge cases
21. math::sqrt returns NaN
Severity: Unlikely break
What changed: Returns NaN instead of NONE for negative numbers.
Action: Change checks from NONE to NaN.
22. math::min returns Infinity
Severity: Unlikely break
What changed: Returns Infinity instead of NONE for empty arrays.
Action: Change checks from NONE to Infinity.
23. math::max returns -Infinity
Severity: Unlikely break
What changed: Returns -Infinity instead of NONE for empty arrays.
Action: Change checks from NONE to -Infinity.
24. array::logical_and Behavior
Severity: Unlikely break
What changed: Function is now consistent with && operator.
Breaks when: Relying on specific values rather than truthiness.
Before (2.x):
After (3.x):
Action: Update if relying on specific return values; no change needed if only checking truthiness.
25. array::logical_or behavior
Severity: Unlikely break
What changed: Function now consistent with || operator.
Breaks when: Relying on specific values rather than truthiness.
Before (2.x):
After (3.x):
Action: Update if relying on specific return values; no change needed if only checking truthiness.
26. Mock value type changes
Severity: Unlikely break
What changed: Mocks now return arrays instead of special mock type.
Breaks when: Code depends on the specific mock type being returned.
Before (2.x):
After (3.x):
Note
27. Id field special behavior.
Severity: Unlikely break
What changed: Special behavior regarding .id idioms is removed.
Before 3.0, .id idioms followed by another idiom expression would return the record-id key. After 3.0, the .id behaves like any other .field idiom.
Breaks when: Code depends the special behavior of that .id idioms had.
Before (2.x):
After (3.x):
28. Expressions now allowed inside queries
Many statements had parts changed to support general expressions in those places. This means that identifiers which overlap with statements are no longer supported in those places without escaping. For example, syntax like the following was previously allowed:
This must now be written with backticks, or renamed.
The statements which had this change from an identifier to allowing a general expressions are the following:
The
identafterDEFINE TABLE ident ...The
identafterDEFINE NAMESPACE ident ...The
identafterDEFINE DATABASE ident ...The
identafterDEFINE USER ident ...The
identafterDEFINE ACCESS ident ...Both
identandtableafterDEFINE EVENT ident ON table ...Both
identandtableafterDEFINE FIELD ident ON table ...The
identafterDEFINE ANALYZER ident ...The
identafterDEFINE BUCKET ident ...The
identafterDEFINE SEQUENCE ident ...The
identafterINFO FOR TABLE ident ...The
identafterINFO FOR USER ident ...Both
identandtableafterINFO FOR INDEX ident ON table ...The
identafterREMOVE TABLE ident ...The
identafterREMOVE NAMESPACE ident ...The
identafterREMOVE DATABASE ident ...The
identafterREMOVE USER ident ...The
identafterREMOVE ACCESS ident ...Both
identandtableafterREMOVE EVENT ident ON table ...Both
identandtableafterREMOVE FIELD ident ON table ...Both
identandtableafterREMOVE INDEX ident ON table ...The
identafterREMOVE ANALYZER ident ...The
identafterREMOVE BUCKET ident ...The
identafterREMOVE SEQUENCE ident ...
29. DEFINE FIELD number of items for arrays and sets
A DEFINE FIELD statement for arrays and sets in SurrealDB 2.x allowed a maximum number of items to be indicated. This number now refers to the required number of items.
As such, a schema with an ASSERT $value().len() is equal to a certain number can now have the required number in the type definition itself. Additionally, definitions that indicate a maximum number of items must be changed to ASSERT $value.len() <= followed by the maximum number.