This feature is provided by the contrib/surrealql package, which is outside of the backward compatibility guarantees of the core SDK. Its API may change without following semantic versioning.
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:
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:
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.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
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:
sql,vars:=q.Build() // BEGIN TRANSACTION; CREATE users SET name = $set_1; CREATE users SET name = $set_2; COMMIT TRANSACTION;
Note
Text-based transactions built with the query builder are different from interactive transactions. Text-based transactions execute all statements atomically in a single RPC call. Interactive transactions allow inspecting results between statements.
Learn more
Executing queries for running the built queries with surrealdb.Query
Data manipulation for the typed CRUD functions as an alternative to the query builder