Working with types

This page contains a number of examples and tips for working with various data types that go beyond the general API documentation for each type.

For general information on these data types, see the data types page in the query language documentation.

Working with arrays is one of the most important skills when working with SurrealDB, as SELECT statements return an array of values by default unless the ONLY keyword is used on an array that contains a single item.

-- Even this returns an array
SELECT * FROM 9;
-- Use the `ONLY` clause to return a single item
SELECT * FROM ONLY 9;
-- Error: array has more than one item
SELECT * FROM ONLY [1,9];

Output

-------- Query 1  --------

[
9
]

-------- Query 2 --------

9

-------- Query 3 --------

'Expected a single result output when using the ONLY keyword'

This also means that a SELECT statement used to fetch records from a datastore can be used unchanged for any other array of values.

LET $ten_items = [8,5,3,2,6,4,76,9,8,5];
SELECT * FROM $ten_items START 5 LIMIT 5;

Output

[
4,
76,
9,
8,
5
]

Other syntax can be used to achieve the same result, such as pulling from a range of indexes.

LET $ten_items = [8,5,3,2,6,4,76,9,8,5];
$ten_items[5..10];

Using typed LET statements is a good practice when prototyping code or when getting used to SurrealQL for the first time. Take the following example that attempts to count the number of true values in a field by filtering out values that are not true, without noticing that the field actually contains strings instead of booleans. The query output ends up being 0, rather than the expected 2.

CREATE some:record SET vals = ["true", "false", "true"];
some:record.vals.filter(|$val| $val = true).len();

Output

0

Breaking this into multiple typed LET statements shows the error right away.

LET $vals: array<bool> = some:record.vals;
LET $len: number = $vals.filter(|$val| $val = true).len();
$len;

Output

-------- Query 1 --------

"Tried to set `$vals`, but couldn't coerce value: Expected `bool`
but found `'true'` when coercing an element of `array<bool>`"

-------- Query 2 --------

'There was a problem running the filter() function.
no such method found for the none type'

-------- Query 3 --------

NONE

With the location of the error in clear sight, a fix is that much easier to implement.

-- Use .map() to turn each string into a bool
LET $vals: array<bool> = some:record.vals.map(|$val| <bool>$val);
LET $len: number = $vals.filter(|$val| $val = true).len();
$len;

Output

2

The array::map() function provides access to each item in an array, allowing an opearation to be performed on it before being passed on. Other similar functions can also be used, such as array::filter() which passes on an array that only contains the items that return true to an expression of your choice.

The following example shows how to chain these functions to validate and modify data in a single statement. The example below removes any items with a NONE, checks to see if a the location data is a valid geometric point, and then returns the remaining items as objects with a different structure.

[
NONE,
{
at: (98, 65.7),
name: "Some city"
},
{
at: (-190.7, 0),
name: NONE
},
{
name: "Other city",
at: (0.0, 0.1)
},
{
name: "Nonexistent city",
at: (200.0, 66.5)
}
]
.filter(|$v| $v != NONE AND $v.name != NONE)
.filter(|$v| $v.at.is_valid())
.map(|$v, $i| {
item: $i,
name: $v.name,
coordinates: $v.at
});

Output

[
{
coordinates: (98, 65.7),
item: 0,
name: 'Some city'
},
{
coordinates: (0, 0.1),
item: 1,
name: 'Other city'
}
]

A combination of files and SurrealDB's encoding functions can be used to set up ad-hoc memory storage. This can be convenient when running an instance that saves data to disk but prefers to keep certain items in memory.

The following example shows how this pattern might be used for temporary storage such as a user's shopping cart during a single session.

# Set the allowlist env var to allow the directory to be accessed
SURREAL_BUCKET_FOLDER_ALLOWLIST="/users/your_user_name" \
surreal start --allow-experimental files
-- Set up the in-memory backend
DEFINE BUCKET my_bucket BACKEND "file:/users/your_user_name";

-- Convenience functions to save, decode back into
-- SurrealQL type, and delete
DEFINE FUNCTION fn::save_file($file_name: string, $input: any) {
LET $file = type::file("shopping_carts", $file_name);
$file.put(encoding::cbor::encode($input));
};

DEFINE FUNCTION fn::get_file($file_name: string) -> object {
encoding::cbor::decode(type::file("shopping_carts", $file_name).get())
};

