Query builder

The surrealql query builder lets you construct SurrealQL queries programmatically using a fluent Go API. It automatically binds values as parameters to prevent injection, and produces a query string and variables map that you can pass directly to surrealdb.Query.

Installing the package

The query builder is part of the SDK module. Import it alongside the main package:

import "github.com/surrealdb/surrealdb.go/contrib/surrealql"

Building and executing queries

Every query builder type has a .Build() method that returns a SurrealQL string and a map[string]any of parameters. Pass these directly to surrealdb.Query:

q := surrealql.Select("users").Where("age > ?", 18).Limit(10)
sql, vars := q.Build()

results, err := surrealdb.Query[[]User](ctx, db, sql, vars)

Selecting records

Use Select to start a SELECT query. Chain .Fields(), .Where(), .OrderBy(), .Limit(), and other methods to refine it.

surrealql.Select("users")
// SELECT * FROM users

surrealql.Select("users").Fields("name", "email").Where("active = ?", true)
// SELECT name, email FROM users WHERE active = $param_1

surrealql.Select("users").
Field(surrealql.Expr("count()").As("total")).
GroupBy("department")
// SELECT count() AS total FROM users GROUP BY department

SelectOnly produces a SELECT ... ONLY query that returns a single record instead of an array. You can pass RecordID and Table values directly as targets:

surrealql.SelectOnly(models.NewRecordID("users", "tobie")).Fields("name")
// SELECT ONLY name FROM $id_1

Filtering with Where

The .Where() method accepts a condition string with ? placeholders. Values are bound as parameters automatically.

surrealql.Select("products").
Where("price < ?", 100).
Where("category = ?", "electronics")
// SELECT * FROM products WHERE price < $param_1 AND category = $param_2

Ordering and pagination

surrealql.Select("users").
OrderByDesc("created_at").
Limit(20).
Start(40)
// SELECT * FROM users ORDER BY created_at DESC LIMIT 20 START 40

Creating records

Use Create to build a CREATE query. Set fields with .Set() or provide the entire content with .Content().

surrealql.Create("users").
Set("name", "Alice").
Set("age", 30)
// CREATE users SET name = $set_1, age = $set_2

surrealql.Create("users").Content(map[string]any{
"name": "Alice",
"age": 30,
})
// CREATE users CONTENT $content_1

CreateOnly produces a CREATE ... ONLY query:

surrealql.CreateOnly("users").Set("name", "Alice").ReturnNone()
// CREATE ONLY users SET name = $set_1 RETURN NONE

Updating records

Use Update to build an UPDATE query with .Set() and .Where():

surrealql.Update("users").
Set("active", false).
Where("last_login < ?", "2025-01-01")
// UPDATE users SET active = $set_1 WHERE last_login < $param_1

Compound operations are supported in .Set():

surrealql.Update("products").Set("stock -= ?", 1).Where("id = ?", "products:apple")
// UPDATE products SET stock -= $set_1 WHERE id = $param_1

Upserting records

Use Upsert to build an UPSERT query. This creates a record if it does not exist, or updates it if it does. After calling Upsert, choose one of the data modes: .Set(), .Content(), .Merge(), .Patch(), or .Replace().

surrealql.Upsert("users:tobie").Set("name", "Tobie").Set("active", true)
// UPSERT users:tobie SET name = $set_1, active = $set_2

surrealql.Upsert("users:tobie").Content(map[string]any{
"name": "Tobie",
"active": true,
})
// UPSERT users:tobie CONTENT $upsert_content_1

surrealql.Upsert("users:tobie").Merge(map[string]any{"active": false})
// UPSERT users:tobie MERGE $upsert_merge_1

Upsert queries also support .Where(), .Timeout(), .Parallel(), and .Explain().

Deleting records

Use Delete to build a DELETE query:

surrealql.Delete("sessions").Where("expired_at < ?", "2025-01-01")
// DELETE sessions WHERE expired_at < $param_1

Inserting records

Use Insert to build an INSERT query. This is useful for bulk inserts and inserting from subqueries.

surrealql.Insert("users").Value(map[string]any{
"name": "Alice",
"age": 30,
})
// INSERT INTO users $insert_data_1

surrealql.Insert("users").
Fields("name", "age").
Values("Alice", 30).
Values("Bob", 25)
// INSERT INTO users (name, age) VALUES ($insert_0_0_1, $insert_0_1_1), ($insert_1_0_1, $insert_1_1_1)

surrealql.Insert("users").Relation().Value(map[string]any{
"in": "users:tobie",
"out": "posts:first",
})
// INSERT RELATION INTO users $insert_data_1

Handle conflicts with ON DUPLICATE KEY UPDATE:

surrealql.Insert("products").
Fields("id", "stock").
Values("products:apple", 10).
OnDuplicateKeyUpdateRaw("stock += $input.stock")

Creating relations

Use Relate to build a RELATE query between two records:

surrealql.Relate(
models.NewRecordID("users", "tobie"),
"wrote",
models.NewRecordID("posts", "first"),
).Set("created_at", "2026-01-01")

Using expressions

The Expr function creates parameterized expressions with ? placeholders. Use it for function calls, graph traversals, or any expression that needs value binding.

surrealql.Select("users").
Field(surrealql.Expr("math::mean([?, ?, ?])", 1, 2, 3).As("avg"))
// SELECT math::mean([$param_1, $param_2, $param_3]) AS avg FROM users

surrealql.Select(surrealql.Expr("?->knows->users", models.NewRecordID("users", "tobie")))
// SELECT * FROM $from_param_1->knows->users

Return clauses

All mutation queries support return clauses:

surrealql.Create("users").Set("name", "Alice").ReturnNone()
// CREATE users SET name = $set_1 RETURN NONE

surrealql.Update("users").Set("active", true).Return("AFTER")
// UPDATE users SET active = $set_1 RETURN AFTER

surrealql.Delete("sessions").ReturnBefore()
// DELETE sessions RETURN BEFORE

Building text-based transactions

The query builder can compose text-based transactions with Begin:

q := surrealql.Begin().
Then(surrealql.Create("users").Set("name", "Alice")).
Then(surrealql.Create("users").Set("name", "Bob"))

sql, vars := q.Build()
// BEGIN TRANSACTION; CREATE users SET name = $set_1; CREATE users SET name = $set_2; COMMIT TRANSACTION;

Learn more