
Surrealql
- 349 installs
- 21 repo stars
- Updated June 16, 2026
- surrealdb/agent-skills
Helps with ai & agent building tasks.
About
surrealql is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- surrealql
- AI & Agent Building
- AI-coding skill
Surrealql by the numbers
- 349 all-time installs (skills.sh)
- +20 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,139 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/surrealdb/agent-skills --skill surrealqlAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 349 |
|---|---|
| repo stars | ★ 21 |
| Last updated | June 16, 2026 |
| Repository | surrealdb/agent-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
SurrealQL
A skill for writing and modifying SurrealQL queries to interact with SurrealDB databases.
SurrealQL is the official query language for SurrealDB. It is a modern, flexible, and powerful query language that is designed to be easy to learn and use.
When to use this skill
Reference these guidelines when:
- Writing, modifying, or troubleshooting SurrealQL queries
- Designing or managing schemas
- Converting other query languages to SurrealQL
Version & Documentation
Always target the latest stable SurrealDB release. SurrealQL evolves between major versions, and syntax from older releases (e.g. SurrealDB 2.x) is a common source of incorrect, non-validating queries. Unless the user explicitly asks for an older version, generate current (3.x) syntax.
Determine the active version before generating version-sensitive syntax:
- If the SurrealDB CLI is installed, run
surreal version. - Otherwise, the latest released version is published as a plain string at
https://download.surrealdb.com (e.g. v3.1.4):
curl -s https://download.surrealdb.comhttps://surrealdb.com/docs always documents the latest stable release — treat it as the source of truth for current syntax. When in doubt about whether a function or form still applies (for example type::* helpers and how record IDs are constructed), confirm against the current docs rather than assuming older behavior.
Rules & Conventions
- SurrealQL is NOT ANSI-SQL. Never assume SQL knowledge from other databases applies. Always refer to the examples below or the documentation at https://surrealdb.com/docs for accurate syntax and behavior.
- When SurrealQL is stored in a file, it should have a
.surqlextension. - SurrealQL is a relatively young language and changes between releases. Default to the latest SurrealDB version (see Version & Documentation) and refer to https://surrealdb.com/docs for the most up-to-date syntax.
Statements
Query Statements
| Statement | Purpose |
|---|---|
| SELECT | Query records, traverse graphs, aggregate data |
| CREATE | Create new records (errors if record exists) |
| INSERT | Insert one or more records or graph edges; supports ON DUPLICATE KEY UPDATE |
| UPDATE | Update existing records (no-op if record doesn't exist) |
| UPSERT | Insert a record, or update it if it already exists |
| DELETE | Delete records or graph edges |
| RELATE | Create graph edges between records |
| LIVE SELECT | Stream real-time changes to a table |
| KILL | Cancel an active LIVE SELECT query |
| LET | Assign a value to a parameter |
| RETURN | Return a value from a block or function |
Schema & Resource Statements
| Statement | Purpose |
|---|---|
| DEFINE NAMESPACE | Define a namespace |
| DEFINE DATABASE | Define a database |
| DEFINE TABLE | Define a table (schemafull, schemaless, as view) |
| DEFINE FIELD | Define a field with type, default, assertion |
| DEFINE INDEX | Define an index (unique, search, vector) |
| DEFINE EVENT | Define event triggers on a table |
| DEFINE FUNCTION | Define a custom function |
| DEFINE ANALYZER | Define a search analyzer |
| DEFINE ACCESS | Define authentication access methods (Bearer, JWT, Record) |
| DEFINE API | Define an API endpoint |
| DEFINE BUCKET | Define a storage bucket |
| DEFINE CONFIG | Define a configuration |
| DEFINE MODULE | Define a Surrealism extension module |
| DEFINE PARAM | Define a global parameter |
| DEFINE SEQUENCE | Define an auto-incrementing sequence |
| DEFINE USER | Define a system user |
| ALTER | Alter an existing resource definition |
| REMOVE | Remove any defined resource |
| REBUILD | Rebuild an index |
| ACCESS | Manage access grants |
| USE | Switch to a different namespace or database |
| INFO | Inspect definitions for a resource |
| SHOW | View changefeed for a table or database |
Control Flow Statements
| Statement | Purpose |
|---|---|
| BEGIN / COMMIT | Begin and commit a manual transaction |
| CANCEL | Cancel a transaction |
| IF / ELSE | Conditional execution |
| FOR | Iterate over values |
| BREAK | Exit a FOR loop early |
| CONTINUE | Skip to next iteration in a FOR loop |
| THROW | Cancel execution and return an error |
| SLEEP | Pause execution for a duration |
References
For detailed querying patterns (filtering, graph traversal, aggregation, subqueries), see references/querying.md.
For schema management patterns (tables, fields, indexes, events, access), see references/schema.md.
For in-depth information about the values that can be stored in SurrealDB records, see references/values.md.
Validation
When generating SurrealQL queries, or modifying existing queries, you should always validate them using the SurrealDB CLI if available. Validation may fail to due version differences, at which point you can retrieve your SurrealDB CLI version with surreal version. Validation can only be performed against full queries or values, not partial or fragmentary statements.
Usage
# Validate a single file:
surreal validate query.surql
# Validate glob pattern of files:
surreal validate queries/*.surql
# Validate from stdin (available since SurrealDB v3.1.0):
echo "SELECT * FROM person WHERE age > 18" | surreal validate --stdinFormatting
When generating SurrealQL queries or SQON values you may decide to format them using the surqlfmt CLI tool if a NodeJS-like runtime is available. Situations in which you should always format include:
- When presenting queries to users
- When generating migration files
- When writing
.surqlfiles
Usage
# Format a file and print to stdout:
npx @surrealdb/surql-fmt query.surql
# Format files in-place:
npx @surrealdb/surql-fmt --write migrations/*.surql
# Check if files are already formatted (exits with code 1 if not):
npx @surrealdb/surql-fmt --check src/**/*.surql
# Format from stdin:
echo "SELECT * FROM person WHERE age>18" | npx @surrealdb/surql-fmt --stdinData Querying Patterns
Common SurrealQL patterns for querying and mutating data. For full statement syntax, see the statements docs.
SELECT
-- All records
SELECT * FROM person;
-- Specific fields
SELECT name, age FROM person;
-- By record ID
SELECT * FROM person:john;Filtering
SELECT * FROM person WHERE age > 25;
SELECT * FROM person
WHERE age > 18
AND email IS NOT NONE
AND address.country = 'NL';
SELECT * FROM person WHERE tags CONTAINS 'developer';Ordering, Limits, and Pagination
SELECT * FROM person ORDER BY name ASC LIMIT 10;
SELECT * FROM person ORDER BY created_at DESC LIMIT 20 START AT 40;Aggregation
SELECT count() FROM person GROUP BY city;
SELECT count(), math::mean(age) AS avg_age FROM person GROUP BY country;Subqueries
SELECT * FROM person WHERE id IN (SELECT VALUE author FROM article);
SELECT
name,
(SELECT VALUE count() FROM ->likes) AS like_count
FROM person;SELECT VALUE
Return flat values instead of records.
SELECT VALUE name FROM person WHERE age > 21;SPLIT
Expand array fields into separate rows.
SELECT * FROM person SPLIT ON tags;FETCH
Resolve record links inline.
SELECT * FROM article FETCH author, comments;CREATE
Create new records. Errors if the record already exists.
-- Auto-generated ID
CREATE person CONTENT { name: 'Alice', age: 30 };
-- Specific record ID
CREATE person:alice CONTENT { name: 'Alice', age: 30 };
-- Using SET syntax
CREATE person SET name = 'Alice', age = 30;
-- Return control
CREATE person:bob CONTENT { name: 'Bob' } RETURN AFTER;INSERT
Insert one or more records. Supports ON DUPLICATE KEY UPDATE for upsert behaviour based on unique indexes.
-- Single record
INSERT INTO person { name: 'Alice', age: 30 };
-- Bulk insert
INSERT INTO person [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
];
-- Upsert on duplicate key
INSERT INTO person { name: 'Alice', age: 31 }
ON DUPLICATE KEY UPDATE
age = $input.age;
-- Insert graph edges
INSERT RELATION INTO knows {
in: person:alice,
out: person:bob,
};UPDATE
Update existing records. No-op if the record doesn't exist (use UPSERT to create-if-missing).
-- Replace entire content
UPDATE person:alice CONTENT { name: 'Alice', age: 31, city: 'Amsterdam' };
-- Merge with existing data
UPDATE person:alice MERGE { age: 31, city: 'Amsterdam' };
-- Set individual fields
UPDATE person:alice SET age = 31, city = 'Amsterdam';
-- Unset fields
UPDATE person:alice UNSET city;
-- JSON Patch
UPDATE person:alice PATCH [
{ op: 'replace', path: '/age', value: 31 },
];
-- Conditional bulk update
UPDATE person SET age += 1 WHERE age < 30;
-- Return control
UPDATE person:alice SET age = 31 RETURN DIFF;UPSERT
Insert a record if it doesn't exist, update it if it does.
UPSERT person:alice SET name = 'Alice', age = 31;
UPSERT person:alice MERGE { settings: { theme: 'dark' } };
-- With WHERE (only updates if condition matches)
UPSERT person:alice SET name = 'Alice' WHERE active = true;DELETE
-- Specific record
DELETE person:alice;
-- All records in a table
DELETE person;
-- Conditional
DELETE person WHERE active = false;
-- Return deleted records
DELETE person:alice RETURN BEFORE;
-- Delete graph edges
DELETE person:alice->knows WHERE out = person:bob;RELATE
Create graph edges between records. Edge tables can hold additional data.
-- Simple relationship
RELATE person:alice -> knows -> person:bob;
-- With edge data
RELATE person:alice -> wrote -> article:intro CONTENT {
date: d'2024-01-15',
word_count: 1500,
};
-- Using SET
RELATE person:alice -> likes -> post:123 SET
created_at = time::now(),
reaction = 'love';Graph Traversal
-- Outbound: people that alice knows
SELECT ->knows->person FROM person:alice;
-- Inbound: people who know bob
SELECT <-knows<-person FROM person:bob;
-- Chained traversal
SELECT ->wrote->article->has->category.name FROM person:alice;
-- Filter on traversal
SELECT * FROM person WHERE ->knows->person.age > 30;LIVE SELECT
Stream real-time changes. Returns a UUID identifying the live query.
LIVE SELECT * FROM person;
LIVE SELECT * FROM person WHERE active = true;
-- Receive diffs instead of full records
LIVE SELECT DIFF FROM person;Cancel with KILL <uuid>.
Transactions
Wrap multiple statements in a transaction for atomicity.
BEGIN TRANSACTION;
UPDATE account:alice SET balance -= 100;
UPDATE account:bob SET balance += 100;
RELATE account:alice -> transfer -> account:bob SET
amount = 100,
timestamp = time::now();
COMMIT TRANSACTION;
-- Cancel instead of committing
BEGIN TRANSACTION;
UPDATE account:alice SET balance -= 100;
IF $error {
CANCEL TRANSACTION;
};
COMMIT TRANSACTION;FOR Loops
FOR $item IN $items {
CREATE item SET
name = $item.name,
value = $item.value;
};
-- With BREAK / CONTINUE
FOR $user IN (SELECT * FROM user) {
IF $user.role = 'bot' {
CONTINUE;
};
CREATE notification SET target = $user.id;
};RETURN
-- Return a literal
RETURN 42;
-- Return a query result
RETURN SELECT * FROM person WHERE age > 18;
-- Return from a block
{
LET $x = 10;
LET $y = 20;
RETURN $x + $y;
};Parameterized Queries
Use parameters to prevent injection and improve readability.
LET $min_age = 18;
SELECT * FROM person WHERE age > $min_age;
-- Safely parameterize table names
SELECT * FROM type::table($table);Schema Management
Patterns for defining and managing schemas in SurrealDB. For full syntax, see the DEFINE docs.
Namespace & Database
SurrealDB organises data into namespaces and databases. Switch between them with USE.
DEFINE NAMESPACE production;
DEFINE DATABASE app;
USE NS production DB app;Tables
-- Schemaless (default): accepts any fields
DEFINE TABLE post SCHEMALESS;
-- Schemafull: only explicitly defined fields are allowed
DEFINE TABLE person SCHEMAFULL;
-- Table as a view (pre-computed aggregation)
DEFINE TABLE post_stats AS SELECT
product,
count() AS total,
math::mean(rating) AS avg_rating
FROM review
GROUP BY product;Permissions
DEFINE TABLE person SCHEMAFULL
PERMISSIONS
FOR select, create FULL
FOR update WHERE $auth.id = id
FOR delete WHERE $auth.role = 'admin';Fields
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD age ON TABLE person TYPE int;
DEFINE FIELD email ON TABLE person TYPE string
ASSERT string::is::email($value);
DEFINE FIELD created_at ON TABLE person TYPE datetime
VALUE time::now()
DEFAULT time::now();
DEFINE FIELD tags ON TABLE post TYPE option<array<string>>
DEFAULT [];Indexes
-- Unique index
DEFINE INDEX idx_email ON TABLE person FIELDS email UNIQUE;
-- Full-text search index
DEFINE INDEX idx_title ON TABLE article
FIELDS title
FULLTEXT ANALYZER my_analyzer BM25(1.2, 0.75);
-- Vector index (HNSW)
DEFINE INDEX idx_embedding ON TABLE document
FIELDS embedding
HNSW DIMENSION 384 DIST COSINE TYPE F32;Rebuilding Indexes
Force an index to be rebuilt (e.g. after changing analyzer settings).
REBUILD INDEX idx_title ON TABLE article;Events
DEFINE EVENT log_create ON TABLE person
WHEN $event = 'CREATE'
THEN {
CREATE log SET
table = 'person',
action = $event,
record = $after.id,
time = time::now();
};Analyzers
DEFINE ANALYZER my_analyzer
TOKENIZERS blank, class
FILTERS lowercase, snowball(english);Functions
DEFINE FUNCTION fn::greet($name: string) {
RETURN 'Hello, ' + $name + '!';
};
-- With typed parameters and return type
DEFINE FUNCTION fn::calculate($base: float, $multiplier: float) -> float {
RETURN math::round($base * $multiplier, 2);
};Access (Authentication)
DEFINE ACCESS account ON DATABASE
TYPE RECORD
SIGNUP (
CREATE user SET
name = $name,
email = $email,
password = crypto::argon2::generate($password)
)
SIGNIN (
SELECT * FROM user
WHERE email = $email
AND crypto::argon2::compare(password, $password)
)
DURATION FOR TOKEN 15m, FOR SESSION 12h;Users
DEFINE USER admin ON DATABASE PASSWORD 'secret' ROLES OWNER;Parameters
DEFINE PARAM $default_limit VALUE 50;Sequences
Auto-incrementing sequences for generating sequential IDs.
DEFINE SEQUENCE invoice_number;ALTER
Modify an existing definition without dropping and recreating it.
-- Add a comment to an existing table
ALTER TABLE person COMMENT 'Main user table';
-- Change a field type
ALTER FIELD age ON TABLE person TYPE float;Removing Definitions
REMOVE TABLE person;
REMOVE FIELD email ON TABLE person;
REMOVE INDEX idx_email ON TABLE person;
REMOVE EVENT log_create ON TABLE person;
REMOVE FUNCTION fn::greet;
REMOVE ANALYZER my_analyzer;
REMOVE ACCESS account ON DATABASE;
REMOVE USER admin ON DATABASE;
REMOVE PARAM $default_limit;
REMOVE SEQUENCE invoice_number;Inspecting Definitions
INFO FOR ROOT;
INFO FOR NS;
INFO FOR DB;
INFO FOR TABLE person;Changefeed
View recent changes to a table or database using SHOW.
SHOW CHANGES FOR TABLE person SINCE d'2024-01-01T00:00:00Z';SurrealQL Values
In SurrealDB, values represent the data that can be stored in records. These values encompass a wide variety of types allowing for rich data modeling and flexible expressions within SurrealQL queries. Understanding the different value types and their semantics is crucial for designing schemas, constructing queries, and ensuring accurate data representation in SurrealDB.
The syntax used to represent values in SurrealQL is known as "SurrealQL Object Notation" - or "SQON" for short.
Data types
None
none represents the explicit absence of a value. It is distinct from null and is used to indicate that a field has no value at all. Responses typically omit none-valued fields entirely rather than including them.
Example SQON:
NONENull
null represents an unknown or undefined value. While semantically similar to none, null conveys "value is unknown" rather than "value is absent".
Example SQON:
NULLBool
A boolean value: true or false.
Example SQON:
true
falseNumber
SurrealDB supports three numeric subtypes, all of which fall under the umbrella number type:
| Subtype | Storage | Range / precision | SQON syntax |
|---|---|---|---|
int | 64-bit signed integer | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | 42 |
float | 64-bit IEEE 754 double | ≈15–17 significant decimal digits | 3.14 or 3.14f |
decimal | 128-bit decimal floating point | Arbitrary precision, no IEEE 754 rounding | 3.14dec |
A numeric literal without a decimal point and within the int range is stored as an int. A literal with a decimal point or outside the int range is stored as a float.
Underscores in numeric literals are ignored and can be used for readability (e.g. 1_000_000).
Example SQON:
42 -- Int
3.14f -- Float
3.14159265358979dec -- DecimalDuration
A non-negative time span with nanosecond precision. Durations are composed of one or more unit segments:
| Unit | Meaning |
|---|---|
ns | Nanoseconds |
us / µs | Microseconds |
ms | Milliseconds |
s | Seconds |
m | Minutes |
h | Hours |
d | Days |
w | Weeks |
y | Years |
Units can be combined in a single literal: 1y2w3d4h5m6s7ms8us9ns. A duration can be zero (0ns) but cannot be negative.
Example SQON:
1h30m
2w3d
100msString
A UTF-8 encoded text value of arbitrary length. Strings can contain Unicode characters, emojis, tabs, and line breaks. You can prefix strings with certain characters to indicate their type (e.g. datetime, uuid, file).
Example SQON:
'hello'
"hello"Datetime
An RFC 3339 / ISO 8601 timestamp with nanosecond precision. Datetimes are stored internally as UTC; a timezone offset in the input is converted to UTC on storage.
Example SQON:
d"2024-01-15T09:30:00Z"UUID
A universally unique identifier conforming to RFC 4122. SurrealDB supports UUID v4 (random) and v7 (time-ordered).
Example SQON:
u"01924b3c-f1a2-7e3d-a001-2f4b8c9d0e1f"Array
An ordered, indexed collection of values. Arrays may contain values of any type, including nested arrays and objects. Individual elements are accessed by zero-based index. An optional element type and length constraint can be specified in schema definitions (e.g. array<string, 5>).
Example SQON:
[1, 2, 3]Set
An ordered, automatically deduplicated collection of values. Sets differ from arrays in two ways: duplicate values are removed, and values are sorted. Sets support the same element type and length constraints as arrays in schema definitions.
Example SQON:
{,} -- Empty set (comma is required)
{1,} -- Set with one element (trailing comma is required)
{1, 2, 3} -- Set with many elementsObject
An unordered key-value map with string keys and values of any type. Objects may be nested and can contain any other value type. The quoting of keys is optional and only required if the key contains special characters or is a reserved word.
Example SQON:
{ name: 'Jane', age: 30 }Geometry
A geospatial value conforming to RFC 7946 (GeoJSON). SurrealDB supports the following geometry subtypes:
| Subtype | Description |
|---|---|
Point | A single position (longitude, latitude) |
LineString | An ordered sequence of positions |
Polygon | A closed shape with an exterior ring and optional interior rings (holes) |
MultiPoint | A collection of points |
MultiLineString | A collection of line strings |
MultiPolygon | A collection of polygons |
GeometryCollection | A heterogeneous collection of geometry objects |
Example SQON:
{ type: "Point", coordinates: [-122.4194, 37.7749] } -- Regular GeoJSON object
(-122.4194, 37.7749) -- Special shorthand notation for PointBytes
Raw binary data. Bytes are typically displayed in hexadecimal encoding.
Example SQON:
b"48656C6C6F"Record ID
A record ID uniquely identifies a single record within a table. It is composed of two parts: a table name and an identifier.
The identifier can take several forms:
| Form | Example (SQON) |
|---|---|
| Text | user:tobie |
| Numeric (64-bit int) | user:42 |
| UUID | user:u"01924b3c-f1a2-7e3d-a001-2f4b8c9d0e1f" |
| Array (composite key) | temperature:['London', d'2025-02-13'] |
| Object (structured key) | user:{ name: 'john', age: 30 } |
| Generated | user:rand(), user:ulid(), user:uuid() |
| Range | temperature:['London', '2022-08-29T08:03:39']..['London', '2022-08-29T08:09:31'] |
Record IDs are immutable and double as record links — holding a record ID is sufficient to traverse to another record's data.
Example SQON:
user:abc123
user:42
user:u"01924b3c-f1a2-7e3d-a001-2f4b8c9d0e1f"
user:{ name: 'john', age: 30 }
temperature:['London', d'2025-02-13']File
A reference to a file in a storage bucket.
Example SQON:
f"bucket:/path/to/file.txt"Range
A bounded or unbounded range of values. Ranges are composed of the .. operator with optional lower and upper bounds:
| Syntax | Meaning |
|---|---|
a..b | From a (inclusive) to b (exclusive) |
a..=b | From a (inclusive) to b (inclusive) |
a>..b | From a (exclusive) to b (exclusive) |
a>..=b | From a (exclusive) to b (inclusive) |
a.. | From a (inclusive), unbounded above |
..b | Unbounded below, to b (exclusive) |
.. | Fully unbounded (infinite range) |
Ranges can be constructed from any value type supporting comparison.
Example SQON:
0..10 -- From 0 (inclusive) to 10 (exclusive)
0..=10 -- From 0 (inclusive) to 10 (inclusive)
0>..10 -- From 0 (exclusive) to 10 (exclusive)
0>..=10 -- From 0 (exclusive) to 10 (inclusive)
0.. -- From 0 (inclusive), unbounded above
..10 -- Unbounded below, to 10 (exclusive)
.. -- Fully unbounded (infinite range)