DEFINE FUNCTION fn::delete_file($file_name: string) {
type::file("shopping_carts", $file_name).delete();
};

-- Save current shopping cart
fn::save_file("temp_cart_user_24567", {
items: ["shirt1"],
last_updated: time::now()
});

fn::get_file("temp_cart_user_24567");
-- Returns { items: ['shirt1', 'deck_of_cards'], last_updated: d'2025-11-20T01:03:24.141080Z' }

-- User adds item, save over file with newer information
fn::save_file("temp_cart_user_24567", {
items: ["shirt1", "deck_of_cards"],
last_updated: time::now()
});

fn::get_file("temp_cart_user_24567");
-- Returns { items: ['shirt1', 'deck_of_cards'], last_updated: d'2025-11-20T01:06:02.752429Z' }

-- Session is over, delete temp file
fn::delete_file("temp_cart_user_24567");

The following example includes five records from an open database with cities worldwide that have of a population of at least 1000. The queries below create a city record from each entry that includes their name, location, and name. Next, it uses the geo::distance function to find their two closest neighbours, relating them via the close_to relation table. The final query can be viewed in traditional form to see each city's neighbours, or on Surrealist's graph view to see a visual representation of the network of closely linked cities.

DEFINE TABLE city SCHEMAFULL;
DEFINE FIELD name ON city TYPE string;
DEFINE FIELD location ON city TYPE point;

FOR $city IN [{"geoname_id": "5881639", "name": "100 Mile House", "ascii_name": "100 Mile House", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "02", "admin2_code": "5941", "admin3_code": "5941005", "admin4_code": null, "population": 1980, "elevation": null, "dem": 928, "timezone": "America/Vancouver", "modification_date": "2019-11-26", "label_en": "Canada", "coordinates": {"lon": -121.28594, "lat": 51.64982}},{"geoname_id": "5896969", "name": "Beaverlodge", "ascii_name": "Beaverlodge", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "01", "admin2_code": "4819009", "admin3_code": null, "admin4_code": null, "population": 2219, "elevation": null, "dem": 723, "timezone": "America/Edmonton", "modification_date": "2024-02-28", "label_en": "Canada", "coordinates": {"lon": -119.43605, "lat": 55.21664}},{"geoname_id": "5911606", "name": "Burnaby", "ascii_name": "Burnaby", "feature_class": "P", "feature_code": "PPLA3", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "02", "admin2_code": "5915", "admin3_code": "5915025", "admin4_code": null, "population": 202799, "elevation": null, "dem": 87, "timezone": "America/Vancouver", "modification_date": "2019-02-26", "label_en": "Canada", "coordinates": {"lon": -122.95263, "lat": 49.26636}},{"geoname_id": "5920996", "name": "Chertsey", "ascii_name": "Chertsey", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "10", "admin2_code": "14", "admin3_code": "62047", "admin4_code": null, "population": 4836, "elevation": null, "dem": 251, "timezone": "America/Toronto", "modification_date": "2016-06-22", "label_en": "Canada", "coordinates": {"lon": -73.89095, "lat": 46.07109}},{"geoname_id": "5941905", "name": "Dorset Park", "ascii_name": "Dorset Park", "alternate_names": null, "feature_class": "P", "feature_code": "PPLX", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "08", "admin2_code": "3520", "admin3_code": null, "admin4_code": null, "population": 25003, "elevation": null, "dem": 164, "timezone": "America/Toronto", "modification_date": "2020-05-02", "label_en": "Canada", "coordinates": {"lon": -79.28215, "lat": 43.75386}}]

{
CREATE type::record("city", <int>$city.geoname_id) SET
location = <point>[$city.coordinates.lon, $city.coordinates.lat],
name = $city.name;
};

FOR $city IN SELECT * FROM city {
LET $this_location = $city.location;
LET $closest =
(SELECT id, location, geo::distance($this_location, location) AS distance FROM city
ORDER BY distance ASC
LIMIT 3
).filter(|$c| $c.distance != 0);
FOR $closest IN $closest {
RELATE $city->close_to->$closest SET
distance = geo::distance($city.location, $closest.location);
};
};

SELECT name, id, ->close_to->city AS neighbours FROM city;

Was this page helpful?